feat: 优化学习视频转写与字幕清洗

This commit is contained in:
2026-09-11 14:26:02 +08:00
parent a8fe133aa4
commit eba9163246
12 changed files with 552 additions and 28 deletions
+22 -7
View File
@@ -1,7 +1,7 @@
"""faster-whisper ASR 节点。
单体版中作为进程内节点模块,由调度器直接调用。模型权重默认优先从本地
目录加载,避免从远端下载,仅在本地找不到模型时才回退到远端 large-v3
目录加载,避免从远端下载,仅在本地找不到模型时才回退到远端 large-v2
CUDA 动态库通过 ctypes 在进程内预加载,替代分布式版的 LD_LIBRARY_PATH 注入。
"""
@@ -60,13 +60,13 @@ def _load_cuda_libraries() -> None:
def _local_model_candidates() -> list[Path]:
"""返回本地模型候选目录:单体根目录 model/ 优先,其次 nodes/ 同级 model/。
单体根目录 model/ 对应仓库根下的 model/faster-whisper-large-v3
单体根目录 model/ 对应仓库根下的 model/faster-whisper-large-v2
nodes/ 同级 model/ 允许部署时把权重随代码目录一起携带。
"""
monolith_root = Path(__file__).resolve().parent.parent
return [
monolith_root / "model" / "faster-whisper-large-v3",
monolith_root / "nodes" / "model" / "faster-whisper-large-v3",
monolith_root / "model" / "faster-whisper-large-v2",
monolith_root / "nodes" / "model" / "faster-whisper-large-v2",
]
@@ -75,7 +75,7 @@ def resolve_model_path(
env: dict | None = None,
candidates: list[Path] | None = None,
) -> str:
"""按 参数 > 环境变量 > 本地候选目录 > 远端 large-v3 的顺序解析模型路径。
"""按 参数 > 环境变量 > 本地候选目录 > 远端 large-v2 的顺序解析模型路径。
本地优先是默认行为:只要候选目录存在且包含 model.bin 就使用本地权重,
避免从 Hugging Face 下载;远端下载仅在全部本地候选缺失时作为兜底。
@@ -99,7 +99,7 @@ def resolve_model_path(
# model.bin 是 CTranslate2 权重的必需文件,存在才认为模型完整。
if candidate.is_dir() and (candidate / "model.bin").is_file():
return str(candidate)
return "large-v3"
return "large-v2"
def format_timestamp(seconds: float) -> str:
"""把秒数格式化为 SRT 时间戳,例如 01:00:00,500。"""
@@ -232,6 +232,8 @@ def invoke(request: InvokeRequest) -> InvokeResponse:
# 把真话当非语音剔除(实测 savr-1054 全片仅召回 115 条),开启后强制
# 无 VAD 整段解码(vad_filter=False 且跳过自动 VAD 分析)以召回弱语音,
# 代价是无语音段会产生长时套话幻觉,由下方日语幻觉清洗兜底移除。
# 另:decode_full 救回的弱语音中混有纯语气词碎片(あ/ん?等),由
# short_moan_max_chars(默认 3,0=关闭)参数控制短呻吟整条删除。
# task 默认 transcribe,中文直出模型可传 translate 直接翻译为目标语言。
# condition_on_previous_text 默认 False:长音频下开启会导致重复/漂移,
# 关闭后每个 30s 窗口独立解码,是 faster-whisper 官方建议的长音频方案。
@@ -305,10 +307,23 @@ def invoke(request: InvokeRequest) -> InvokeResponse:
# 剔除**(序号/时间轴/文本全删、剩余重编号),不留下 '-' 占位污染下游
# (占位会渲染进 ASS 成减号、翻译/过滤都要额外处理);短时(≤15s)相同词
# 可能是剧情真实道晚安,保留。见 nodes/subtitle_cleanup.py。
# 其次(2026-09 用户决策):decode_full 也会把呻吟/BGM 混叠的弱语音整段
# 救回,其中混有大量**纯语气词碎片**(あ…/ん?/はぁ…/あ!あ!/んふふ 等),
# 这类噪声影响字幕观感;在此按'全部字符∈纯呻吟集合 且 有效假名≤max_chars'
# 判据**整条删除**remove_short_moan_entries),真实短对话(そこ/やばい/
# ねえ/やだ)天然不命中。仅 decode_full 生效,demo 等 VAD 链路不受影响;
# 参数 short_moan_max_chars 可调(默认 3,设 0 关闭)。
if decode_full:
from nodes.subtitle_cleanup import clean_japanese_hallucinations
from nodes.subtitle_cleanup import (
clean_japanese_hallucinations,
remove_short_moan_entries,
)
body = clean_japanese_hallucinations("\n".join(lines))
body = remove_short_moan_entries(
body,
max_chars=int(request.params.get("short_moan_max_chars", 3)),
)
else:
body = "\n".join(lines)
output_path = output_dir / "transcript.srt"