Python实现批量视频竖屏转横屏工具实战

在当今数字化时代,视频内容已成为信息传播的重要载体。然而,不同设备和平台对视频显示格式的要求各不相同,尤其是竖屏与横屏之间的转换需求日益增多。为了满足这一需求,本文将介绍如何使用Python实现一个批量视频竖屏转横屏的工具。通过实战演练,我们将深入了解视频处理的基本原理,掌握利用Python进行视频格式转换的技巧,并借助强大的第三方库,轻松实现视频的批量处理。无论是对于个人用户还是专业视频处理人员,本文都将提供一份宝贵的参考指南,助你在视频处理领域更上一层楼。

1. 简介

这是一款基于Python和Tkinter框架开发的视频处理器应用。该应用集成了FFmpeg,用于批量横屏转竖屏视频处理,支持多种视频格式和编码选择,并提供多线程支持以提升处理效率。用户可以通过简洁直观的图形界面导入、删除视频文件,并且对每个文件设置处理参数,如输出格式、视频编码器、音频编码器及高斯模糊效果。应用还支持暂停/继续和多线程并发处理,确保在长时间处理时能保持灵活性和高效性。

2.功能说明

图片[1]-Python实现批量视频竖屏转横屏工具实战-趣考网

图片[2]-Python实现批量视频竖屏转横屏工具实战-趣考网

2.1 文件管理

  • 支持通过拖放或文件选择对话框导入视频文件。

  • 支持删除已导入的文件。

  • 显示导入文件的基本信息,包括文件名和处理状态。

2.2 FFmpeg配置

  • 用户可以选择视频输出格式(MP4、AVI、MOV等)。

  • 提供不同的视频和音频编码器选项(如libx264、aac)。

  • 支持设置处理线程数,允许用户根据系统性能调整并发处理数量。

  • 可选应用高斯模糊效果以实现视频特效。

2.3 多线程处理

  • 使用ThreadPoolExecutor实现多线程并发处理多个视频文件。

  • 支持暂停和继续处理,用户可以在处理过程中暂停视频文件的转换,稍后继续。

2.4 输出文件管理

  • 用户可以设置输出文件夹,处理完成的视频会保存至该目录。

  • 支持在处理完成后直接打开输出文件夹。

2.5 进度监控与日志

  • 在文件列表中显示每个视频的处理进度。

  • 提供日志框,实时显示处理过程中的信息。

2.6 拖放支持

支持通过拖拽文件到窗口的方式导入视频文件,提升用户体验。

2.7 错误处理与反馈

针对文件已存在、格式不支持等情况提供相应的错误提示。

3. 运行效果

图片[3]-Python实现批量视频竖屏转横屏工具实战-趣考网

图片[4]-Python实现批量视频竖屏转横屏工具实战-趣考网

视频处理转换前(横屏状态):

图片[5]-Python实现批量视频竖屏转横屏工具实战-趣考网

视频处理转换后(竖屏状态):

图片[6]-Python实现批量视频竖屏转横屏工具实战-趣考网

4. 相关源码

