鱼C论坛

 找回密码
 立即注册
查看: 47|回复: 2

[作品展示] 视频转字符画

[复制链接]
发表于 16 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
  1. import cv2
  2. import tkinter as tk
  3. import os.path as op
  4. from tkinter import messagebox as mgb
  5. from tkinter import ttk
  6. from tkinter import filedialog as fdl

  7. def video2strs(path_, root):

  8.     if not op.isfile(path_) or not path_.split(".")[1] == "mp4":
  9.         mgb.showwarning(title="Warning", messge="目前仅支持MP4格式的转换!!")
  10.         return

  11.     dir_name, file_name = op.split(path_)
  12.     new_text_position = dir_name + r"/"+ file_name.split(".")[0]

  13.     #os.chdir(dir_name)
  14.     _ = 0
  15.     suffix = ".v2s"
  16.     while op.isfile(new_text_position + suffix):
  17.         _ += 1
  18.         suffix = f"({_}).v2s"
  19.     new_text_position += suffix
  20.     print(new_text_position)

  21.     def get_resize_height():
  22.         _ = cv2.VideoCapture(path_)
  23.         ret, temp_frame = _.read()
  24.         temp_frame = cv2.resize(temp_frame, (0, 0), fx=0.09375,fy=0.046875)
  25.         __ = temp_frame.shape[0]
  26.         _.release()
  27.         return __

  28.     def start_trans():

  29.         text_f = open(new_text_position, "w", encoding="utf-8")
  30.         text_f.write(str(get_resize_height()) + "\n")
  31.         #with open(new_text_position, "w", encoding="utf-8") as text_f:

  32.         def trans():
  33.             nonlocal count
  34.             if video.isOpened():
  35.                 temp_text = ""
  36.                 ret, current_frame = video.read()
  37.                 if ret:
  38.                     current_frame = cv2.resize(cv2.cvtColor(current_frame, cv2.COLOR_BGR2GRAY), (0, 0), fx=0.09375,fy=0.046875)
  39.                     for i in range(current_frame.shape[0]):
  40.                         for j in range(current_frame.shape[1]):
  41.                             temp_text += ascii__[int(current_frame[i, j] / 255 * 7)]
  42.                         temp_text += '\n'
  43.                     else:
  44.                         text_f.write(temp_text)
  45.                         if count > one_of_ten:
  46.                             progress_bar["value"] += 10
  47.                             progress_bar.pack(pady=(1, 2))
  48.                             count = 0
  49.                         show_progress.after(0, trans)
  50.                         count += 1
  51.                         #print("+1f")
  52.                 else:
  53.                     label1.config(text="OK!")
  54.                     progress_bar["value"] = 100
  55.                     progress_bar.pack(pady=(1, 2))
  56.                     text_f.close()
  57.                     tk.Button(show_progress, text="Quit", command=show_progress.destroy, width=5).pack(pady=15)
  58.                     video.release()

  59.         video = cv2.VideoCapture(path_)
  60.         amount_of_frame = video.get(cv2.CAP_PROP_FRAME_COUNT)
  61.         one_of_ten = amount_of_frame // 10
  62.         temp_text = ""
  63.         ascii__ = ["@", "G·", "¥", "·*·", "·+", "·=", "-'-", "·_·"]
  64.         count = 0
  65.         show_progress.after(0, trans)

  66.     show_progress = tk.Tk()
  67.     show_progress.geometry("300x180")

  68.     label1 = tk.Label(show_progress, text="Patience!", justify="center")
  69.     progress_bar = ttk.Progressbar(show_progress, length=200, mode="determinate")
  70.     label1.pack(pady=(30, 1))
  71.     progress_bar.pack(pady=(1, 2))
  72.     show_progress.after(0, start_trans) # after方法 第二个参数之后是收集参数

  73. class V2sTrans:
  74.     def __init__(self, root):
  75.         self.root = root
  76.         self.root.geometry(f"500x400+{self.root.winfo_screenwidth()//2-250}+{self.root.winfo_screenheight()//2-200}")
  77.         self.init_win()

  78.     def init_win(self):

  79.         def set_dir():

  80.             path_ = fdl.askopenfilename(title="选择视频文件", filetypes=[("MP4", '.mp4')])

  81.             dir_entry.delete(0, "end")
  82.             dir_entry.insert(0,path_)

  83.         def set_dir_play():
  84.             path_ = fdl.askopenfilename(title="选择v2s文件", filetypes=[("V2S", '.v2s')])

  85.             dir_entry_.delete(0, "end")
  86.             dir_entry_.insert(0, path_)


  87.         l_frame0 = tk.LabelFrame(master=self.root, text="V2S", height=30)
  88.         l_frame0.pack(fill='x', padx=20, pady=10, ipady=50)

  89.         l_frame1 = tk.LabelFrame(master=l_frame0, text="输入mp4文件路径")
  90.         l_frame1.pack(fill="x", padx=20, pady=(50, 0))

  91.         dir_entry = tk.Entry(master=l_frame1, width=40)
  92.         dir_choice_button = tk.Button(master=l_frame1, text="...", relief="raised", command=set_dir, width=4)
  93.         dir_entry.grid(row=0, column=0, padx=(45, 5), pady=(5,10))
  94.         dir_choice_button.grid(row=0, column=1, padx=(5, 10), pady=(5,10))

  95.         start_trans_button = tk.Button(master=l_frame0, text="转换", width=20,
  96.                                        command=lambda: video2strs(dir_entry.get(), self.root))
  97.         start_trans_button.pack(pady=10)

  98.         l_frame2 = tk.LabelFrame(master=l_frame0, text="输入v2s文件路径")
  99.         l_frame2.pack(fill="x", padx=20, pady=(20, 0))

  100.         dir_entry_ = tk.Entry(master=l_frame2, width=40)
  101.         dir_choice_button_ = tk.Button(master=l_frame2, text="...", relief="raised", command=set_dir_play, width=4)
  102.         dir_entry_.grid(row=0, column=0, padx=(45, 5), pady=(5,10))
  103.         dir_choice_button_.grid(row=0, column=1, padx=(5, 10), pady=(5,10))

  104.         start_trans_button = tk.Button(master=l_frame0, text="播放", width=20,
  105.                                        command=lambda: play_v2s(dir_entry_.get()))

  106.         start_trans_button.pack(pady=10)

  107. def play_v2s(path_):
  108.     with open(path_, "r", encoding="utf-8") as f:
  109.         v2s = f.readlines()
  110.         height = int(v2s[0].split("\")[0])
  111.         print(height)
  112.         v2s.pop(0)

  113.     frames = []

  114.     count = 0
  115.     temp_str = ""
  116.     for i in v2s:
  117.         if count == height:
  118.             count = 0
  119.             frames.append(temp_str)
  120.             temp_str = ""
  121.         temp_str += i
  122.         count += 1

  123.     frames_iter = iter(frames)

  124.     def update_frame():
  125.         label.config(text=next(frames_iter))
  126.         label.pack()
  127.         show_v2s.after(27, update_frame)


  128.     show_v2s = tk.Tk()
  129.     show_v2s.title("Playing")
  130.     #show_v2s.geometry(f"800x600+{show_v2s.winfo_screenwidth()//2-400}+{show_v2s.winfo_screenheight()//2-300}")
  131.     label = tk.Label(show_v2s, text="Ready", justify="center", width=1024, height=720, font=("微软雅黑", 6))
  132.     label.pack()
  133.     show_v2s.after(3000, update_frame)
  134.     show_v2s.mainloop()

  135. if __name__ == '__main__':
  136.     root_ = tk.Tk()
  137.     root__ = V2sTrans(root_)
  138.     root__.root.mainloop()
复制代码

评分

参与人数 1荣誉 +2 鱼币 +3 贡献 +3 C币 +3 收起 理由
不二如是 + 2 + 3 + 3 + 3 请不要无意义灌水!

查看全部评分

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 16 小时前 | 显示全部楼层
首先,我来帮你修正这段视频转字符画的代码。主要问题包括语法错误、逻辑错误和未定义的变量。以下是修正后的代码,并附上关键说明。

关键修正点:

1. 文件扩展名检查:使用 path_.endswith('.mp4') 替代错误的 split 比较。
2. 文件路径拼接:正确获取文件名(不带扩展名)来生成输出文件路径。
3. 字符映射:定义了 ascii_chars 字符串,用于将灰度值映射到字符。
4. 帧处理:在转换每个帧时,正确遍历像素并映射到字符。
5. 进度条更新:使用 progress_bar['value'] 来更新进度。
6. v2s 文件解析:在播放部分,正确读取和分割帧数据。

修正后的代码:


  1. import cv2
  2. import tkinter as tk
  3. import os.path as op
  4. from tkinter import messagebox as mgb
  5. from tkinter import ttk
  6. from tkinter import filedialog as fdl

  7. def video2strs(path_, root):
  8.     if not op.isfile(path_) or not path_.endswith('.mp4'):
  9.         mgb.showwarning(title="Warning", message="目前仅支持MP4格式的转换!!")
  10.         return

  11.     dir_name, file_name = op.split(path_)
  12.     file_base_name = op.splitext(file_name)[0]  # 获取不带扩展名的文件名
  13.     new_text_position = op.join(dir_name, file_base_name)  # 使用 os.path.join 确保路径正确

  14.     _ = 0
  15.     suffix = ".v2s"
  16.     while op.isfile(new_text_position + suffix):
  17.         _ += 1
  18.         suffix = f"({_}).v2s"
  19.     new_text_position += suffix
  20.     print("输出文件路径:", new_text_position)

  21.     def get_resize_height():
  22.         cap = cv2.VideoCapture(path_)
  23.         ret, temp_frame = cap.read()
  24.         if ret:
  25.             # 调整帧大小,这里设置为宽度120,高度按比例计算
  26.             height, width = temp_frame.shape[:2]
  27.             new_width = 120
  28.             new_height = int(height * (new_width / width) * 0.5)  # 高度减半以匹配字符画比例
  29.             temp_frame = cv2.resize(temp_frame, (new_width, new_height))
  30.             cap.release()
  31.             return (new_height, new_width)  # 返回调整后的高度和宽度
  32.         cap.release()
  33.         return (0, 0)

  34.     def start_trans():
  35.         # 定义字符映射,用于将灰度值转换为字符
  36.         ascii_chars = "@%#*+=-:. "

  37.         text_f = open(new_text_position, "w", encoding="utf-8")
  38.         resize_dim = get_resize_height()
  39.         text_f.write(f"{resize_dim[0]},{resize_dim[1]}\n")  # 写入高度和宽度,用逗号分隔

  40.         video = cv2.VideoCapture(path_)
  41.         amount_of_frame = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
  42.         one_of_ten = max(amount_of_frame // 10, 1)  # 避免除零
  43.         count = 0

  44.         def trans():
  45.             nonlocal count
  46.             if video.isOpened():
  47.                 ret, current_frame = video.read()
  48.                 if ret:
  49.                     # 调整帧大小并转换为灰度图
  50.                     current_frame = cv2.cvtColor(current_frame, cv2.COLOR_BGR2GRAY)
  51.                     current_frame = cv2.resize(current_frame, (resize_dim[1], resize_dim[0]))
  52.                     temp_text = ""
  53.                     for i in range(resize_dim[0]):  # 高度
  54.                         for j in range(resize_dim[1]):  # 宽度
  55.                             gray = current_frame[i, j]
  56.                             # 将灰度值映射到字符索引
  57.                             index = int(gray / 255 * (len(ascii_chars) - 1))
  58.                             temp_text += ascii_chars[index]
  59.                         temp_text += '\n'
  60.                     text_f.write(temp_text)
  61.                     count += 1
  62.                     # 更新进度条
  63.                     if count % one_of_ten == 0:
  64.                         progress_bar['value'] += 10
  65.                         progress_bar.update()
  66.                     show_progress.after(1, trans)  # 使用 after 继续处理下一帧
  67.                 else:
  68.                     # 处理完成
  69.                     label1.config(text="OK!")
  70.                     progress_bar['value'] = 100
  71.                     progress_bar.update()
  72.                     text_f.close()
  73.                     video.release()
  74.                     tk.Button(show_progress, text="Quit", command=show_progress.destroy, width=5).pack(pady=15)
  75.             else:
  76.                 mgb.showerror("Error", "无法打开视频文件")

  77.         show_progress.after(0, trans)

  78.     # 创建进度窗口
  79.     show_progress = tk.Toplevel(root)  # 使用 Toplevel 而非 Tk,避免多个主窗口
  80.     show_progress.geometry("300x180")
  81.     show_progress.title("转换进度")

  82.     label1 = tk.Label(show_progress, text="Patience!", justify="center")
  83.     progress_bar = ttk.Progressbar(show_progress, length=200, mode="determinate", maximum=100)
  84.     label1.pack(pady=(30, 1))
  85.     progress_bar.pack(pady=(1, 2))
  86.     show_progress.after(0, start_trans)

  87. class V2sTrans:
  88.     def __init__(self, root):
  89.         self.root = root
  90.         self.root.geometry(f"500x400+{self.root.winfo_screenwidth()//2-250}+{self.root.winfo_screenheight()//2-200}")
  91.         self.root.title("视频转字符画")
  92.         self.init_win()

  93.     def init_win(self):
  94.         def set_dir():
  95.             path_ = fdl.askopenfilename(title="选择视频文件", filetypes=[("MP4 files", "*.mp4")])
  96.             if path_:
  97.                 dir_entry.delete(0, "end")
  98.                 dir_entry.insert(0, path_)

  99.         def set_dir_play():
  100.             path_ = fdl.askopenfilename(title="选择v2s文件", filetypes=[("V2S files", "*.v2s")])
  101.             if path_:
  102.                 dir_entry_.delete(0, "end")
  103.                 dir_entry_.insert(0, path_)

  104.         l_frame0 = tk.LabelFrame(master=self.root, text="V2S", height=30)
  105.         l_frame0.pack(fill='x', padx=20, pady=10, ipady=50)

  106.         l_frame1 = tk.LabelFrame(master=l_frame0, text="输入mp4文件路径")
  107.         l_frame1.pack(fill="x", padx=20, pady=(50, 0))

  108.         dir_entry = tk.Entry(master=l_frame1, width=40)
  109.         dir_choice_button = tk.Button(master=l_frame1, text="...", relief="raised", command=set_dir, width=4)
  110.         dir_entry.grid(row=0, column=0, padx=(45, 5), pady=(5,10))
  111.         dir_choice_button.grid(row=0, column=1, padx=(5, 10), pady=(5,10))

  112.         start_trans_button = tk.Button(master=l_frame0, text="转换", width=20,
  113.                                        command=lambda: video2strs(dir_entry.get(), self.root))
  114.         start_trans_button.pack(pady=10)

  115.         l_frame2 = tk.LabelFrame(master=l_frame0, text="输入v2s文件路径")
  116.         l_frame2.pack(fill="x", padx=20, pady=(20, 0))

  117.         dir_entry_ = tk.Entry(master=l_frame2, width=40)
  118.         dir_choice_button_ = tk.Button(master=l_frame2, text="...", relief="raised", command=set_dir_play, width=4)
  119.         dir_entry_.grid(row=0, column=0, padx=(45, 5), pady=(5,10))
  120.         dir_choice_button_.grid(row=0, column=1, padx=(5, 10), pady=(5,10))

  121.         start_play_button = tk.Button(master=l_frame0, text="播放", width=20,
  122.                                       command=lambda: play_v2s(dir_entry_.get()))
  123.         start_play_button.pack(pady=10)

  124. def play_v2s(path_):
  125.     if not op.isfile(path_) or not path_.endswith('.v2s'):
  126.         mgb.showwarning(title="Warning", message="请选择有效的 .v2s 文件!")
  127.         return

  128.     with open(path_, "r", encoding="utf-8") as f:
  129.         lines = f.readlines()
  130.         if not lines:
  131.             mgb.showerror("Error", "文件为空或格式错误")
  132.             return
  133.         # 第一行是高度和宽度
  134.         first_line = lines[0].strip()
  135.         try:
  136.             height, width = map(int, first_line.split(','))
  137.         except:
  138.             mgb.showerror("Error", "文件格式错误:第一行应为高度,宽度")
  139.             return
  140.         # 移除第一行
  141.         char_lines = lines[1:]

  142.     # 将字符数据分割成帧
  143.     frames = []
  144.     temp_str = ""
  145.     count = 0
  146.     for line in char_lines:
  147.         if count < height:
  148.             temp_str += line
  149.             count += 1
  150.         if count == height:
  151.             frames.append(temp_str)
  152.             temp_str = ""
  153.             count = 0
  154.     # 检查是否有剩余数据
  155.     if temp_str:
  156.         frames.append(temp_str)

  157.     if not frames:
  158.         mgb.showerror("Error", "没有有效的帧数据")
  159.         return

  160.     frames_iter = iter(frames)
  161.     show_v2s = tk.Tk()
  162.     show_v2s.title("Playing")
  163.     # 设置窗口大小,根据字符画尺寸调整
  164.     label = tk.Label(show_v2s, text="Ready", justify="left", font=("Courier", 10))  # 使用等宽字体
  165.     label.pack()

  166.     def update_frame():
  167.         try:
  168.             frame_text = next(frames_iter)
  169.             label.config(text=frame_text)
  170.             show_v2s.after(27, update_frame)  # 约37帧每秒,但字符画可能较慢,可调整
  171.         except StopIteration:
  172.             label.config(text="播放完毕")
  173.             tk.Button(show_v2s, text="关闭", command=show_v2s.destroy).pack()

  174.     show_v2s.after(100, update_frame)  # 延迟开始播放
  175.     show_v2s.mainloop()

  176. if __name__ == '__main__':
  177.     root_ = tk.Tk()
  178.     app = V2sTrans(root_)
  179.     root_.mainloop()
复制代码


注意事项:

1. 字符映射:使用了 ascii_chars = "@%#*+=-:. " 作为灰度到字符的映射,你可以根据需要调整字符顺序或数量。
2. 帧大小调整:在 get_resize_height 中,将帧宽度固定为120,高度按比例计算并减半,以适应字符画的比例(字符通常高度大于宽度)。
3. 进度条更新:使用 progress_bar['value'] += 10 来逐步更新进度。
4. 播放部分:使用 font=("Courier", 10) 等宽字体,以确保字符对齐。
5. 错误处理:添加了基本的错误检查,如文件是否存在、格式是否正确。

运行前请确保已安装 OpenCV:可以通过 pip install opencv-python 安装。如果遇到问题,请检查视频文件路径和输出目录的写入权限。


以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 小时前 | 显示全部楼层
加一些效果演示的图片或动图会更好
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2026-4-5 18:49

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表