import subprocess import os import json class VideoUtils: @staticmethod def prepare_audio(input_path, output_path): """从视频或音频中提取/转换音频为标准格式 (16kHz, mono, wav)""" # 使用 wav 格式更通用,不需要 libmp3lame 编码器 cmd = [ 'ffmpeg', '-y', '-i', input_path, '-vn', '-acodec', 'pcm_s16le', '-ar', '16000', '-ac', '1', output_path ] try: result = subprocess.run(cmd, check=True, capture_output=True, text=True) return result except subprocess.CalledProcessError as e: error_msg = f"FFmpeg 处理音频失败!\n错误代码: {e.returncode}\n错误输出: {e.stderr}" print(error_msg) # 控制台打印 raise Exception(error_msg) except FileNotFoundError: raise Exception("找不到 FFmpeg 命令。请确保 FFmpeg 已安装并已添加到系统环境变量 PATH 中。") @staticmethod def embed_subtitles(video_path, srt_path, output_path): """将字幕嵌入视频 (硬压)""" # 注意:Windows下路径处理较复杂,ffmpeg 的 subtitles 滤镜需要特殊转义 abs_srt_path = os.path.abspath(srt_path).replace('\\', '/').replace(':', '\\:') cmd = [ 'ffmpeg', '-y', '-i', video_path, '-vf', f"subtitles='{abs_srt_path}'", '-c:a', 'copy', output_path ] try: result = subprocess.run(cmd, check=True, capture_output=True, text=True) return result except subprocess.CalledProcessError as e: error_msg = f"FFmpeg 合成视频失败!\n错误代码: {e.returncode}\n错误输出: {e.stderr}" print(error_msg) raise Exception(error_msg) except FileNotFoundError: raise Exception("找不到 FFmpeg 命令。") @staticmethod def format_timestamp(seconds: float): """将秒转换为 SRT 时间格式 00:00:00,000""" td_hours = int(seconds // 3600) td_mins = int((seconds % 3600) // 60) td_secs = int(seconds % 60) td_msecs = int((seconds - int(seconds)) * 1000) return f"{td_hours:02}:{td_mins:02}:{td_secs:02},{td_msecs:03}" @staticmethod def write_srt(segments, output_path): """生成 SRT 格式文件""" with open(output_path, 'w', encoding='utf-8') as f: for i, segment in enumerate(segments, 1): start = VideoUtils.format_timestamp(segment.start) end = VideoUtils.format_timestamp(segment.end) f.write(f"{i}\n{start} --> {end}\n{segment.text.strip()}\n\n") @staticmethod def parse_srt(srt_content): """简单的 SRT 解析器,返回 segments 列表""" import re segments = [] # 正则匹配 SRT 块 pattern = re.compile(r'(\d+)\n(\d{2}:\d{2}:\d{2},\d{3}) --> (\d{2}:\d{2}:\d{2},\d{3})\n(.*?)(?=\n\n|\n$|$)', re.DOTALL) def time_to_seconds(t_str): h, m, s_ms = t_str.split(':') s, ms = s_ms.split(',') return int(h) * 3600 + int(m) * 60 + int(s) + int(ms) / 1000.0 matches = pattern.findall(srt_content) for m in matches: idx, start_t, end_t, text = m seg = type('Segment', (), { 'start': time_to_seconds(start_t), 'end': time_to_seconds(end_t), 'text': text.strip() }) segments.append(seg) return segments class SettingsManager: SETTINGS_FILE = ".user_settings.json" @staticmethod def save_settings(settings): """保存设置到本地 JSON 文件""" try: with open(SettingsManager.SETTINGS_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, ensure_ascii=False, indent=4) except Exception as e: print(f"保存设置失败: {e}") @staticmethod def load_settings(): """从本地 JSON 文件加载设置""" if os.path.exists(SettingsManager.SETTINGS_FILE): try: with open(SettingsManager.SETTINGS_FILE, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: print(f"加载设置失败: {e}") return {}