视频转 WEBM
python 代码
import os
import subprocess
from concurrent.futures import ThreadPoolExecutor
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from threading import Thread
import queue
class VideoConverterApp:
def __init__(self, root):
self.root = root
self.root.title("视频转换器")
self.root.geometry("400x200")
# 设置暗黑主题(macOS 需要额外支持)
self.root.tk_setPalette(background='#2d2d2d', foreground='#ffffff')
self.label = tk.Label(root, text="选择视频文件进行转换", bg='#2d2d2d', fg='#ffffff')
self.label.pack(pady=10)
self.select_button = tk.Button(root, text="选择文件", command=self.select_files, bg='#3c3c3c', fg='#ffffff')
self.select_button.pack(pady=10)
self.progress = ttk.Progressbar(root, orient="horizontal", length=300, mode="determinate")
self.progress.pack(pady=20)
self.convert_button = tk.Button(root, text="开始转换", command=self.start_conversion, state=tk.DISABLED, bg='#3c3c3c', fg='#ffffff')
self.convert_button.pack(pady=10)
self.video_files = []
self.progress_queue = queue.Queue()
def select_files(self):
self.video_files = filedialog.askopenfilenames(
title="选择视频文件",
filetypes=[("视频文件", "*.mp4 *.mov *.avi *.mkv *.flv *.m4v")]
)
if self.video_files:
self.convert_button.config(state=tk.NORMAL)
self.label.config(text=f"已选择 {len(self.video_files)} 个文件")
def start_conversion(self):
self.progress["value"] = 0
self.progress["maximum"] = len(self.video_files)
self.convert_button.config(state=tk.DISABLED)
self.select_button.config(state=tk.DISABLED)
# 启动后台线程处理转换任务
self.conversion_thread = Thread(target=self.run_conversion, daemon=True)
self.conversion_thread.start()
# 启动进度更新循环
self.update_progress()
def run_conversion(self):
with ThreadPoolExecutor(max_workers=min(6, os.cpu_count() - 2 or 4)) as executor:
for i, _ in enumerate(executor.map(self.convert_to_webm, self.video_files)):
self.progress_queue.put(i + 1) # 将进度信息放入队列
# 转换完成后,发送完成信号
self.progress_queue.put("done")
def convert_to_webm(self, input_path):
output_path = os.path.splitext(input_path)[0] + '.webm'
if os.path.exists(output_path):
print(f"Skipped existing: {output_path}")
return
ffmpeg_cmd = [
'ffmpeg',
'-y', '-i', input_path,
'-c:v', 'libvpx-vp9',
'-crf', '15', # 质量调节核心参数
'-b:v', '0',
'-row-mt', '1', # 启用行级多线程
'-cpu-used', '2', # 更高质量预设(相比默认值3)
'-threads', '8', # 针对M1 Max调整线程数
'-slices', '8', # 并行处理单元
'-tile-columns', '4', # 提升并行度
'-frame-parallel', '1', # 帧级并行
'-c:a', 'libopus',
'-b:a', '192k',
'-vbr', 'on',
'-vf', 'scale=iw:ih:flags=lanczos',
'-progress', '-', # 启用进度输出
'-color_primaries', 'bt709',
'-colorspace', 'bt709',
'-color_trc', 'bt709',
'-auto-alt-ref', '0',
output_path
]
try:
process = subprocess.Popen(
ffmpeg_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True
)
# 实时进度解析
while True:
line = process.stdout.readline()
if not line:
break
if 'out_time=' in line:
time = line.split('=')[1].strip()
print(f"Processing {input_path}: {time}", end='\r')
process.wait()
if process.returncode == 0:
print(f"\n✅ Completed: {output_path}")
else:
print(f"\n❌ Failed: {input_path}")
except Exception as e:
print(f"\n⚠️ Error processing {input_path}: {str(e)}")
def update_progress(self):
try:
while True:
# 从队列中获取进度信息
value = self.progress_queue.get_nowait()
if value == "done":
messagebox.showinfo("完成", "所有转换已完成!")
self.convert_button.config(state=tk.NORMAL)
self.select_button.config(state=tk.NORMAL)
self.label.config(text="选择视频文件进行转换")
break
else:
self.progress["value"] = value
self.root.update_idletasks()
except queue.Empty:
# 如果队列为空,继续等待
self.root.after(100, self.update_progress)
if name == "__main__":
root = tk.Tk()
app = VideoConverterApp(root)
root.mainloop()
附件中的可执行文件只能用在 macos