Python批量實(shí)現(xiàn)橫屏轉(zhuǎn)豎屏的視頻處理工具
1. 簡介
這是一款基于Python和Tkinter框架開發(fā)的視頻處理器應(yīng)用。該應(yīng)用集成了FFmpeg,用于批量橫屏轉(zhuǎn)豎屏視頻處理,支持多種視頻格式和編碼選擇,并提供多線程支持以提升處理效率。用戶可以通過簡潔直觀的圖形界面導(dǎo)入、刪除視頻文件,并且對(duì)每個(gè)文件設(shè)置處理參數(shù),如輸出格式、視頻編碼器、音頻編碼器及高斯模糊效果。應(yīng)用還支持暫停/繼續(xù)和多線程并發(fā)處理,確保在長時(shí)間處理時(shí)能保持靈活性和高效性。
2.功能說明
2.1 文件管理
- 支持通過拖放或文件選擇對(duì)話框?qū)胍曨l文件。
- 支持刪除已導(dǎo)入的文件。
- 顯示導(dǎo)入文件的基本信息,包括文件名和處理狀態(tài)。
2.2 FFmpeg配置
- 用戶可以選擇視頻輸出格式(MP4、AVI、MOV等)。
- 提供不同的視頻和音頻編碼器選項(xiàng)(如libx264、aac)。
- 支持設(shè)置處理線程數(shù),允許用戶根據(jù)系統(tǒng)性能調(diào)整并發(fā)處理數(shù)量。
- 可選應(yīng)用高斯模糊效果以實(shí)現(xiàn)視頻特效。
2.3 多線程處理
- 使用ThreadPoolExecutor實(shí)現(xiàn)多線程并發(fā)處理多個(gè)視頻文件。
- 支持暫停和繼續(xù)處理,用戶可以在處理過程中暫停視頻文件的轉(zhuǎn)換,稍后繼續(xù)。
2.4 輸出文件管理
- 用戶可以設(shè)置輸出文件夾,處理完成的視頻會(huì)保存至該目錄。
- 支持在處理完成后直接打開輸出文件夾。
2.5 進(jìn)度監(jiān)控與日志
- 在文件列表中顯示每個(gè)視頻的處理進(jìn)度。
- 提供日志框,實(shí)時(shí)顯示處理過程中的信息。
2.6 拖放支持
支持通過拖拽文件到窗口的方式導(dǎo)入視頻文件,提升用戶體驗(yàn)。
2.7 錯(cuò)誤處理與反饋
針對(duì)文件已存在、格式不支持等情況提供相應(yīng)的錯(cuò)誤提示。
3. 運(yùn)行效果
視頻處理轉(zhuǎn)換前(橫屏狀態(tài)):
視頻處理轉(zhuǎn)換后(豎屏狀態(tài)):
4. 相關(guān)源碼
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 = ('序號(hào)', '文件夾名字', '進(jìn)度') self.file_tree = ttk.Treeview(self.file_list_frame, columns=columns, show='headings') self.file_tree.heading('序號(hào)', text='序號(hào)') self.file_tree.heading('文件夾名字', text='文件夾名字') self.file_tree.heading('進(jìn)度', text='進(jìn)度') self.file_tree.column('序號(hào)', width=100, anchor='center') self.file_tree.column('文件夾名字', width=400, anchor='w') self.file_tree.column('進(jìn)度', 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="導(dǎo)入文件", 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="暫停/繼續(xù)", 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="導(dǎo)出文件", 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="線程數(shù):").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="應(yīng)用高斯模糊效果", 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. 導(dǎo)入文件:點(diǎn)擊“導(dǎo)入文件”按鈕,選擇需要處理的視頻文件,或?qū)⒁曨l文件拖拽到軟件窗口中。\n" "2. 設(shè)置輸出文件夾:點(diǎn)擊“導(dǎo)出文件”按鈕,選擇一個(gè)文件夾作為輸出文件夾。\n" "3. 配置FFmpeg參數(shù):在“FFmpeg 配置”區(qū)域,選擇輸出格式、視頻編碼器、音頻編碼器、線程數(shù),并可選擇是否應(yīng)用高斯模糊效果。\n" "4. 開始處理:點(diǎn)擊“開始處理文件”按鈕,開始批量處理視頻文件。處理過程中可以查看處理進(jìn)度和日志信息。\n" "5. 查看輸出文件:點(diǎn)擊“打開文件”按鈕,打開輸出文件夾查看處理完成的視頻文件。\n" "6. 刪除文件:選擇文件列表中的文件,點(diǎn)擊“刪除文件”按鈕刪除不需要處理的文件。\n" ) messagebox.showinfo("使用說明", instructions) def show_software_details(self): details = ( "僅供學(xué)習(xí),切勿使用到其他用途\n" "1. 輸出格式:支持MP4、AVI和MOV等常見格式,用戶可自定義選擇。\n" "2. 視頻壓縮:默認(rèn)使用libx264視頻編碼器和aac音頻編碼器,支持高效視頻壓縮,用戶可自定義選擇其他編碼器。\n" "3. 視頻裁剪:適用于將1920x1080橫屏視頻裁剪成9:16豎屏視頻,不會(huì)變形。\n" "4. 高斯模糊:可選應(yīng)用高斯模糊效果,適用于特殊視頻效果需求。\n" "5. 多線程處理:支持多線程并發(fā)處理,用戶可自定義線程數(shù),提高處理效率。\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"導(dǎo)入文件: {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"導(dǎo)入文件: {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"設(shè)置輸出文件夾: {self.output_folder}") def start_processing(self): if not self.input_files or not self.output_folder: messagebox.showerror("錯(cuò)誤", "請(qǐng)?zhí)砑游募⒃O(shè)置輸出文件夾。") 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("處理繼續(xù)") 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("錯(cuò)誤", "請(qǐng)先設(shè)置輸出文件夾。") 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", # 自動(dòng)覆蓋輸出文件 "-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", # 自動(dòng)覆蓋輸出文件 "-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"處理文件時(shí)出錯(cuò): {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"進(jìn)度: {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.總結(jié)
該視頻處理器應(yīng)用通過Python與Tkinter提供了一個(gè)強(qiáng)大而簡潔的圖形界面,允許用戶批量處理視頻文件。借助FFmpeg,用戶不僅可以自由選擇輸出格式和編碼器,還可以根據(jù)需求應(yīng)用視頻特效,如高斯模糊。多線程的支持使得處理多個(gè)視頻文件更加高效,確保了在處理過程中能夠靈活控制暫停、繼續(xù)和取消操作。通過增強(qiáng)的文件管理和進(jìn)度監(jiān)控,用戶能夠輕松掌控整個(gè)視頻處理過程。此工具對(duì)于需要批量轉(zhuǎn)換視頻格式或應(yīng)用特效的用戶非常實(shí)用,尤其適合需要高效處理大量視頻文件的場景。
以上就是Python批量實(shí)現(xiàn)橫屏轉(zhuǎn)豎屏的視頻處理工具的詳細(xì)內(nèi)容,更多關(guān)于Python視頻橫屏轉(zhuǎn)豎屏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python 專題五 列表基礎(chǔ)知識(shí)(二維list排序、獲取下標(biāo)和處理txt文本實(shí)例)
本文主要簡單的介紹使用Python處理txt漢字文字、二維列表排序和獲取list下標(biāo)的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-03-03Python使用Networkx實(shí)現(xiàn)復(fù)雜的人物關(guān)系圖
日常工作、生活中我們經(jīng)常會(huì)遇到一些復(fù)雜的事務(wù)關(guān)系,比如人物關(guān)系,那如何才能清楚直觀的看清楚這些任務(wù)關(guān)系呢?所以小編給大家介紹了Python如何使用Networkx實(shí)現(xiàn)復(fù)雜的人物關(guān)系圖,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2023-11-11Python機(jī)器學(xué)習(xí)之隨機(jī)梯度下降法的實(shí)現(xiàn)
如果當(dāng)我們數(shù)據(jù)量和樣本量非常大時(shí),每一項(xiàng)都要參與到梯度下降,那么它的計(jì)算量時(shí)非常大的,所以我們需要采用隨機(jī)梯度下降法。本文介紹了Python實(shí)現(xiàn)隨機(jī)梯度下降法的方法,希望對(duì)大家有所幫助2023-02-02Python圖片視頻超分模型RealBasicVSR的使用教程
這篇文章主要和大家分享一個(gè)有意思的模型:RealBasicVSR。這個(gè)模型可以實(shí)現(xiàn)圖片或視頻的超分處理,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-05-05Django中如何使用Celery執(zhí)行異步任務(wù)
這篇文章主要介紹了Django中如何使用Celery執(zhí)行異步任務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11選擇Python寫網(wǎng)絡(luò)爬蟲的優(yōu)勢和理由
在本篇文章里小編給各位整理了一篇關(guān)于選擇Python寫網(wǎng)絡(luò)爬蟲的優(yōu)勢和理由以及相關(guān)代碼實(shí)例,有興趣的朋友們閱讀下吧。2019-07-07利用pyinstaller或virtualenv將python程序打包詳解
這篇文章主要給大家介紹了利用pyinstaller將python程序打包的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-03-03