一、背景介紹
- 對于一端沒有字幕外國視頻、字幕,在不懂外語的情況下,怎么獲取相關內容?
- 作為技術宅,怎么自建搭建一個語音轉文字的環境
- 當前AI技術這么發達? 試試
二、系統設計
- 音頻提取(僅僅是視頻需要該邏輯、本身就是音頻不需要)
- 優化(去掉靜音、噪聲等)
- 語音識別
- 對齊
- 合并和 生成最終文字信息
系統主要依賴 Faster-Whisper(語音識別加速版)、WhisperX(時間戳對齊工具)、以及音頻處理模塊(如 LUFS 標準化和高通濾波)。
Faster-Whisper(語音識別加速版)
Whisper large-v3(推薦,多語言,性能最佳):https://huggingface.co/openai/whisper-large-v3
Whisper medium(代碼中默認,平衡速度和精度):https://huggingface.co/openai/whisper-medium
Whisper medium.en(英語專用,速度更快):https://huggingface.co/openai/whisper-medium.en
Whisper small(輕量,適合低資源設備):https://huggingface.co/openai/whisper-small
Whisper tiny(最小模型,速度最快):https://huggingface.co/openai/whisper-tiny
WhisperX(時間戳對齊工具)
中文對齊模型(wav2vec2-large-xlsr-53-chinese-zh-cn):https://huggingface.co/jonatasgrosman/wav2vec2-large-xlsr-53-chinese-zh-cn
日語對齊模型(wav2vec2-large-xlsr-53-japanese):https://huggingface.co/jonatasgrosman/wav2vec2-large-xlsr-53-japanese
多語言備用模型(wav2vec2-large-xlsr-53):https://huggingface.co/facebook/wav2vec2-large-xlsr-53
音頻處理模塊(如 LUFS 標準化和高通濾波)
系統整體設計思路
系統的設計核心是“端到端”處理視頻到字幕的流程:提取音頻 → 優化信號 → 轉錄文本 → 對齊時間戳 → 合并并輸出 SRT。離線運行是關鍵需求,因此優先使用本地模型和量化加速,避免網絡依賴。
- 設計方案:采用模塊化架構,每個步驟獨立函數,便于調試和擴展。預加載模型減少重復計算,支持 GPU 加速(Torch 和 CUDA)。回退邏輯(如模型缺失時使用未對齊結果)確保魯棒性。
- 思路:結合監督學習(Whisper 的多語言訓練)和自監督表示(Wav2Vec2),實現高精度 ASR。音頻優化先行,以提升識別準確率(噪音減少可降低字錯誤率 WER 達 10-20%)。
Whisper 模型:語音識別的核心算法與原理
Whisper 是 OpenAI 開發的通用語音識別模型,Faster-Whisper 是其加速實現。Whisper 的設計思路是構建一個多任務、多語言的序列到序列(seq2seq)模型,能夠處理轉錄、翻譯和語言識別等多項任務。
原理與設計思路
- 核心思路:Whisper 使用 Transformer 架構,將音頻梅爾譜圖(Mel-spectrogram)作為輸入,輸出文本序列。訓練于 680,000 小時多語言數據,支持 99 種語言,強調魯棒性(處理噪音、口音)。 多任務學習允許模型同時預測時間戳和文本,減少了單獨的時間對齊步驟。
- 算法流程:
- 音頻預處理:將原始音頻轉換為梅爾譜圖。
- 編碼器:提取音頻特征。
- 解碼器:生成文本序列,使用 beam search 優化輸出(beam_size=5 在代碼中)。
- 設計方案:Transformer 的自注意力機制捕捉長距離依賴,適合長音頻。模型大小從 tiny 到 large,平衡準確率和速度。
Faster-Whisper:加速原理與優化方案
Faster-Whisper 是 Whisper 的再實現,使用 CTranslate2 引擎加速推理。
原理與設計思路
- 核心思路:針對 Whisper 的高計算成本(Transformer 的 O(n^2) 復雜度),Faster-Whisper 通過量化、批處理和內核優化加速。設計方案聚焦于生產環境:支持 INT8/FP16 量化,減少內存占用 50%,推理速度提升 4-12 倍。
- 算法流程:
- 模型轉換:將 PyTorch 模型轉換為 CTranslate2 格式。
- 量化:權重從 FP32 降到 INT8,代碼中 COMPUTE_TYPE=“int8” 是默認。
- 并行處理:支持 batch_size>1 和多線程。
- 設計方案:靜態緩存和內核融合減少冗余計算。例如,在長音頻中,分段處理并合并結果。
三、系統實現
系統概述和技術棧
在開始拆解代碼前,讓我們先了解系統的整體架構:
- 輸入:視頻文件(例如 MP4 格式)。
- 輸出:對應的 SRT 字幕文件。
- 關鍵步驟:
- 提取音頻。
- 優化音頻(音量標準化和高通濾波)。
- 語音識別和時間戳對齊。
- 字幕合并。
- 生成 SRT 文件。
- 技術棧:
- 語音識別:Faster-Whisper(Whisper 模型的加速版,支持 INT8 量化以減少內存占用)。
- 時間戳對齊:WhisperX(基于 Wav2Vec2 的對齊工具)。
- 音頻處理:MoviePy(提取音頻)、Pydub(優化音頻)。
- 其他:Torch(GPU 加速)、Tqdm(進度條)、Logging(日志記錄)。
- 環境配置:強制離線模式,通過環境變量避免網絡訪問。
這個系統優化了資源管理(如 GPU 內存清理),并支持配置文件和命令行參數,提高了靈活性。現在,讓我們逐模塊拆解代碼。
全局配置和環境設置
代碼開頭定義了全局參數,這是系統的“控制中心”。這里設置了模型路徑、設備(GPU 或 CPU)和計算類型。
# 配置日志
logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s",handlers=[logging.FileHandler("subtitle_generator.log"), logging.StreamHandler()]
)# 加載配置文件(如果存在)
CONFIG_FILE = "config.json"
config = {}
if os.path.exists(CONFIG_FILE):try:with open(CONFIG_FILE, "r", encoding="utf-8") as f:config = json.load(f)logging.info("已加載配置文件: config.json")except Exception as e:logging.warning(f"加載配置文件失敗: {e},使用默認配置")# 全局參數與模型路徑配置
MODELS_DIR = config.get("models_dir", "/path/to/your/models") # 示例路徑,請替換為實際路徑
os.environ["WHISPERX_MODEL_DIR"] = MODELS_DIR
os.environ["HF_HUB_OFFLINE"] = "1" # 強制使用本地模型
os.environ["TRANSFORMERS_OFFLINE"] = "1" # 禁用transformers的網絡訪問WHISPER_MODEL_PATH = config.get("whisper_model_path", f"{MODELS_DIR}/faster-whisper-medium")
ALIGN_MODEL_PATHS = config.get("align_model_paths", {"zh": f"{MODELS_DIR}/wav2vec2-large-xlsr-53-chinese-zh-cn","ja": f"{MODELS_DIR}/wav2vec2-large-xlsr-53-japanese",
})
OTHER_LANGUAGES_MODEL_PATHS = config.get("other_languages_model_path", f"{MODELS_DIR}/wav2vec2-large-xlsr-53")DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
COMPUTE_TYPE = config.get("compute_type", "int8")
MAX_GAP_MS = config.get("max_gap_ms", 1000)
MERGE_IDENTICAL = config.get("merge_identical", True)
MAX_LENGTH_CHARS = config.get("max_length_chars", 80)
TARGET_LUFS = config.get("target_lufs", -20.0)
HIGH_PASS_FREQ = config.get("high_pass_freq", 100)
拆解解釋:
- 日志配置:使用 Python 的 logging 模塊記錄信息、警告和錯誤,支持控制臺和文件輸出。這有助于調試和監控系統運行。
- 配置文件加載:通過 JSON 文件(如 config.json)動態加載參數,提高可配置性。如果文件不存在,使用默認值。
- 環境變量:設置
HF_HUB_OFFLINE
和TRANSFORMERS_OFFLINE
確保 Hugging Face 模型庫不從網絡下載,強制本地運行。 - 模型路徑:支持特定語言的對齊模型(如中文和日語),并提供多語言備用。
DEVICE
和COMPUTE_TYPE
優化 GPU 使用,INT8 量化可將內存需求降低 50% 以上。 - 其他參數:如字幕合并的最大間隔(1000ms)和字符長度限制(80),這些是經驗值,可根據實際需求調整。
這個部分體現了系統的靈活性:用戶可以通過修改 config.json 輕松自定義路徑和參數,而無需更改代碼。
工具函數
接下來是輔助函數,用于格式化時間戳。
def format_time(seconds):"""將秒數轉換為SRT標準時間格式 (HH:MM:SS,ms)"""hours = int(seconds // 3600)minutes = int((seconds % 3600) // 60)secs = int(seconds % 60)millis = int((seconds - int(seconds)) * 1000)return f"{hours:02}:{minutes:02}:{secs:02},{millis:03}"
拆解解釋:
- 這個函數將浮點秒數轉換為 SRT 所需的字符串格式(如 “00:01:23,456”)。
- 使用整數除法和模運算處理小時、分鐘、秒和毫秒,確保輸出始終是兩位數(使用 f-string 格式化)。
- 這是 SRT 文件的標準要求,避免了手動字符串拼接的錯誤。
核心處理流程
系統的“心臟”部分,包括音頻提取、優化、轉錄、對齊、合并和 SRT 生成。
音頻提取
def extract_and_prepare_audio(video_path, output_audio_path):"""從視頻中提取音頻"""logging.info("[步驟 1/5] 正在從視頻中提取音頻...")try:os.makedirs(os.path.dirname(output_audio_path), exist_ok=True)with AudioFileClip(video_path) as audio_clip:audio_clip.write_audiofile(output_audio_path,codec='pcm_s16le',fps=16000,ffmpeg_params=['-ac', '1'])logging.info(f" 音頻已成功提取并保存至: {output_audio_path}")return Trueexcept Exception as e:logging.error(f" [錯誤] 音頻提取失敗: {e}")raise
拆解解釋:
- 使用 MoviePy 的 AudioFileClip 從視頻提取音頻,轉換為 WAV 格式(采樣率 16000Hz,單聲道)。
- FFmpeg 參數確保兼容 Whisper 模型的輸入要求。
- 異常處理:如果失敗,記錄錯誤并拋出異常,防止后續步驟執行。
音頻優化
def optimize_audio(input_audio_path, output_audio_path, target_lufs=TARGET_LUFS, high_pass_freq=HIGH_PASS_FREQ):"""音頻優化 (不使用AI降噪)"""logging.info("[步驟 2/5] 正在進行音頻優化...")try:audio = AudioSegment.from_wav(input_audio_path)normalized_audio = audio.apply_gain(target_lufs - audio.dBFS)filtered_audio = normalized_audio.high_pass_filter(high_pass_freq)filtered_audio.export(output_audio_path, format="wav")logging.info(f" 優化后的音頻已保存至: {output_audio_path}")return Trueexcept Exception as e:logging.error(f" [錯誤] 音頻優化失敗: {e}")try:os.rename(input_audio_path, output_audio_path)logging.info(f" 使用原始音頻作為替代: {output_audio_path}")return Trueexcept:logging.error(" [嚴重錯誤] 無法保存音頻文件")return False
拆解解釋:
- 使用 Pydub 加載音頻,應用音量標準化(目標 -20 LUFS,廣播標準)和高通濾波(去除 100Hz 以下低頻噪音)。
- 如果優化失敗,回退到原始音頻,確保流程不中斷。
- 這步提升了語音識別準確率,尤其在嘈雜或低音視頻中。
語音識別與時間戳對齊
def transcribe_and_align(audio_path, whisper_model, align_models):"""語音識別與時間戳對齊"""logging.info("[步驟 3/5] 正在進行高精度語音識別與對齊...")try:# 階段1: 語音識別logging.info(f" - 正在使用 Whisper 模型: {WHISPER_MODEL_PATH}")audio = whisperx.load_audio(audio_path)segments, info = whisper_model.transcribe(audio,language=None, # 自動檢測語言task="transcribe",beam_size=5,word_timestamps=True)segments = list(tqdm(segments, desc="轉錄音頻"))result = {"segments": [],"language": info.language}for segment in segments:result["segments"].append({"start": segment.start,"end": segment.end,"text": segment.text,"words": [{"word": word.word,"start": word.start,"end": word.end,"probability": word.probability} for word in segment.words] if segment.words else None})# 檢測語言detected_language = info.languagelogging.info(f" - 檢測到語言: '{detected_language}'")# 階段2: 時間戳對齊logging.info(" - 準備時間戳對齊...")align_model_path = ALIGN_MODEL_PATHS.get(detected_language)if align_model_path is None or not os.path.exists(align_model_path):align_model_path = OTHER_LANGUAGES_MODEL_PATHSif not os.path.exists(align_model_path):logging.warning(f" [警告] 多語言對齊模型路徑不存在: {align_model_path}")logging.info(" 使用未對齊的識別結果")return resultlogging.info(f" - 未找到語言 '{detected_language}' 的本地對齊模型,切換到多語言模型")required_files = ["preprocessor_config.json", "pytorch_model.bin", "config.json", "vocab.json"]missing_files = [f for f in required_files if not os.path.exists(os.path.join(align_model_path, f))]if missing_files:logging.warning(f" [警告] 對齊模型缺少文件: {', '.join(missing_files)}")logging.info(" 使用未對齊的識別結果")return resultlogging.info(f" - 加載本地對齊模型: {align_model_path}")if detected_language in align_models:model_a, metadata = align_models[detected_language]else:model_a, metadata = whisperx.load_align_model(language_code=detected_language,device=DEVICE,model_dir=align_model_path)align_models[detected_language] = (model_a, metadata) # 緩存模型aligned_result = whisperx.align(result["segments"],model_a,metadata,audio,DEVICE,return_char_alignments=False)logging.info(" - 語音識別與對齊完成。")return aligned_resultexcept Exception as e:logging.error(f" [錯誤] 語音識別失敗: {e}")traceback.print_exc()return None
拆解解釋:
- 語音識別:使用 Faster-Whisper 轉錄音頻,自動檢測語言,支持單詞級時間戳。Tqdm 添加進度條,提升用戶體驗。
- 結果轉換:將轉錄結果格式化為 WhisperX 兼容的字典。
- 時間戳對齊:加載 Wav2Vec2 模型進行精確對齊,支持語言特定模型和緩存(避免重復加載)。
- 回退邏輯:如果模型缺失,使用未對齊結果,確保系統魯棒性。
- 技術亮點:Beam search (beam_size=5) 提升準確率,單詞級時間戳便于后續合并。
字幕合并
def merge_subtitles(transcription_result, max_gap_ms=MAX_GAP_MS, merge_identical=MERGE_IDENTICAL, max_length_chars=MAX_LENGTH_CHARS):"""合并字幕的核心函數"""logging.info("[步驟 4/5] 正在進行字幕合并...")segments = []for seg in transcription_result["segments"]:if 'words' in seg and seg['words']:for word_info in seg['words']:text = word_info.get('word', '').strip()if text:segments.append({'start': word_info['start'],'end': word_info['end'],'text': text})else:text = seg.get('text', '').strip()if text:segments.append({'start': seg['start'],'end': seg['end'],'text': text})if not segments:logging.info(" - 沒有檢測到有效字幕內容,跳過合并。")return []logging.info(f" - 合并前字幕條數: {len(segments)}")merged_segments = []if segments:current_segment = segments[0].copy()for i in range(1, len(segments)):next_segment = segments[i]gap_ms = (next_segment['start'] - current_segment['end']) * 1000if merge_identical and current_segment['text'] == next_segment['text']:current_segment['end'] = next_segment['end']continueelif gap_ms <= max_gap_ms and len(current_segment['text']) + len(next_segment['text']) + 1 <= max_length_chars:current_segment['text'] += " " + next_segment['text']current_segment['end'] = next_segment['end']else:merged_segments.append(current_segment)current_segment = next_segment.copy()merged_segments.append(current_segment)logging.info(f" - 合并后字幕條數: {len(merged_segments)}")return merged_segments
拆解解釋:
- 將轉錄結果拆分成單詞或句子級片段。
- 合并邏輯:如果間隔小于 max_gap_ms 且總長度不超過 max_length_chars,則合并;相同文本直接擴展時間。
- 這避免了過碎的字幕,提高可讀性。日志記錄合并前后條數,便于調試。
SRT 文件生成
def generate_srt_file(transcription_result, output_srt_path, max_gap_ms=MAX_GAP_MS, merge_identical=MERGE_IDENTICAL):"""生成SRT字幕文件 (包含合并邏輯)"""merged_transcription = merge_subtitles(transcription_result,max_gap_ms=max_gap_ms,merge_identical=merge_identical)logging.info("[步驟 5/5] 正在生成SRT字幕文件...")try:with open(output_srt_path, "w", encoding="utf-8") as srt_file:for i, segment in enumerate(merged_transcription):start_time = format_time(segment['start'])end_time = format_time(segment['end'])text = segment['text'].strip()if text:srt_file.write(f"{i + 1}\n")srt_file.write(f"{start_time} --> {end_time}\n")srt_file.write(f"{text}\n\n")logging.info(f" 字幕文件已成功生成: {output_srt_path}")return Trueexcept Exception as e:logging.error(f" [錯誤] 生成SRT文件失敗: {e}")return False
拆解解釋:
- 調用合并函數后,遍歷片段寫入 SRT 文件(序號、時間戳、文本)。
- UTF-8 編碼確保多語言兼容。異常處理防止文件寫入失敗。
主工作流程和模型加載
def main_workflow(video_file_path, whisper_model, align_models):"""主工作流程控制器"""if not os.path.exists(video_file_path):logging.error(f"[錯誤] 輸入的視頻文件不存在: {video_file_path}")return Falselogging.info(f"\n{'=' * 50}")logging.info(f"開始處理視頻: {os.path.basename(video_file_path)}")logging.info(f"{'=' * 50}\n")start_total_time = time.time()temp_dir = "temp_audio"os.makedirs(temp_dir, exist_ok=True)base_name = os.path.splitext(os.path.basename(video_file_path))[0]temp_raw_audio = os.path.join(temp_dir, f"temp_{base_name}_raw.wav")temp_final_audio = os.path.join(temp_dir, f"temp_{base_name}_final.wav")output_srt = f"{os.path.dirname(video_file_path)}\\{base_name}.srt"try:extract_and_prepare_audio(video_file_path, temp_raw_audio)optimize_audio(temp_raw_audio, temp_final_audio)transcription_data = transcribe_and_align(temp_final_audio, whisper_model, align_models)if transcription_data is None:logging.error(" [錯誤] 語音識別失敗,無法生成字幕")return Falsegenerate_srt_file(transcription_data, output_srt)return Trueexcept Exception as e:logging.error(f"\n[!!!] 工作流程中斷,發生嚴重錯誤: {e}")traceback.print_exc()return Falsefinally:for temp_file in [temp_raw_audio, temp_final_audio]:if os.path.exists(temp_file):try:os.remove(temp_file)logging.info(f" - 已刪除: {temp_file}")except OSError as e:logging.warning(f" [警告] 無法刪除臨時文件 {temp_file}: {e}")end_total_time = time.time()total_time = end_total_time - start_total_timeminutes = int(total_time // 60)seconds = int(total_time % 60)logging.info(f"\n{'=' * 50}")logging.info(f"所有任務完成! 總耗時: {minutes}分{seconds}秒")logging.info(f"生成的字幕文件: {output_srt}")logging.info(f"{'=' * 50}")if torch.cuda.is_available():torch.cuda.empty_cache()gc.collect()logging.info("顯存和內存清理完成")def load_models():"""預加載模型"""logging.info("預加載 Whisper 模型...")try:whisper_model = WhisperModel(WHISPER_MODEL_PATH, device=DEVICE, compute_type=COMPUTE_TYPE)except Exception as e:logging.warning(f"無法使用 {COMPUTE_TYPE} 量化: {e},嘗試 float16")whisper_model = WhisperModel(WHISPER_MODEL_PATH, device=DEVICE, compute_type="float16")align_models = {}for lang, path in ALIGN_MODEL_PATHS.items():if os.path.exists(path):logging.info(f"預加載 {lang} 對齊模型...")align_models[lang] = whisperx.load_align_model(language_code=lang, device=DEVICE, model_dir=path)return whisper_model, align_models
拆解解釋:
- 模型加載:預加載 Whisper 和對齊模型,支持量化回退,提高批量處理效率。
- 主流程:順序調用各步驟,管理臨時文件和時間計算。Finally 塊確保清理資源,防止內存泄漏。
- 返回布爾值表示成功,便于批量腳本調用。
程序入口
if __name__ == '__main__':parser = argparse.ArgumentParser(description="離線字幕生成系統")parser.add_argument("input_dir", help="視頻文件目錄路徑")args = parser.parse_args()dir_path = args.input_dirwhisper_model, align_models = load_models()videos = [vid for vid in os.listdir(dir_path) if vid.lower().endswith(('.mp4', '.mkv', '.avi'))]for vid in tqdm(videos, desc="處理視頻"):input_video = os.path.join(dir_path, vid)if main_workflow(input_video, whisper_model, align_models):logging.info(f"成功處理: {input_video}")else:logging.error(f"處理失敗: {input_video}")sys.exit(0)
拆解解釋:
- 使用 Argparse 支持命令行輸入目錄。
- 過濾視頻文件,Tqdm 顯示進度。
- 循環調用主流程,支持批量處理。
總結與改進建議
這個系統展示了如何將 AI 模型集成到 Python 腳本中,實現高效的離線字幕生成。優勢包括:離線隱私保護、GPU 優化和可配置性。潛在改進:添加說話人識別(diarization)、支持更多格式,或集成 GUI 接口。
注意:
- 如果你想運行這個系統,確保下載相應模型。
- 注意環境配置,AI大模型對于最新依賴項和代碼是對不上的,需要自己看依賴項目,避免出現依賴地獄
# ============== Core AI Libraries (Version Locked) ==============
numpy<2.0
torch==2.1.2
torchaudio==2.1.2
whisperx==3.1.1
speechbrain==1.0.0
ctranslate2==3.24.0
pyannote.audio==3.1.1
pytorch-lightning<2.0.0# ============== Audio & Video Tools ==============
moviepy
pydub
ffmpeg-python
srt# ============== Other Dependencies ==============
onnxruntime