importosimportttkbootstrapasttkfromttkbootstrap.constantsimport*fromtkinterimportfiledialog,messagebox,END,Text,StringVar,IntVar,BooleanVar,Menufromconcurrent.futuresimportThreadPoolExecutor,as_completedimportsubprocessimportthreadingimportpsutilimportreimportsysfromtkinterdnd2importTkinterDnD,DND_FILESdefresource_path(relative_path):"""Getabsolutepathtoresource,worksfordevandforPyInstaller"""try:#PyInstallercreatesatempfolderandstorespathin_MEIPASSbase_path=sys._MEIPASSexceptException:base_path=os.path.abspath(".")returnos.path.join(base_path,relative_path)classVideoProcessor:def__init__(self,master):self.master=masterself.master.title("视频处理器吾爱作者:是谁的大海(是貔貅呀)版本:1.3")self.input_files=[]self.output_folder=""self.process_thread=Noneself.pause_event=threading.Event()self.pause_event.set()#Startintheunpausedstateself.ffmpeg_processes=[]#Listtokeeptrackofallffmpegprocessesself.is_closing=Falseself.output_format=StringVar(value="mp4")self.video_codec=StringVar(value="libx264")self.audio_codec=StringVar(value="aac")self.thread_count=IntVar(value=1)#Defaultto1threadsself.apply_blur=BooleanVar(value=False)#Booleantocheckifblurshouldbeappliedself.create_widgets()self.create_menu()self.master.protocol("WM_DELETE_WINDOW",self.on_closing)defcreate_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)#Setupdraganddropself.master.drop_target_register(DND_FILES)self.master.dnd_bind(\'<>\',self.drop_files)defcreate_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)defshow_usage_instructions(self):instructions=("使用说明:\\n""1.导入文件:点击“导入文件”按钮,选择需要处理的视频文件,或将视频文件拖拽到软件窗口中。\\n""2.设置输出文件夹:点击“导出文件”按钮,选择一个文件夹作为输出文件夹。\\n""3.配置FFmpeg参数:在“FFmpeg配置”区域,选择输出格式、视频编码器、音频编码器、线程数,并可选择是否应用高斯模糊效果。\\n""4.开始处理:点击“开始处理文件”按钮,开始批量处理视频文件。处理过程中可以查看处理进度和日志信息。\\n""5.查看输出文件:点击“打开文件”按钮,打开输出文件夹查看处理完成的视频文件。\\n""6.删除文件:选择文件列表中的文件,点击“删除文件”按钮删除不需要处理的文件。\\n")messagebox.showinfo("使用说明",instructions)defshow_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)defdrop_files(self,event):files=self.master.tk.splitlist(event.data)forfileinfiles:iffilenotinself.input_filesandfile.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)}")defadd_files(self):files=filedialog.askopenfilenames(title="选择视频文件",filetypes=[("视频文件","*.mp4*.avi*.mov")])forfileinfiles:iffilenotinself.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)}")defremove_files(self):selected_items=self.file_tree.selection()indices_to_remove=[]foriteminselected_items:values=self.file_tree.item(item,\'values\')ifvalues:index=int(values[0])-1indices_to_remove.append(index)self.file_tree.delete(item)#删除索引列表中的元素(倒序删除避免索引问题)forindexinsorted(indices_to_remove,reverse=True):delself.input_files[index]self.log("删除选中文件")self.refresh_file_list()defrefresh_file_list(self):foriteminself.file_tree.get_children():self.file_tree.delete(item)forindex,fileinenumerate(self.input_files):self.file_tree.insert(\'\',END,values=(index+1,os.path.basename(file),"未处理"))defset_output_folder(self):self.output_folder=filedialog.askdirectory(title="选择输出文件夹")self.log(f"设置输出文件夹:{self.output_folder}")defstart_processing(self):ifnotself.input_filesornotself.output_folder:messagebox.showerror("错误","请添加文件并设置输出文件夹。")returnself.process_thread=threading.Thread(target=self.process_videos_concurrently)self.process_thread.start()deftoggle_pause(self):ifself.pause_event.is_set():self.pause_event.clear()self.log("处理暂停")forprocessinself.ffmpeg_processes:proc=psutil.Process(process.pid)proc.suspend()else:self.pause_event.set()self.log("处理继续")forprocessinself.ffmpeg_processes:proc=psutil.Process(process.pid)proc.resume()defopen_output_folder(self):ifself.output_folder:os.startfile(self.output_folder)self.log(f"打开输出文件夹:{self.output_folder}")else:messagebox.showerror("错误","请先设置输出文件夹。")deflog(self,message):ifnotself.is_closing:self.master.after(0,self._log,message)def_log(self,message):ifnotself.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)defupdate_tree_status(self,index,status):ifnotself.is_closing:self.master.after(0,self._update_tree_status,index,status)def_update_tree_status(self,index,status):ifnotself.is_closing:self.file_tree.item(self.file_tree.get_children()[index],values=(index+1,os.path.basename(self.input_files[index]),status))defprocess_videos_concurrently(self):max_workers=self.thread_count.get()withThreadPoolExecutor(max_workers=max_workers)asexecutor:futures=[executor.submit(self.process_video,index,input_file)forindex,input_fileinenumerate(self.input_files)]forfutureinas_completed(futures):future.result()defprocess_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()}")ifos.path.exists(output_file):overwrite=messagebox.askyesno("文件已存在",f"{output_file}已存在,是否覆盖?")ifnotoverwrite:self.update_tree_status(index,"跳过")returnifself.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_USESHOWWINDOWprocess=subprocess.Popen(cmd,stderr=subprocess.PIPE,universal_newlines=True,encoding=\'utf-8\',startupinfo=startupinfo)self.ffmpeg_processes.append(process)forlineinprocess.stderr:ifself.is_closing:breakprogress=self.parse_progress(line)ifprogress:self.update_tree_status(index,progress)process.wait()exceptExceptionase:self.log(f"处理文件时出错:{filename}-{str(e)}")self.update_tree_status(index,"处理失败")returnifself.is_closing:self.update_tree_status(index,"未完成")else:self.log(f"完成处理:{filename}")self.update_tree_status(index,"已完成")self.ffmpeg_processes.remove(process)defparse_progress(self,line):match=re.search(r\'time=(\\d+:\\d+:\\d+\\.\\d+)\',line)ifmatch:returnf"进度:{match.group(1)}"returnNonedefon_closing(self):self.is_closing=Trueforprocessinself.ffmpeg_processes:proc=psutil.Process(process.pid)proc.terminate()self.master.destroy()if__name__=="__main__":root=TkinterDnD.Tk()root.title("视频处理器")root.geometry("870x520")#Setthewindowsizeto870x520root.resizable(False,False)#Makethewindownon-resizableapp=VideoProcessor(master=root)root.mainloop()

5.总结

该视频处理器应用通过Python与Tkinter提供了一个强大而简洁的图形界面,允许用户批量处理视频文件。借助FFmpeg,用户不仅可以自由选择输出格式和编码器,还可以根据需求应用视频特效,如高斯模糊。多线程的支持使得处理多个视频文件更加高效,确保了在处理过程中能够灵活控制暂停、继续和取消操作。通过增强的文件管理和进度监控,用户能够轻松掌控整个视频处理过程。此工具对于需要批量转换视频格式或应用特效的用户非常实用,尤其适合需要高效处理大量视频文件的场景。

通过本次实战演练,我们成功实现了一个基于Python的批量视频竖屏转横屏工具。从视频处理的基础知识出发,我们逐步掌握了利用Python进行视频格式转换的关键技术。通过引入第三方库,我们实现了视频的批量读取、处理与保存,大大提高了工作效率。同时,我们也见证了Python在视频处理领域的强大潜力和广泛应用前景。无论是对于个人日常的视频编辑需求,还是对于专业视频处理人员的工作要求,本工具都展现出了出色的性能和稳定性。希望本文能够为你提供一份有价值的参考,助你在视频处理的道路上越走越远。

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享