一、背景介绍
- 对于一端没有字幕外国视频、字幕,在不懂外语的情况下,怎么获取相关内容?
- 作为技术宅,怎么自建搭建一个语音转文字的环境
- 当前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)和计算类型。
python
# 配置日志
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 轻松自定义路径和参数,而无需更改代码。
工具函数
接下来是辅助函数,用于格式化时间戳。
python
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 生成。
音频提取
python
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 True
except Exception as e:
logging.error(f" [错误] 音频提取失败: {e}")
raise
拆解解释:
- 使用 MoviePy 的 AudioFileClip 从视频提取音频,转换为 WAV 格式(采样率 16000Hz,单声道)。
- FFmpeg 参数确保兼容 Whisper 模型的输入要求。
- 异常处理:如果失败,记录错误并抛出异常,防止后续步骤执行。
音频优化
python
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 True
except Exception as e:
logging.error(f" [错误] 音频优化失败: {e}")
try:
os.rename(input_audio_path, output_audio_path)
logging.info(f" 使用原始音频作为替代: {output_audio_path}")
return True
except:
logging.error(" [严重错误] 无法保存音频文件")
return False
拆解解释:
- 使用 Pydub 加载音频,应用音量标准化(目标 -20 LUFS,广播标准)和高通滤波(去除 100Hz 以下低频噪音)。
- 如果优化失败,回退到原始音频,确保流程不中断。
- 这步提升了语音识别准确率,尤其在嘈杂或低音视频中。
语音识别与时间戳对齐
python
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.language
logging.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_PATHS
if not os.path.exists(align_model_path):
logging.warning(f" [警告] 多语言对齐模型路径不存在: {align_model_path}")
logging.info(" 使用未对齐的识别结果")
return result
logging.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 result
logging.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_result
except Exception as e:
logging.error(f" [错误] 语音识别失败: {e}")
traceback.print_exc()
return None
拆解解释:
- 语音识别:使用 Faster-Whisper 转录音频,自动检测语言,支持单词级时间戳。Tqdm 添加进度条,提升用户体验。
- 结果转换:将转录结果格式化为 WhisperX 兼容的字典。
- 时间戳对齐:加载 Wav2Vec2 模型进行精确对齐,支持语言特定模型和缓存(避免重复加载)。
- 回退逻辑:如果模型缺失,使用未对齐结果,确保系统鲁棒性。
- 技术亮点:Beam search (beam_size=5) 提升准确率,单词级时间戳便于后续合并。
字幕合并
python
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']) * 1000
if merge_identical and current_segment['text'] == next_segment['text']:
current_segment['end'] = next_segment['end']
continue
elif 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 文件生成
python
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 True
except Exception as e:
logging.error(f" [错误] 生成SRT文件失败: {e}")
return False
拆解解释:
- 调用合并函数后,遍历片段写入 SRT 文件(序号、时间戳、文本)。
- UTF-8 编码确保多语言兼容。异常处理防止文件写入失败。
主工作流程和模型加载
python
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 False
logging.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 False
generate_srt_file(transcription_data, output_srt)
return True
except Exception as e:
logging.error(f"\n[!!!] 工作流程中断,发生严重错误: {e}")
traceback.print_exc()
return False
finally:
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_time
minutes = 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 块确保清理资源,防止内存泄漏。
- 返回布尔值表示成功,便于批量脚本调用。
程序入口
python
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="离线字幕生成系统")
parser.add_argument("input_dir", help="视频文件目录路径")
args = parser.parse_args()
dir_path = args.input_dir
whisper_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大模型对于最新依赖项和代码是对不上的,需要自己看依赖项目,避免出现依赖地狱
xml
# ============== 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