Python進(jìn)行批量剪輯視頻片頭片尾
1.簡(jiǎn)介
批量剪輯片頭片尾的軟件,讓你的視頻創(chuàng)作事半功倍,視頻剪輯處理完成后,用戶可以在指定文件夾中查看已經(jīng)剪切完片頭片尾的視頻。這些工具不僅適用于個(gè)人用戶進(jìn)行日常的視頻編輯工作,也適合視頻制作團(tuán)隊(duì)在項(xiàng)目制作中提高工作效率。
功能:
加載和保存配置:讀取和保存配置文件。
獲取視頻時(shí)長(zhǎng):使用ffprobe獲取視頻的總時(shí)長(zhǎng)。
視頻剪輯:使用ffmpeg截取視頻片段,支持批量處理。
圖形用戶界面:通過tkinter選擇輸入文件和輸出目錄,設(shè)置開始和結(jié)束時(shí)間。
語音提示:在剪輯完成后進(jìn)行語音提示。
2.運(yùn)行效果
3.相關(guān)源碼
import os import subprocess import threading import tkinter as tk from tkinter import filedialog from tkinter import messagebox from tkinter import ttk import json from datetime import datetime, timedelta import pyttsx3 INTERNAL_FOLDER = os.path.join(os.path.dirname(__file__), '_internal') CONFIG_FILE = os.path.join(INTERNAL_FOLDER, 'config.json') FFMPEG_FOLDER = os.path.join(INTERNAL_FOLDER, 'ffmpeg_folder') COMPLETION_FILE = os.path.join(INTERNAL_FOLDER, 'completed.txt') ICON_PATH = os.path.join(INTERNAL_FOLDER, 'moviecamera.ico') # 使用_internal文件夾中的圖標(biāo) def ensure_internal_dir_exists(): if not os.path.exists(INTERNAL_FOLDER): os.makedirs(INTERNAL_FOLDER) def load_config(): if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r', encoding='utf-8') as f: return json.load(f) return {} def save_config(config): ensure_internal_dir_exists() with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(config, f) def format_time(hours, minutes, seconds, milliseconds): return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}.{int(milliseconds):03}" def get_video_duration(input_path): ffprobe_path = os.path.join(FFMPEG_FOLDER, 'ffprobe.exe') if not os.path.exists(ffprobe_path): messagebox.showerror("錯(cuò)誤", "找不到 ffprobe 可執(zhí)行文件") return 0 result = subprocess.run( [ffprobe_path, "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", input_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) try: return float(result.stdout) except ValueError: print("FFprobe輸出:", result.stdout) # 打印ffprobe標(biāo)準(zhǔn)輸出以便調(diào)試 print("FFprobe錯(cuò)誤:", result.stderr) # 打印ffprobe錯(cuò)誤輸出以便調(diào)試 messagebox.showerror("錯(cuò)誤", "無法獲取視頻時(shí)長(zhǎng),請(qǐng)檢查輸入文件") return 0 def trim_video(input_path, output_path, start_time, end_time): ffmpeg_path = os.path.join(FFMPEG_FOLDER, 'ffmpeg.exe') if not os.path.exists(ffmpeg_path): messagebox.showerror("錯(cuò)誤", "找不到 ffmpeg 可執(zhí)行文件") return if start_time == "00:00:00.000" and end_time == "00:00:00.000": messagebox.showerror("錯(cuò)誤", "請(qǐng)至少選擇一個(gè)開始時(shí)間或結(jié)束時(shí)間") return duration = get_video_duration(input_path) if duration == 0: return start_time_seconds = timedelta( hours=int(start_time.split(":")[0]), minutes=int(start_time.split(":")[1]), seconds=int(start_time.split(":")[2].split(".")[0]), milliseconds=int(start_time.split(":")[2].split(".")[1]) ).total_seconds() end_time_seconds = timedelta( hours=int(end_time.split(":")[0]), minutes=int(end_time.split(":")[1]), seconds=int(end_time.split(":")[2].split(".")[0]), milliseconds=int(end_time.split(":")[2].split(".")[1]) ).total_seconds() trim_duration = duration - start_time_seconds - end_time_seconds if trim_duration <= 0: messagebox.showerror("錯(cuò)誤", "剪輯后的持續(xù)時(shí)間小于等于0") return cmd = [ ffmpeg_path, '-ss', start_time, '-i', input_path, '-t', str(trim_duration), '-vcodec', 'copy', '-acodec', 'copy', output_path, '-y' ] print("執(zhí)行FFmpeg命令:", " ".join(cmd)) # 打印命令以便調(diào)試 startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW result = subprocess.run(cmd, startupinfo=startupinfo, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, encoding='utf-8') print("FFmpeg輸出:", result.stdout) # 打印FFmpeg標(biāo)準(zhǔn)輸出 print("FFmpeg錯(cuò)誤:", result.stderr) # 打印FFmpeg錯(cuò)誤輸出 if result.returncode != 0: messagebox.showerror("錯(cuò)誤", f"FFmpeg執(zhí)行失敗: {result.stderr}") def batch_trim_videos(input_files, output_dir, start_time, end_time): if not os.path.exists(output_dir): os.makedirs(output_dir) for input_file in input_files: output_file = os.path.join(output_dir, os.path.basename(input_file)) trim_video(input_file, output_file, start_time, end_time) with open(COMPLETION_FILE, 'w', encoding='utf-8') as f: f.write("老弟已經(jīng)完成了,你開心嗎") print("視頻剪輯完成標(biāo)志已寫入文件") def select_input_files(): files = filedialog.askopenfilenames(filetypes=[("MP4 files", "*.mp4")]) if files: entry_input_files.delete(0, tk.END) entry_input_files.insert(0, f"已選擇 {len(files)} 個(gè)文件") entry_input_files.files = files def select_output_directory(): directory = filedialog.askdirectory() if directory: entry_output_directory.config(state='normal') entry_output_directory.delete(0, tk.END) entry_output_directory.insert(0, directory) entry_output_directory.config(state='disabled', disabledbackground='#d9d9d9', disabledforeground='#000000') label_status.config(text="輸出目錄已選擇。請(qǐng)選擇開始時(shí)間和結(jié)束時(shí)間。") config['output_directory'] = directory save_config(config) def validate_time_format(time_str): try: datetime.strptime(time_str, '%H:%M:%S.%f') return True except ValueError: return False def start_trimming(): input_files = getattr(entry_input_files, 'files', []) output_directory = entry_output_directory.get() start_time = format_time(var_start_hours.get(), var_start_minutes.get(), var_start_seconds.get(), var_start_milliseconds.get()) end_time = format_time(var_end_hours.get(), var_end_minutes.get(), var_end_seconds.get(), var_end_milliseconds.get()) # 驗(yàn)證時(shí)間格式 if not validate_time_format(start_time) or not validate_time_format(end_time): messagebox.showerror("錯(cuò)誤", "時(shí)間格式不正確") return if start_time == "00:00:00.000" and end_time == "00:00:00.000": messagebox.showerror("錯(cuò)誤", "請(qǐng)至少選擇一個(gè)開始時(shí)間或結(jié)束時(shí)間") return if not input_files: messagebox.showwarning("警告", "請(qǐng)選擇輸入文件!") return if not output_directory: messagebox.showwarning("警告", "請(qǐng)選擇輸出目錄!") return threading.Thread(target=batch_trim_videos, args=(input_files, output_directory, start_time, end_time)).start() threading.Thread(target=monitor_completion).start() def monitor_completion(): while not os.path.exists(COMPLETION_FILE): pass with open(COMPLETION_FILE, 'r', encoding='utf-8') as f: message = f.read() print("檢測(cè)到完成標(biāo)志文件,內(nèi)容:", message) speak(message) def speak(text): print("初始化語音引擎") engine = pyttsx3.init() print("語音引擎初始化成功") engine.say(text) print("語音引擎開始說話") engine.runAndWait() print("語音引擎說話結(jié)束") def show_about(): about_window = tk.Toplevel(root) about_window.title("關(guān)于") about_window.geometry("400x300") about_window.resizable(False, False) text = tk.Text(about_window, wrap='word', height=15, width=50) text.insert(tk.END, "n這是一個(gè)用來截取視頻片段的小工具。\n\n功能特性:\n" "1. 支持批量視頻截取\n" "2. 支持自定義開始時(shí)間和結(jié)束時(shí)間\n" "3. 使用 FFmpeg 進(jìn)行視頻處理\n" "4. 提供簡(jiǎn)單易用的圖形用戶界面\n\n" "感謝使用本工具?。ㄊ菬o損快剪哈)") text.config(state='disabled') scrollbar = tk.Scrollbar(about_window, command=text.yview) text.config(yscrollcommand=scrollbar.set) text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) config = load_config() root = tk.Tk() root.title("視頻截取工具") root.geometry("510x250") root.resizable(False, False) root.iconbitmap(ICON_PATH) # 設(shè)置窗口圖標(biāo) # 創(chuàng)建菜單欄 menu_bar = tk.Menu(root) root.config(menu=menu_bar) # 創(chuàng)建菜單 menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade(label="菜單", menu=menu) menu.add_command(label="關(guān)于", command=show_about) tk.Label(root, text="選擇輸入文件:").grid(row=0, column=0, padx=5, pady=5, sticky='e') entry_input_files = tk.Entry(root, width=50) entry_input_files.grid(row=0, column=1, padx=5, pady=5, columnspan=4, sticky='w') entry_input_files.files = [] tk.Button(root, text="瀏覽", command=select_input_files).grid(row=0, column=5, padx=5, pady=5, sticky='w') tk.Label(root, text="選擇輸出目錄:").grid(row=1, column=0, padx=5, pady=5, sticky='e') entry_output_directory = tk.Entry(root, width=50) entry_output_directory.grid(row=1, column=1, padx=5, pady=5, columnspan=4, sticky='w') tk.Button(root, text="瀏覽", command=select_output_directory).grid(row=1, column=5, padx=5, pady=5, sticky='w') if 'output_directory' in config: entry_output_directory.insert(0, config['output_directory']) entry_output_directory.config(state='disabled', disabledbackground='#d9d9d9', disabledforeground='#000000') label_status = tk.Label(root, text="輸出目錄已選擇。請(qǐng)選擇開始時(shí)間和結(jié)束時(shí)間。") else: label_status = tk.Label(root, text="請(qǐng)選擇輸出目錄。") label_status.grid(row=4, column=0, columnspan=6, pady=10) hours = [f"{i:02}" for i in range(24)] minutes_seconds = [f"{i:02}" for i in range(60)] milliseconds = [f"{i:03}" for i in range(1000)] var_start_hours = tk.StringVar(value="00") var_start_minutes = tk.StringVar(value="00") var_start_seconds = tk.StringVar(value="00") var_start_milliseconds = tk.StringVar(value="000") var_end_hours = tk.StringVar(value="00") var_end_minutes = tk.StringVar(value="00") var_end_seconds = tk.StringVar(value="00") var_end_milliseconds = tk.StringVar(value="000") def create_time_frame(root, var_hours, var_minutes, var_seconds, var_milliseconds): frame = tk.Frame(root) ttk.Combobox(frame, textvariable=var_hours, values=hours, width=3).pack(side='left') tk.Label(frame, text="時(shí)").pack(side='left', padx=3) ttk.Combobox(frame, textvariable=var_minutes, values=minutes_seconds, width=3).pack(side='left') tk.Label(frame, text="分").pack(side='left', padx=3) ttk.Combobox(frame, textvariable=var_seconds, values=minutes_seconds, width=3).pack(side='left') tk.Label(frame, text="秒").pack(side='left', padx=3) ttk.Combobox(frame, textvariable=var_milliseconds, values=milliseconds, width=4).pack(side='left') tk.Label(frame, text="毫秒").pack(side='left', padx=3) return frame tk.Label(root, text="開始時(shí)間:").grid(row=2, column=0, padx=5, pady=5, sticky='e') start_time_frame = create_time_frame(root, var_start_hours, var_start_minutes, var_start_seconds, var_start_milliseconds) start_time_frame.grid(row=2, column=1, columnspan=5, padx=5, pady=5, sticky='w') tk.Label(root, text="結(jié)束時(shí)間:").grid(row=3, column=0, padx=5, pady=5, sticky='e') end_time_frame = create_time_frame(root, var_end_hours, var_end_minutes, var_end_seconds, var_end_milliseconds) end_time_frame.grid(row=3, column=1, columnspan=5, padx=5, pady=5, sticky='w') tk.Button(root, text="開始截取", command=start_trimming).grid(row=5, column=0, columnspan=6, pady=10, sticky='ew') root.mainloop()
到此這篇關(guān)于Python進(jìn)行批量剪輯視頻片頭片尾的文章就介紹到這了,更多相關(guān)Python剪輯視頻內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python3 破解 geetest(極驗(yàn))的滑塊驗(yàn)證碼功能
這篇文章主要介紹了python3 破解 geetest(極驗(yàn))的滑塊驗(yàn)證碼功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-02-02深入了解python基于tkinter寫的畫圖項(xiàng)目
這篇文章主要為大家介紹了python基于tkinter寫的畫圖項(xiàng)目,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-12-12Django實(shí)現(xiàn)jquery select2帶搜索的下拉框
最近在開發(fā)一個(gè)web應(yīng)用中需要用到帶搜索功能下拉框,本文實(shí)現(xiàn)Django實(shí)現(xiàn)jquery select2帶搜索的下拉框,感興趣的小伙伴們可以參考一下2021-06-06python疲勞駕駛困倦低頭檢測(cè)功能的實(shí)現(xiàn)
這篇文章主要介紹了python疲勞駕駛困倦低頭檢測(cè),該系統(tǒng)可以檢測(cè)一個(gè)人在開車時(shí)是否困倦,及時(shí)提醒,做到安全隱患排查,對(duì)實(shí)現(xiàn)代碼感興趣的朋友一起看看吧2022-04-04Pytorch轉(zhuǎn)onnx、torchscript方式
這篇文章主要介紹了Pytorch轉(zhuǎn)onnx、torchscript方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-05-05Django框架HttpResponse對(duì)象用法實(shí)例分析
這篇文章主要介紹了Django框架HttpResponse對(duì)象用法,結(jié)合實(shí)例形式分析了Django框架HttpResponse對(duì)象基本原理、功能及響應(yīng)請(qǐng)求的相關(guān)操作技巧,需要的朋友可以參考下2019-11-11python函數(shù)實(shí)例萬花筒實(shí)現(xiàn)過程
這篇文章主要為大家介紹了python函數(shù)實(shí)例萬花筒實(shí)現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06python中?conda?虛擬環(huán)境管理和jupyter內(nèi)核管理
這篇文章主要介紹了python中?conda?虛擬環(huán)境管理和jupyter內(nèi)核管理,文章基于pyhton以及conda的虛擬環(huán)境創(chuàng)建、刪除、jupyter添加、刪除虛擬kernel的方法,需要的朋友可以參考一下2022-04-04python第三方包安裝路徑site-packages下.libs作用詳解
這篇文章主要為大家介紹了python?第三方包安裝路徑?site-packages?下面的以?.libs?結(jié)尾的路徑作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09