# -*- 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):
vc = cv2.VideoCapture(video_path)
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)
fps = vc.get(cv2.CAP_PROP_FPS)
vc.release()
time.sleep(1)
return frame_count, frame_width, frame_height, fps
def video2char_video(video_path, output_dir):
frame_count, frame_width, frame_height, fps = get_image_size(video_path)
fourcc = cv2.VideoWriter_fourcc('D', 'I', 'V', 'X')
opencv_video_path = os.path.join(output_dir, "output.mp4")
audio_path = os.path.join(output_dir, "output.mp3")
new_video_path = os.path.join(output_dir, "new.mp4")
video_writer = cv2.VideoWriter(opencv_video_path, fourcc, fps,
(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 = os.path.join(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()