python+ffmpeg实现视频字幕合并压缩分离裁剪提取片段代码
代码语言:python
所属分类:其他
代码描述:python+ffmpeg实现视频字幕合并压缩分离裁剪提取片段代码,还可将多张图片转成视频。
代码标签: python ffmpeg 视频 字幕 合并 压缩 分离 裁剪 提取 片段 代码
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
# -*- coding: utf-8 -*- import os import sys import re import json import shutil import tempfile import threading import subprocess import queue import time import tkinter as tk from tkinter import ttk, filedialog, messagebox APP_TITLE = "FFmpeg 工具箱 - Win10(优化版)" DEFAULT_VIDEO_EXT = ".mp4" def which_exe(name): return shutil.which(name) def parse_time_str(s): s = s.strip() if not s: raise ValueError("时间为空") if re.match(r"^\d+(\.\d+)?$", s): return float(s) m = re.match(r"^(?:(\d{1,2}):)?(\d{1,2}):(\d{1,2})(?:\.(\d{1,3}))?$", s) if not m: raise ValueError(f"时间格式不合法: {s}") hh = int(m.group(1) or 0) mm = int(m.group(2)) ss = int(m.group(3)) ms = int(m.group(4) or 0) return hh*3600 + mm*60 + ss + ms/1000.0 def seconds_to_hhmmss(t): hh = int(t // 3600) mm = int((t % 3600) // 60) ss = t - hh*3600 - mm*60 return f"{hh:02d}:{mm:02d}:{ss:06.3f}" def escape_subtitles_filter_path(path): p = os.path.abspath(path) p = p.replace("\\", "/") p = p.replace(":", r"\:") p = p.replace("'", r"\'") return p def escape_concat_path(path): # 给 concat 列表使用:用正斜杠并转义引号 p = os.path.abspath(path).replace("\\", "/") return p.replace("'", "'\\''") def human_kbps(val): """将诸如 '2500k' '2500' '2.5M' 统一成 ffmpeg 接受的码率字符串""" s = str(val).strip().lower() if s.endswith("k") or s.endswith("m"): return s if s.endswith("kbps"): return s[:-4] if s.endswith("mbps"): try: num = float(s[:-4]) return f"{int(num*1000)}k" except: return "2500k" # 纯数字默认 kbps if re.match(r"^\d+(\.\d+)?$", s): if "." in s: try: f = float(s) return f"{int(f)}k" except: return "2500k" return f"{s}k" return "2500k" def file_size(path): try: return os.path.getsize(path) except: return -1 class FFmpegGUI(tk.Tk): def __init__(self): super().__init__() self.title(APP_TITLE) self.geometry("1050x800") self.minsize(980, 720) self.ffmpeg_path = which_exe("ffmpeg") or "" self.ffprobe_path = which_exe("ffprobe") or "" self.log_queue = queue.Queue() self.proc = None self.proc_thread = None self._queued_cmds = [] self._tmp_concat_file = None self._tmp_to_delete = [] self._comp_guard_ctx = None # 压缩/字幕大小保护上下文(共用) self._build_ui() # ---------------- UI ---------------- def _build_ui(self): self.notebook = ttk.Notebook(self) self.notebook.pack(fill=tk.BOTH, expand=True, padx=6, pady=6) self.tab_subs = ttk.Frame(self.notebook) self.tab_compress = ttk.Frame(self.notebook) self.tab_demux = ttk.Frame(self.notebook) self.tab_images = ttk.Frame(self.notebook) self.tab_crop = ttk.Frame(self.notebook) self.tab_cut = ttk.Frame(self.notebook) self.tab_settings = ttk.Frame(self.notebook) self.notebook.add(self.tab_subs, text="字幕硬合并") self.notebook.add(self.tab_compress, text="压缩视频(增强)") self.notebook.add(self.tab_demux, text="分离音视频") self.notebook.add(self.tab_images, text="多图转视频") self.notebook.add(self.tab_crop, text="画面裁剪") self.notebook.add(self.tab_cut, text="片段提取") self.notebook.add(self.tab_settings, text="设置") self._build_tab_subs() self._build_tab_compress() self._build_tab_demux() self._build_tab_images() self._build_tab_crop() self._build_tab_cut() self._build_tab_settings() # Log + Control bottom = ttk.Frame(self) bottom.pack(fill=tk.BOTH, expand=False, padx=8, pady=(0,8)) self.run_btn = ttk.Button(bottom, text="开始执行", command=self.on_run_click) self.run_btn.grid(row=0, column=0, padx=4, pady=4, sticky="w") self.stop_btn = ttk.Button(bottom, text="停止", command=self.on_stop_click, state=tk.DISABLED) self.stop_btn.grid(row=0, column=1, padx=4, pady=4, sticky="w") self.clear_btn = ttk.Button(bottom, text="清空日志", command=self.clear_log) self.clear_btn.grid(row=0, column=2, padx=4, pady=4, sticky="w") # Log text log_frame = ttk.Frame(self) log_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=(0,8)) self.log_text = tk.Text(log_frame, height=16, wrap=tk.NONE, font=("Consolas", 10)) ys = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log_text.yview) xs = ttk.Scrollbar(log_frame, orient=tk.HORIZONTAL, command=self.log_text.xview) self.log_text.configure(yscrollcommand=ys.set, xscrollcommand=xs.set) self.log_text.grid(row=0, column=0, sticky="nsew") ys.grid(row=0, column=1, sticky="ns") xs.grid(row=1, column=0, sticky="ew") log_frame.rowconfigure(0, weight=1) log_frame.columnconfigure(0, weight=1) self.status_var = tk.StringVar(value="就绪") self.status = ttk.Label(self, textvariable=self.status_var, anchor="w") self.status.pack(fill=tk.X, side=tk.BOTTOM, padx=8, pady=(0,8)) self.after(100, self._poll_log_queue) def _browse_file(self, var, title="选择文件", filetypes=(("所有文件","*.*"),)): path = filedialog.askopenfilename(title=title, filetypes=filetypes) if path: var.set(path) def _browse_save_video(self, var, title="保存为", defaultext=DEFAULT_VIDEO_EXT): ftypes = (("MP4 视频","*.mp4"), ("MKV 视频","*.mkv"), ("MOV 视频","*.mov"), ("所有文件","*.*")) path = filedialog.asksaveasfilename(title=title, defaultextension=defaultext, filetypes=ftypes) if path: var.set(path) def _browse_folder(self, var, title="选择文件夹"): path = filedialog.askdirectory(title=title) if path: var.set(path) # ------------- Tab: 字幕硬合并 ------------- def _build_tab_subs(self): f = self.tab_subs pad = {"padx": 6, "pady": 6} self.subs_in_video = tk.StringVar() self.subs_in_sub = tk.StringVar() self.subs_out = tk.StringVar() # 默认 CRF 提升到 22,避免体积暴涨 self.subs_crf = tk.IntVar(value=22) self.subs_preset = tk.StringVar(value="medium") self.subs_copy_audio = tk.BooleanVar(value=True) ttk.Label(f, text="输入视频:").grid(row=0, column=0, sticky="e", **pad) ttk.Entry(f, textvariable=self.subs_in_video, width=70).grid(row=0, column=1, sticky="we", **pad) ttk.Button(f, text="浏览", command=lambda:self._browse_file(self.subs_in_video, "选择视频", (("视频文件","*.mp4 *.mkv *.mov *.avi *.flv *.wmv"),("所有文件","*.*")))).grid(row=0, column=2, **pad) ttk.Label(f, text="字幕文件:").grid(row=1, column=0, sticky="e", **pad) ttk.Entry(f, textvariable=self.subs_in_sub, width=70).grid(row=1, column=1, sticky="we", **pad) ttk.Button(f, text="浏览", command=lambda:self._browse_file(self.subs_in_sub, "选择字幕文件", (("字幕文件","*.srt *.ass *.ssa"),("所有文件","*.*")))).grid(row=1, column=2, **pad) ttk.Label(f, text="输出文件:").grid(row=2, column=0, sticky="e", **pad) ttk.Entry(f, textvariable=self.subs_out, width=70).grid(row=2, column=1, sticky="we", **pad) ttk.Button(f, text="浏览", command=lambda:self._browse_save_video(self.subs_out, "保存硬字幕视频为")).grid(row=2, column=2, **pad) ttk.Label(f, text="CRF(0-51):").grid(row=3, column=0, sticky="e", **pad) ttk.Spinbox(f, from_=0, to=51, textvariable=self.subs_crf, width=6).grid(row=3, column=1, sticky="w", **pad) ttk.Label(f, text="preset:").grid(row=3, column=1, sticky="e", padx=(200,6), pady=6) ttk.Combobox(f, values=["ultrafast","superfast","veryfast","faster","fast","medium","slow","slower","veryslow"], textvariable=self.subs_preset, width=10, state="readonly").grid(row=3, column=2, sticky="w", **pad) ttk.Checkbutton(f, text="复制音频(c:a copy)", variable=self.subs_copy_audio).grid(row=4, column=1, sticky="w", **pad) info = "提示: 需要 ffmpeg 带 libass 的 build;字幕是硬字幕,写入画面,无法关闭。" ttk.Label(f, text=info, foreground="#666").grid(row=5, column=0, columnspan=3, sticky="w", **pad) for i in range(3): f.columnconfigure(i, weight=1) # ------------- Tab: 压缩视频(增强) ------------- def _build_tab_compress(self): f = self.tab_compress pad = {"padx": 6, "pady": 6} self.comp_in = tk.StringVar() self.comp_out = tk.StringVar() # 新增参数 self.comp_encoder = tk.StringVar(value="libx264 (H.264 CPU)") self.comp_mode = tk.StringVar(value="CRF/CQP") self.comp_crf = tk.IntVar(value=23) self.comp_preset = tk.StringVar(value="medium") self.comp_tune = tk.StringVar(value="none") self.comp_bitrate = tk.StringVar(value="2500k") self.comp_scale = tk.StringVar(value="保持原分辨率") se.........完整代码请登录后点击上方下载按钮下载查看
网友评论0