在当今数字化时代,视频内容已成为信息传播的重要载体。然而,不同设备和平台对视频显示格式的要求各不相同,尤其是竖屏与横屏之间的转换需求日益增多。为了满足这一需求,本文将介绍如何使用Python实现一个批量视频竖屏转横屏的工具。通过实战演练,我们将深入了解视频处理的基本原理,掌握利用Python进行视频格式转换的技巧,并借助强大的第三方库,轻松实现视频的批量处理。无论是对于个人用户还是专业视频处理人员,本文都将提供一份宝贵的参考指南,助你在视频处理领域更上一层楼。
1. 简介
这是一款基于Python和Tkinter框架开发的视频处理器应用。该应用集成了FFmpeg,用于批量横屏转竖屏视频处理,支持多种视频格式和编码选择,并提供多线程支持以提升处理效率。用户可以通过简洁直观的图形界面导入、删除视频文件,并且对每个文件设置处理参数,如输出格式、视频编码器、音频编码器及高斯模糊效果。应用还支持暂停/继续和多线程并发处理,确保在长时间处理时能保持灵活性和高效性。
2.功能说明
2.1 文件管理
支持通过拖放或文件选择对话框导入视频文件。
支持删除已导入的文件。
显示导入文件的基本信息,包括文件名和处理状态。
2.2 FFmpeg配置
用户可以选择视频输出格式(MP4、AVI、MOV等)。
提供不同的视频和音频编码器选项(如libx264、aac)。
支持设置处理线程数,允许用户根据系统性能调整并发处理数量。
可选应用高斯模糊效果以实现视频特效。
2.3 多线程处理
使用ThreadPoolExecutor实现多线程并发处理多个视频文件。
支持暂停和继续处理,用户可以在处理过程中暂停视频文件的转换,稍后继续。
2.4 输出文件管理
用户可以设置输出文件夹,处理完成的视频会保存至该目录。
支持在处理完成后直接打开输出文件夹。
2.5 进度监控与日志
在文件列表中显示每个视频的处理进度。
提供日志框,实时显示处理过程中的信息。
2.6 拖放支持
支持通过拖拽文件到窗口的方式导入视频文件,提升用户体验。
2.7 错误处理与反馈
针对文件已存在、格式不支持等情况提供相应的错误提示。
3. 运行效果
视频处理转换前(横屏状态):
视频处理转换后(竖屏状态):
4. 相关源码
import os import ttkbootstrap as ttk from ttkbootstrap.constants import * from tkinter import filedialog, messagebox, END, Text, StringVar, IntVar, BooleanVar, Menu from concurrent.futures import ThreadPoolExecutor, as_completed import subprocess import threading import psutil import re import sys from tkinterdnd2 import TkinterDnD, DND_FILES 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("视频处理器 吾爱作者:是谁的大海(是貔貅呀) 版本:1.3") 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=1) # Default to 1 threads self.apply_blur = BooleanVar(value=False) # Boolean to check if blur should be applied self.create_widgets() self.create_menu() 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) self.blur_checkbox = ttk.Checkbutton(config_frame, text="应用高斯模糊效果", variable=self.apply_blur) self.blur_checkbox.pack(side=LEFT, padx=5, pady=5) # Set up drag and drop self.master.drop_target_register(DND_FILES) self.master.dnd_bind('<<Drop>>', self.drop_files) def create_menu(self): menu_bar = Menu(self.master) self.master.config(menu=menu_bar) help_menu = Menu(menu_bar, tearoff=0) menu_bar.add_cascade(label="帮助", menu=help_menu) help_menu.add_command(label="使用说明", command=self.show_usage_instructions) help_menu.add_command(label="软件具体说明", command=self.show_software_details) def show_usage_instructions(self): instructions = ( "使用说明:\n" "1. 导入文件:点击“导入文件”按钮,选择需要处理的视频文件,或将视频文件拖拽到软件窗口中。\n" "2. 设置输出文件夹:点击“导出文件”按钮,选择一个文件夹作为输出文件夹。\n" "3. 配置FFmpeg参数:在“FFmpeg 配置”区域,选择输出格式、视频编码器、音频编码器、线程数,并可选择是否应用高斯模糊效果。\n" "4. 开始处理:点击“开始处理文件”按钮,开始批量处理视频文件。处理过程中可以查看处理进度和日志信息。\n" "5. 查看输出文件:点击“打开文件”按钮,打开输出文件夹查看处理完成的视频文件。\n" "6. 删除文件:选择文件列表中的文件,点击“删除文件”按钮删除不需要处理的文件。\n" ) messagebox.showinfo("使用说明", instructions) def show_software_details(self): details = ( "仅供学习,切勿使用到其他用途\n" "1. 输出格式:支持MP4、AVI和MOV等常见格式,用户可自定义选择。\n" "2. 视频压缩:默认使用libx264视频编码器和aac音频编码器,支持高效视频压缩,用户可自定义选择其他编码器。\n" "3. 视频裁剪:适用于将1920x1080横屏视频裁剪成9:16竖屏视频,不会变形。\n" "4. 高斯模糊:可选应用高斯模糊效果,适用于特殊视频效果需求。\n" "5. 多线程处理:支持多线程并发处理,用户可自定义线程数,提高处理效率。\n" ) messagebox.showinfo("软件具体说明", details) def drop_files(self, event): files = self.master.tk.splitlist(event.data) for file in files: if file not in self.input_files and file.lower().endswith(('.mp4', '.avi', '.mov')): 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 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() indices_to_remove = [] for item in selected_items: values = self.file_tree.item(item, 'values') if values: index = int(values[0]) - 1 indices_to_remove.append(index) self.file_tree.delete(item) # 删除索引列表中的元素(倒序删除避免索引问题) for index in sorted(indices_to_remove, reverse=True): del self.input_files[index] 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 if self.apply_blur.get(): cmd = [ ffmpeg_path, "-y", # 自动覆盖输出文件 "-i", input_file, "-vf", "split[a][b];[a]scale=1080:1920,boxblur=10:5[1];[b]scale=1080:ih*1080/iw[2];[1][2]overlay=0:(H-h)/2", "-c:v", self.video_codec.get(), "-crf", "18", "-preset", "veryfast", "-aspect", "9:16", "-c:a", self.audio_codec.get(), output_file ] else: 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(), "-crf", "18", "-preset", "veryfast", "-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, "处理失败") 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 = TkinterDnD.Tk() root.title("视频处理器") root.geometry("870x520") # Set the window size to 870x520 root.resizable(False, False) # Make the window non-resizable app = VideoProcessor(master=root) root.mainloop()
5.总结
该视频处理器应用通过Python与Tkinter提供了一个强大而简洁的图形界面,允许用户批量处理视频文件。借助FFmpeg,用户不仅可以自由选择输出格式和编码器,还可以根据需求应用视频特效,如高斯模糊。多线程的支持使得处理多个视频文件更加高效,确保了在处理过程中能够灵活控制暂停、继续和取消操作。通过增强的文件管理和进度监控,用户能够轻松掌控整个视频处理过程。此工具对于需要批量转换视频格式或应用特效的用户非常实用,尤其适合需要高效处理大量视频文件的场景。
通过本次实战演练,我们成功实现了一个基于Python的批量视频竖屏转横屏工具。从视频处理的基础知识出发,我们逐步掌握了利用Python进行视频格式转换的关键技术。通过引入第三方库,我们实现了视频的批量读取、处理与保存,大大提高了工作效率。同时,我们也见证了Python在视频处理领域的强大潜力和广泛应用前景。无论是对于个人日常的视频编辑需求,还是对于专业视频处理人员的工作要求,本工具都展现出了出色的性能和稳定性。希望本文能够为你提供一份有价值的参考,助你在视频处理的道路上越走越远。
本文来源于#探客白泽,由@蜜芽 整理发布。如若内容造成侵权/违法违规/事实不符,请联系本站客服处理!
该文章观点仅代表作者本人,不代表本站立场。本站不承担相关法律责任。
如若转载,请注明出处:https://www.zhanid.com/biancheng/3333.html