字符视频转换器(带声音版,需要确认电脑是否安装ffmpeg,并配置了环境变量)

发布时间 2023-07-22 23:57:12作者: NAVYSUMMER
# -*- 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()