Python代码-视频帧批处理工具v2025.03.14
import os
import threading
import tkinter as tk
from tkinter import filedialog, messagebox
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.config import change_settings
import customtkinter as ctk
import sys
def get_ffmpeg_path():
"""获取 ffmpeg 的路径"""
# 获取当前运行的目录
current_dir = os.path.dirname(sys.executable) if hasattr(sys, '_MEIPASS') else os.path.dirname(__file__)
ffmpeg_path = os.path.join(current_dir, "ffmpeg.exe")
# 检查 ffmpeg 文件是否存在
if not os.path.exists(ffmpeg_path):
raise FileNotFoundError(f"FFmpeg 文件不存在: {ffmpeg_path}")
return ffmpeg_path
# 设置 ffmpeg 路径
change_settings({"FFMPEG_BINARY": get_ffmpeg_path()})
class VideoProcessorApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("视频帧批处理工具v2025.03.14")
self.geometry("550x800")
# 初始化变量
self.selected_files = []
self.output_folder = ""
self.suffix = "_processed"
# 创建 UI
self.create_widgets()
def create_widgets(self):
"""创建界面组件"""
self.video_label = ctk.CTkLabel(self, text="选择视频文件:", font=("Arial", 16))
self.video_label.pack(pady=10)
self.video_listbox = ctk.CTkTextbox(self, width=500, height=100, font=("Arial", 12))
self.video_listbox.pack(pady=10)
self.select_videos_button = ctk.CTkButton(self, text="添加视频文件", command=self.select_videos)
self.select_videos_button.pack(pady=10)
self.output_folder_label = ctk.CTkLabel(self, text="输出文件夹:", font=("Arial", 16))
self.output_folder_label.pack(pady=10)
self.output_folder_entry = ctk.CTkEntry(self, width=400, font=("Arial", 12))
self.output_folder_entry.pack(pady=10)
self.select_output_folder_button = ctk.CTkButton(self, text="选择文件夹", command=self.select_output_folder)
self.select_output_folder_button.pack(pady=10)
self.suffix_label = ctk.CTkLabel(self, text="添加后缀:", font=("Arial", 16))
self.suffix_label.pack(pady=10)
self.suffix_entry = ctk.CTkEntry(self, width=200, font=("Arial", 12))
self.suffix_entry.insert(0, self.suffix)
self.suffix_entry.pack(pady=10)
self.progress_bar = ctk.CTkProgressBar(self, width=500)
self.progress_bar.pack(pady=20)
self.progress_bar.set(0)
self.status_label = ctk.CTkLabel(self, text="状态:等待处理", font=("Arial", 14))
self.status_label.pack(pady=10)
self.output_textbox = ctk.CTkTextbox(self, width=500, height=100, font=("Arial", 12))
self.output_textbox.pack(pady=10)
self.start_button = ctk.CTkButton(self, text="开始处理", command=self.start_processing)
self.start_button.pack(pady=10)
self.exit_button = ctk.CTkButton(self, text="退出", command=self.quit)
self.exit_button.pack(pady=10)
def select_videos(self):
file_paths = filedialog.askopenfilenames(
title="选择视频文件",
filetypes=[("视频文件", "*.mp4;*.avi;*.mov;*.mkv"), ("所有文件", "*.*")]
)
if file_paths:
self.selected_files = file_paths
self.video_listbox.delete("1.0", tk.END)
for file in file_paths:
self.video_listbox.insert(tk.END, file + "\n")
def select_output_folder(self):
folder_path = filedialog.askdirectory(title="选择输出文件夹")
if folder_path:
self.output_folder = folder_path
self.output_folder_entry.delete(0, tk.END)
self.output_folder_entry.insert(0, folder_path)
def start_processing(self):
if not self.selected_files:
messagebox.showerror("错误", "请先选择视频文件!")
return
if not self.output_folder_entry.get():
messagebox.showerror("错误", "请先选择输出文件夹!")
return
self.suffix = self.suffix_entry.get()
self.output_folder = self.output_folder_entry.get()
self.output_textbox.delete("1.0", tk.END)
threading.Thread(target=self.process_videos).start()
def process_videos(self):
self.status_label.configure(text="正在处理视频...")
self.progress_bar.set(0)
for i, video_path in enumerate(self.selected_files):
try:
filename, ext = os.path.splitext(os.path.basename(video_path))
output_filename = f"{filename}{self.suffix}{ext}"
output_path = os.path.join(self.output_folder, output_filename)
self.process_single_video(video_path, output_path)
self.output_textbox.insert(tk.END, f"已完成: {output_path}\n")
except Exception as e:
self.output_textbox.insert(tk.END, f"处理失败: {video_path}\n错误: {str(e)}\n")
progress = (i + 1) / len(self.selected_files)
self.progress_bar.set(progress)
self.status_label.configure(text=f"正在处理: {i + 1}/{len(self.selected_files)}")
self.status_label.configure(text="所有视频处理完成!")
self.output_textbox.insert(tk.END, "所有视频处理完成!\n")
messagebox.showinfo("完成", "所有视频处理完成!")
def process_single_video(self, input_path, output_path):
try:
video = VideoFileClip(input_path)
self.output_textbox.insert(tk.END, f"成功加载视频: {input_path}\n")
except Exception as e:
self.output_textbox.insert(tk.END, f"加载失败: {input_path}\n错误: {str(e)}\n")
return
try:
if video.duration < 2 / video.fps:
self.output_textbox.insert(tk.END, f"视频时长过短,无法处理: {input_path}\n")
return
fps = video.fps
duration = video.duration
first_frame_time = 1 / fps
last_frame_time = duration - (1 / fps)
self.output_textbox.insert(tk.END, f"正在裁剪视频: {input_path}\n")
processed_video = video.subclip(first_frame_time, last_frame_time)
if processed_video is None:
self.output_textbox.insert(tk.END, f"裁剪后的视频为空: {input_path}\n")
return
self.output_textbox.insert(tk.END, f"正在保存视频: {output_path}\n")
processed_video.write_videofile(output_path, codec="libx264", audio_codec="aac")
self.output_textbox.insert(tk.END, f"已完成: {output_path}\n")
except Exception as e:
self.output_textbox.insert(tk.END, f"处理失败: {input_path}\n错误: {str(e)}\n")
if __name__ == "__main__":
app = VideoProcessorApp()
app.mainloop()