Python批量實(shí)現(xiàn)橫屏轉(zhuǎn)豎屏的視頻處理工具
1. 簡(jiǎn)介
這是一款基于Python和Tkinter框架開發(fā)的視頻處理器應(yīng)用。該應(yīng)用集成了FFmpeg,用于批量橫屏轉(zhuǎn)豎屏視頻處理,支持多種視頻格式和編碼選擇,并提供多線程支持以提升處理效率。用戶可以通過簡(jiǎ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)大而簡(jiǎn)潔的圖形界面,允許用戶批量處理視頻文件。借助FFmpeg,用戶不僅可以自由選擇輸出格式和編碼器,還可以根據(jù)需求應(yīng)用視頻特效,如高斯模糊。多線程的支持使得處理多個(gè)視頻文件更加高效,確保了在處理過程中能夠靈活控制暫停、繼續(xù)和取消操作。通過增強(qiáng)的文件管理和進(jìn)度監(jiān)控,用戶能夠輕松掌控整個(gè)視頻處理過程。此工具對(duì)于需要批量轉(zhuǎn)換視頻格式或應(yīng)用特效的用戶非常實(shí)用,尤其適合需要高效處理大量視頻文件的場(chǎng)景。
以上就是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í)例)
本文主要簡(jiǎn)單的介紹使用Python處理txt漢字文字、二維列表排序和獲取list下標(biāo)的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-03-03
Python使用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-11
Python機(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-02
Python圖片視頻超分模型RealBasicVSR的使用教程
這篇文章主要和大家分享一個(gè)有意思的模型:RealBasicVSR。這個(gè)模型可以實(shí)現(xiàn)圖片或視頻的超分處理,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-05-05
Django中如何使用Celery執(zhí)行異步任務(wù)
這篇文章主要介紹了Django中如何使用Celery執(zhí)行異步任務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
選擇Python寫網(wǎng)絡(luò)爬蟲的優(yōu)勢(shì)和理由
在本篇文章里小編給各位整理了一篇關(guān)于選擇Python寫網(wǎng)絡(luò)爬蟲的優(yōu)勢(shì)和理由以及相關(guān)代碼實(shí)例,有興趣的朋友們閱讀下吧。2019-07-07
利用pyinstaller或virtualenv將python程序打包詳解
這篇文章主要給大家介紹了利用pyinstaller將python程序打包的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。2017-03-03

