视频处理器 v1.2 是一款由是貔貅呀开发的视频编辑和处理工具,提供高效便捷的视频批量横转竖,主要功能:
导入与删除文件:轻松导入多个视频文件,删除不必要的文件。
暂停与继续处理:随时暂停和继续处理。
设置输出文件夹:选择视频处理后的存放文件夹。
FFmpeg 配置:自定义输出格式、视频编码器、音频编码器和线程数。
日志记录:实时记录处理日志。
使用步骤
导入文件
设置输出文件夹
配置FFmpeg
开始处理文件
查看处理进度
暂停与继续
特点
用户友好界面
多格式支持
高效多线程处理
实时日志显示
运行截图


文件代码如下
import os
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
from tkinter import filedialog, messagebox, END, Text, StringVar, IntVar
from concurrent.futures import ThreadPoolExecutor, as_completed
import subprocess
import threading
import psutil
import re
import sys
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
class VideoProcessor:
def __init__(self, master):
self.master = master
self.master.title("视频处理器")
self.input_files = []
self.output_folder = ""
self.process_thread = None
self.pause_event = threading.Event()
self.pause_event.set() # Start in the unpaused state
self.ffmpeg_processes = [] # List to keep track of all ffmpeg processes
self.is_closing = False
self.output_format = StringVar(value="mp4")
self.video_codec = StringVar(value="libx264")
self.audio_codec = StringVar(value="aac")
self.thread_count = IntVar(value=4) # Default to 4 threads
self.create_widgets()
self.master.protocol("WM_DELETE_WINDOW", self.on_closing)
def create_widgets(self):
frame = ttk.Frame(self.master, padding=10)
frame.pack(fill=BOTH, expand=YES)
self.file_list_frame = ttk.Frame(frame)
self.file_list_frame.pack(fill=BOTH, expand=YES, pady=5)
columns = ('序号', '文件夹名字', '进度')
self.file_tree = ttk.Treeview(self.file_list_frame, columns=columns, show='headings')
self.file_tree.heading('序号', text='序号')
self.file_tree.heading('文件夹名字', text='文件夹名字')
self.file_tree.heading('进度', text='进度')
self.file_tree.column('序号', width=100, anchor='center')
self.file_tree.column('文件夹名字', width=400, anchor='w')
self.file_tree.column('进度', width=100, anchor='center')
self.file_tree.pack(side=LEFT, fill=BOTH, expand=YES)
scrollbar = ttk.Scrollbar(self.file_list_frame, orient="vertical", command=self.file_tree.yview)
self.file_tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=RIGHT, fill=Y)
self.log_text = Text(frame, height=5, state='disabled')
self.log_text.pack(fill=BOTH, expand=YES, pady=5)
button_frame = ttk.Frame(frame)
button_frame.pack(fill=BOTH, expand=YES)
self.add_files_button = ttk.Button(button_frame, text="导入文件", command=self.add_files, bootstyle=PRIMARY)
self.add_files_button.pack(side=LEFT, padx=5, pady=5)
self.remove_files_button = ttk.Button(button_frame, text="删除文件", command=self.remove_files, bootstyle=DANGER)
self.remove_files_button.pack(side=LEFT, padx=5, pady=5)
self.pause_button = ttk.Button(button_frame, text="暂停/继续", command=self.toggle_pause, bootstyle=WARNING)
self.pause_button.pack(side=LEFT, padx=5, pady=5)
self.open_output_folder_button = ttk.Button(button_frame, text="打开文件", command=self.open_output_folder, bootstyle=SUCCESS)
self.open_output_folder_button.pack(side=LEFT, padx=5, pady=5)
self.set_output_folder_button = ttk.Button(button_frame, text="导出文件", command=self.set_output_folder, bootstyle=SUCCESS)
self.set_output_folder_button.pack(side=LEFT, padx=5, pady=5)
self.process_button = ttk.Button(button_frame, text="开始处理文件", command=self.start_processing, bootstyle=INFO)
self.process_button.pack(side=RIGHT, padx=5, pady=5)
config_frame = ttk.LabelFrame(frame, text="FFmpeg 配置")
config_frame.pack(fill=BOTH, expand=YES, pady=5)
ttk.Label(config_frame, text="输出格式:").pack(side=LEFT, padx=5, pady=5)
ttk.OptionMenu(config_frame, self.output_format, "mp4", "mp4", "avi", "mov").pack(side=LEFT, padx=5, pady=5)
ttk.Label(config_frame, text="视频编码器:").pack(side=LEFT, padx=5, pady=5)
ttk.OptionMenu(config_frame, self.video_codec, "libx264", "libx264", "libx265", "mpeg4").pack(side=LEFT, padx=5, pady=5)
ttk.Label(config_frame, text="音频编码器:").pack(side=LEFT, padx=5, pady=5)
ttk.OptionMenu(config_frame, self.audio_codec, "aac", "aac", "mp3", "ac3").pack(side=LEFT, padx=5, pady=5)
ttk.Label(config_frame, text="线程数:").pack(side=LEFT, padx=5, pady=5)
ttk.Entry(config_frame, textvariable=self.thread_count).pack(side=LEFT, padx=5, pady=5)
def add_files(self):
files = filedialog.askopenfilenames(title="选择视频文件", filetypes=[("视频文件", "*.mp4 *.avi *.mov")])
for file in files:
if file not in self.input_files:
self.input_files.append(file)
self.file_tree.insert('', END, values=(len(self.input_files), os.path.basename(file), "未处理"))
self.log(f"导入文件: {file}")
else:
messagebox.showwarning("警告", f"文件已存在: {os.path.basename(file)}")
def remove_files(self):
selected_items = self.file_tree.selection()
for item in selected_items:
index = int(self.file_tree.item(item, 'values')[0]) - 1
del self.input_files[index]
self.file_tree.delete(item)
self.log("删除选中文件")
self.refresh_file_list()
def refresh_file_list(self):
for item in self.file_tree.get_children():
self.file_tree.delete(item)
for index, file in enumerate(self.input_files):
self.file_tree.insert('', END, values=(index + 1, os.path.basename(file), "未处理"))
def set_output_folder(self):
self.output_folder = filedialog.askdirectory(title="选择输出文件夹")
self.log(f"设置输出文件夹: {self.output_folder}")
def start_processing(self):
if not self.input_files or not self.output_folder:
messagebox.showerror("错误", "请添加文件并设置输出文件夹。")
return
self.process_thread = threading.Thread(target=self.process_videos_concurrently)
self.process_thread.start()
def toggle_pause(self):
if self.pause_event.is_set():
self.pause_event.clear()
self.log("处理暂停")
for process in self.ffmpeg_processes:
proc = psutil.Process(process.pid)
proc.suspend()
else:
self.pause_event.set()
self.log("处理继续")
for process in self.ffmpeg_processes:
proc = psutil.Process(process.pid)
proc.resume()
def open_output_folder(self):
if self.output_folder:
os.startfile(self.output_folder)
self.log(f"打开输出文件夹: {self.output_folder}")
else:
messagebox.showerror("错误", "请先设置输出文件夹。")
def log(self, message):
if not self.is_closing:
self.master.after(0, self._log, message)
def _log(self, message):
if not self.is_closing:
self.log_text.configure(state='normal')
self.log_text.insert(END, message + '\n')
self.log_text.configure(state='disabled')
self.log_text.yview(END)
def update_tree_status(self, index, status):
if not self.is_closing:
self.master.after(0, self._update_tree_status, index, status)
def _update_tree_status(self, index, status):
if not self.is_closing:
self.file_tree.item(self.file_tree.get_children()[index], values=(index + 1, os.path.basename(self.input_files[index]), status))
def process_videos_concurrently(self):
max_workers = self.thread_count.get()
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(self.process_video, index, input_file) for index, input_file in enumerate(self.input_files)]
for future in as_completed(futures):
future.result()
def process_video(self, index, input_file):
ffmpeg_path = resource_path(os.path.join("ffmpeg_folder", "ffmpeg"))
filename = os.path.basename(input_file)
output_file = os.path.join(self.output_folder, f"processed_{filename}.{self.output_format.get()}")
if os.path.exists(output_file):
overwrite = messagebox.askyesno("文件已存在", f"{output_file} 已存在,是否覆盖?")
if not overwrite:
self.update_tree_status(index, "跳过")
return
cmd = [
ffmpeg_path,
"-y", # 自动覆盖输出文件
"-i", input_file,
"-vf", "scale='if(gt(iw/ih,9/16),1080,-2)':'if(gt(iw/ih,9/16),-2,1920)',pad=1080:1920:(1080-iw)/2:(1920-ih)/2",
"-c:v", self.video_codec.get(),
"-c:a", self.audio_codec.get(),
output_file
]
self.log(f"开始处理: {filename}")
self.update_tree_status(index, "处理中")
try:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
process = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True, encoding='utf-8', startupinfo=startupinfo)
self.ffmpeg_processes.append(process)
for line in process.stderr:
if self.is_closing:
break
progress = self.parse_progress(line)
if progress:
self.update_tree_status(index, progress)
process.wait()
except Exception as e:
self.log(f"处理文件时出错: {filename} - {str(e)}")
self.update_tree_status(index, "处理失败")
process = None
self.current_index = None
return
if self.is_closing:
self.update_tree_status(index, "未完成")
else:
self.log(f"完成处理: {filename}")
self.update_tree_status(index, "已完成")
self.ffmpeg_processes.remove(process)
def parse_progress(self, line):
match = re.search(r'time=(\d+:\d+:\d+\.\d+)', line)
if match:
return f"进度: {match.group(1)}"
return None
def on_closing(self):
self.is_closing = True
for process in self.ffmpeg_processes:
proc = psutil.Process(process.pid)
proc.terminate()
self.master.destroy()
if __name__ == "__main__":
root = ttk.Window(themename="superhero")
app = VideoProcessor(master=root)
root.mainloop()