# -*- coding: utf-8 -*- import os import subprocess import threading import time import tkinter from tkinter import TOP, LEFT, RIGHT, messagebox, filedialog, DISABLED, NORMAL from tkinter.ttk import Progressbar import cv2 import filetype import numpy from PIL import Image, ImageDraw, ImageFont ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ") # 所用字符列表 # 将256灰度映射到70个字符上 def get_char(r, g, b, alpha=256): if alpha == 0: return ' ' length = len(ascii_char) gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b) unit = (256.0 + 1) / length return ascii_char[int(gray / unit)] def frame2char(frame): im = Image.fromarray(frame) origin_img_width = im.width origin_img_height = im.height new_img_width = int(im.width / 6) new_img_height = int(im.height / 15) im_txt = Image.new("RGB", (origin_img_width, origin_img_height), (255, 255, 255)) im = im.resize((new_img_width, new_img_height), Image.NEAREST) txt = "" colors = [] for i in range(new_img_height): for j in range(new_img_width): pixel = im.getpixel((j, i)) colors.append((pixel[0], pixel[1], pixel[2])) if len(pixel) == 4: txt += get_char(pixel[0], pixel[1], pixel[2], pixel[3]) else: txt += get_char(pixel[0], pixel[1], pixel[2]) txt += '\n' colors.append((255, 255, 255)) dr = ImageDraw.Draw(im_txt) font = ImageFont.load_default().font x = y = 0 font_w, font_h = font.getsize(txt[1]) font_h *= 1.37 for i in range(len(txt)): if txt[i] == '\n': x += font_h y = -font_w dr.text((y, x), txt[i], colors[i]) y += font_w return im_txt def get_image_size(video_path): frame_count, frame_width, frame_height = 0, 0, 0 vc = cv2.VideoCapture(video_path) ret = vc.isOpened() while ret: ret, frame = vc.read() if ret: frame_count = int(vc.get(cv2.CAP_PROP_FRAME_COUNT)) frame_height = vc.get(cv2.CAP_PROP_FRAME_HEIGHT) frame_width = vc.get(cv2.CAP_PROP_FRAME_WIDTH) break vc.release() time.sleep(1) return frame_count, frame_width, frame_height def video2char_video(video_path, output_dir): frame_count, frame_width, frame_height = get_image_size(video_path) fourcc = cv2.VideoWriter_fourcc('D', 'I', 'V', 'X') opencv_video_path = f"{output_dir}/output.mp4" audio_path = f"{output_dir}/output.mp3" new_video_path = f"{output_dir}/new.mp4" video_writer = cv2.VideoWriter(opencv_video_path, fourcc, 30.0, (int(frame_width), int(frame_height))) vc = cv2.VideoCapture(video_path) c = 0 ret = vc.isOpened() while ret: c = c + 1 ret, frame = vc.read() if ret: im_txt = frame2char(frame) img = cv2.cvtColor(numpy.array(im_txt), cv2.COLOR_RGB2BGR) video_writer.write(img) schedule = c / float(frame_count) if frame_count != 0 else 1.00 progress_label["text"] = f"转换进度:{schedule:.2%}" progressbar['value'] = schedule * 100 root.update() else: break vc.release() video_writer.release() subprocess.call(['ffmpeg', '-i', video_path, '-vn', audio_path]) time.sleep(2) subprocess.call(['ffmpeg', '-i', opencv_video_path, '-i', audio_path, "-c:v", "copy", "-c:a", "aac", "-strict", "experimental", new_video_path]) label["text"] = f"转换完成,转换后的视频路径是{new_video_path}" choose_file_button['state'] = NORMAL convert_button['state'] = NORMAL progress_label.pack_forget() progressbar.pack_forget() def check_or_create_dir(dir_path): if os.path.exists(dir_path): files = os.listdir(dir_path) if files: return False else: return True else: os.makedirs(dir_path, exist_ok=True) return True def choose_video_file_path(): global video_path video_path = filedialog.askopenfilename() def convert_video(): if not video_path: messagebox.showerror('错误', '没有选择有效的路径,请重新选择') else: if os.path.isfile(video_path): kind = filetype.guess(video_path) if kind: mime = kind.mime if mime: if mime.startswith("video"): video_dir = os.path.dirname(os.path.abspath(video_path)) output_dir = f"{video_dir}/output" flag = check_or_create_dir(output_dir) if not flag: messagebox.showerror('错误', f'{output_dir} 不是空的,请删除或者清空此目录') else: choose_file_button['state'] = DISABLED convert_button['state'] = DISABLED label["text"] = "视频正在转换,请稍等!!!" progress_label.pack(side=LEFT) progressbar.pack(side=RIGHT) t = threading.Thread(target=video2char_video, args=(video_path, output_dir,)) t.setDaemon(True) t.start() else: messagebox.showerror('错误', f'{video_path} 不是视频,请重新选择') else: messagebox.showerror('错误', f'{video_path} 不是视频,请重新选择') else: messagebox.showerror('错误', f'{video_path} 不是视频,请重新选择') else: messagebox.showerror('错误', '没有选择有效的路径,请重新选择') if __name__ == "__main__": root = tkinter.Tk() video_path = None label = tkinter.Label(root, text="欢迎使用海军专属的字符视频转换器") label.pack(side=TOP) progress_label = tkinter.Label(root, text="转换进度:0.0%") progress_label.pack_forget() progressbar = Progressbar(root) progressbar["value"] = 0 progressbar["maximum"] = 100 progressbar.pack_forget() choose_file_button = tkinter.Button(root, text="选择文件", command=choose_video_file_path) choose_file_button.pack(side=LEFT) convert_button = tkinter.Button(root, text="开始转换", command=convert_video) convert_button.pack(side=RIGHT) screen_width, screen_height = root.maxsize() # 获取屏幕最大长宽 w = int((screen_width - 240) / 2) h = int((screen_height - 480) / 2) root.geometry(f'+{w}+{h}') root.resizable(width=False, height=False) root.title('字符视频转换器') root.mainloop()