# -*- coding: utf-8 -*-
import os
import threading
import tkinter
from tkinter import LEFT, RIGHT, filedialog, messagebox, DISABLED, NORMAL, TOP
import cv2
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 single_picture2char_img(origin_img_path, out_img_path):
origin_img_width = 0
origin_img_height = 0
if os.path.exists(origin_img_path):
im = Image.open(origin_img_path)
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
im_txt.save(out_img_path)
return origin_img_width, origin_img_height
def picture2char_img(img_dir, char_img_dir):
origin_img_width = 0
origin_img_height = 0
files = os.listdir(img_dir)
for file in files:
file_path = os.path.join(img_dir, file)
if os.path.isfile(file_path):
out_img_path = f"{char_img_dir}/{file}"
origin_img_width, origin_img_height = single_picture2char_img(file_path, out_img_path)
return origin_img_width, origin_img_height
def char_img2video(char_img_dir, video_dir, origin_img_width, origin_img_height):
fourcc = cv2.VideoWriter_fourcc('D', 'I', 'V', 'X')
video_writer = cv2.VideoWriter(f"{video_dir}/output.mp4", fourcc, 30.0, (origin_img_width, origin_img_height))
files = sorted(os.listdir(char_img_dir), key=lambda x: int(x[:-4]))
for file in files:
filename = os.path.join(char_img_dir, file)
if os.path.exists(filename):
img = cv2.imread(filename=filename)
cv2.waitKey(100)
video_writer.write(img)
video_writer.release()
def video2picture(video_path, img_dir):
vc = cv2.VideoCapture(video_path)
c = 0
ret = vc.isOpened()
while ret:
c = c + 1
ret, frame = vc.read()
if ret:
img_path = f"{img_dir}/{c}.jpg"
cv2.imwrite(img_path, frame)
cv2.waitKey(1)
else:
break
# 视频释放
vc.release()
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 video2char_video(video_path):
video_dir = os.path.dirname(os.path.abspath(video_path))
img_dir = f"{video_dir}/img"
char_img_dir = f"{video_dir}/char_img"
output_dir = f'{video_dir}/output'
img_dir_flag = check_or_create_dir(img_dir)
char_img_dir_flag = check_or_create_dir(char_img_dir)
output_dir_flag = check_or_create_dir(output_dir)
if not img_dir_flag:
messagebox.showerror('错误', f'{img_dir} 不是空的,请删除或者清空此目录')
elif not char_img_dir_flag:
messagebox.showerror('错误', f'{char_img_dir} 不是空的,请删除或者清空此目录')
elif not output_dir_flag:
messagebox.showerror('错误', f'{output_dir} 不是空的,请删除或者清空此目录')
if img_dir_flag and char_img_dir_flag and output_dir_flag:
video2picture(video_path, img_dir)
origin_img_width, origin_img_height = picture2char_img(img_dir, char_img_dir)
char_img2video(char_img_dir, output_dir, origin_img_width, origin_img_height)
label["text"] = "转换完成"
choose_file_button['state'] = NORMAL
convert_button['state'] = NORMAL
def choose_video_file_path():
global filepath
filepath = filedialog.askopenfilename()
def get_video_file_path():
if not filepath:
messagebox.showerror('错误', '没有选择有效的路径,请重新选择')
else:
if os.path.isfile(filepath):
choose_file_button['state'] = DISABLED
convert_button['state'] = DISABLED
label["text"] = "视频正在转换,请稍等!!!"
t = threading.Thread(target=video2char_video, args=(filepath,))
t.setDaemon(True)
t.start()
else:
messagebox.showerror('错误', '没有选择有效的路径,请重新选择')
if __name__ == '__main__':
root = tkinter.Tk()
filepath = None
label = tkinter.Label(root, text="欢迎使用海军专属的字符视频转换器")
label.pack(side=TOP)
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=get_video_file_path)
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()