6 Commits
Author SHA1 Message Date
cat-shark 2c8bbb6469 feat: 转写支持静音段幻觉抑制参数(HST)
无 VAD 的 decode_full 会在无语音段"编"出字幕(`こんにちは`/`おはようございます`/
`東京都交通局8800形電車`)。实测这类幻觉与"呻吟间隙里的真实短台词"在
`no_speech_prob`、`avg_logprob`、silero VAD 与音频能量四个维度上都不可分,
只能用 faster-whisper 自带的 `hallucination_silence_threshold`(HST)抑制:
怀疑幻觉时跳过超过阈值的静音部分(需 `word_timestamps=True`)。

- `nodes/whisper.py` 透传 `word_timestamps` / `hallucination_silence_threshold` /
  `no_speech_threshold` / `log_prob_threshold` / `compression_ratio_threshold`,
  未配置的键不传,保持 faster-whisper 默认值与改造前行为。
- `learn-translate` 默认 HST=2.0 + word_timestamps:同一片头 120 秒无对话段由 15 条
  字幕降到 4 条且无套话幻觉残留,呻吟段基本保留;代价转写约慢 1.8×。
- 实测数据与取舍记录见 docs/workflows.md#decode_full-与幻觉呻吟清洗。
2026-09-18 23:41:32 +08:00
cat-shark 4d2823c005 fix: 短时重复伪影也整条删除(判据补重复次数上限)
真实产物里出现 3.7 秒的 cue 被同一个假名填满 111 次(`は`×111),此前判据要求
"时长 ≥15 秒"才删,于是它漏到翻译层,最终变成 56 个"哈"进中文成品字幕。

- 判据改为:重复段占正文 ≥70% 且重复 ≥6 次,并且**时长 ≥15 秒或重复 ≥20 次**。
- 短条走"极端重复"档:真实呻吟的重复次数实测 ≤15(ぇ×15 占 3.2 秒),不会误删。
- 测试数据取自真实产物(A 的日语转写里那条 112 字短伪影)。
2026-09-18 23:41:32 +08:00
cat-shark 3f4478523e fix: whisper 重复伪影整条删除,避免翻译层空响应失败
whisper 在单个窗口内卡住重复时会把一个单元写满整条 cue(实测 30 秒整、重复
74–446 次,如 `チン`×111)。这类正文会让下游 LLM 跟着循环、把输出预算耗在思考上,
最终 `content` 返回空串并报 `translation alignment failed … Expecting value:
line 1 column 1 (char 0)`;它也可能直接渲染成超长字幕行。

- `nodes/subtitle_cleanup.py` 新增 `remove_repetition_entries`:**纯模式判据、无字符
  词表**——展示时长 ≥15s 且同一 1–6 字单元连续重复 ≥6 次且覆盖正文 ≥70% 的 cue 整条
  删除;真实短促呻吟(`ぇ`×15、`ああああああ`)靠时长区分,零误删。
- `nodes/whisper.py` 在转写产出处应用该清洗(VAD 开关都生效,它与套话幻觉无关)。
- 真实数据验证:本地全部真实转写 3605 条 cue 只删 5 条 30 秒伪影;对照实验同批次
  伪影截短后 5/5 成功、原样 1/5。
2026-09-18 22:23:25 +08:00
cat-shark dcdc5e8604 feat: 批量分块流水线、本地模型显存让渡与任务列表分工
批量引擎改为「分块流水线」:视频按 WOV_BATCH_STAGE_GROUP_SIZE(默认 8)分组,
组内按 DAG 拓扑序跑完全部视频(全部 extract → 全部 ASR → 全部翻译 → 全部 ASS)
再进入下一组,本地模型每组只加载一次、卸载一次,而不是每个视频来回加载卸载;
产物仍按组增量落到视频旁。调度器新增 execute_run(run_id, stop_after=节点):
该节点完成后任务保持 RUNNING 不收尾,下一次调用从产物表跳过已完成节点继续,
用于实现阶段边界。

- nodes/llm.py:翻译节点结束释放本机 Ollama 显存(node 参数 unload_after >
  LLM_UNLOAD_AFTER > 本机 loopback 端点默认卸载,云端端点不卸载;卸载失败只告警),
  新增 keep_model.flag 语义(阶段内保持常驻)与 release_local_model();
  新增节点内暂停(按批 20 行检查 paused.flag,抛 PauseRequested,调度器保持 PAUSED)。
- src/wov_app/batch.py:分组阶段执行与阶段末统一释放显存;失败视频只在它失败
  节点的那个阶段重试(避免 LLM 已常驻时重跑 ASR 抢显存);任务没有明细时保持
  QUEUED 等登记完成、仍有未完成视频时置回 QUEUED 自愈(原先留 RUNNING 会卡死:
  引擎只拾取 QUEUED,任务停在“运行中但没人推进”);无失败视频时删除任务级空目录;
  每个阶段开始前清理 paused.flag / keep_model.flag,避免强杀残留影响后续阶段。
- src/wov_app/config.py:新增 WOV_BATCH_STAGE_GROUP_SIZE(设为 1 即旧的每视频全链路)。
- 任务列表与批量页分工:GET /api/runs 默认排除 source=batch(一个批量任务会产生
  N 条单视频 run,会把 20 条窗口占满;且任务管理页的暂停/重试/删除对批量 run
  语义不成立),需要排查时用 include_batch=1;作为补偿批量页详情新增阶段列
  (阶段 i/N · 中文标签,由该视频 run 的 current_node_id 在 DAG 拓扑序中的位置
  推导,节点类型映射中文标签)。阶段只有节点边界粒度,句级进度不落库、只在日志。
- 顺带纳入此前未提交的批量僵尸状态恢复:recover_interrupted_batch_jobs 除 RUNNING
  外也把「COMPLETED 但仍含未结束视频」的任务置回 QUEUED;fix_zombie_batch_jobs.py
  改为按条件扫描并支持 --apply 预览;批量页明细只列本批真正处理过的视频。

测试新增/更新:分块流水线调用顺序(组内按节点跑完再下一组)、每组只释放一次模型、
阶段内保持常驻标志、翻译按批暂停、失败视频不跨阶段推进、任务无明细/中途登记视频时
置回 QUEUED、任务工作空间与残留信号清理、任务列表默认过滤批量 run、详情阶段字段、
前端阶段列渲染;全量 507 passed(唯一失败为既有素材缺失的 integration 用例)。
2026-09-18 10:31:52 +08:00
cat-shark 7a7212f70c docs: 注释规范要求精简可读,并清理生产代码中的历史叙事
AGENTS.md 的注释规范新增三节可执行约束:

- 只写代码真实逻辑:注释只回答"做什么"与"为什么必须这么做",禁止写决策/
  修改时间、历史版本对比、实测数据与实验结论、事故与缺陷编号(run_xxxx /
  batch_xxxx / R01 等)——这些属 docs/decisions.md 与审查跟踪文件;当前生效
  的约束可以写,但不附带它何时因何变成这样。
- 精简可读:单段连续注释不超过 3 行;docstring 一句话概括职责,不重复函数名
  已表达的信息;不写逐行翻译代码的废话注释,只在非显然处(业务规则、边界、
  易错点、外部约束)加注。
- 覆盖范围:测试注释只说明验证什么行为,回归用例可保留一句溯源;并明确
  参数说明应写在**参数读取处**附近,而不是把多个参数的解释堆在离使用位置
  很远的注释块里。

按此清理生产代码(注释净减 70 行,18 个文件),典型处理:

- nodes/whisper.py:删掉堆在一起、含"用户 2026-08 决定 / 实测 savr-1054"
  等叙事的参数块,把各参数说明移到各自的读取处与 model.transcribe 调用处;
- nodes/llm_filter.py、nodes/subtitle_cleanup.py:模块 docstring 去掉英文
  背景叙事与条数统计,保留"默认只跑规则层""整条删除而非 '-' 占位"等当前
  行为;
- src/wov_app/{batch,db,scheduler}.py 与 routers:去掉 batch_xxx/run_xxx 事故
  编号与"修复前……"对比,改为一句"否则会出现什么问题";
- nodes/ass.py、frame_extract.py:去掉废弃值对比与日期,保留判据本身。

安全验证:用 AST 对比(剥离 docstring 后比较语法树)确认 18 个文件**零逻辑
变更**;`nodes/proper_nouns.py` 的规则表 reason 字段会注入 LLM 提示词,属于
数据而非注释,已恢复原值。全量测试 476 passed。
2026-09-13 16:37:49 +08:00
cat-shark 8a715a8064 test: 按模块重写测试代码,删除旧平铺结构
按"测试规则"重写 tests/:一个模块一个目录、用例按数据→过程→验证三段书写、
不保留全局 conftest.py、测试过程只调用真实生产代码。

结构(73 个文件、30 个模块目录、477 用例):
- tests/nodes/  15 个模块目录(srt/whisper/ass/ffmpeg/frame_extract/vlm/
  subtitle_ocr/llm/llm_filter/subtitle_cleanup/subtitle_correction/
  proper_nouns/adaptive_pool/vad_profiler/echo);
- tests/app/    11 个模块目录(db/scheduler/batch/maintenance/registry/seed/
  storage/config/logging/main/routers 三组 API);
- tests/sdk/test_models、tests/web/test_crop、tests/shared(公共设施)。

测试数据随模块目录入库(tests/**/data/),删除根级 testdata/;.gitignore
的 data/ 改为 /data/,否则会连带忽略 tests/**/data/ 导致测试数据无法入库。

顺带发现并修复三个真实缺陷:
- nodes/srt.py:相邻条目缺少空行时把下一条时间轴吞进正文(静默错位),
  改为正文行遇时间戳行即报错;
- src/wov_app/scheduler.py:_file_size 只捕获 OSError,含 \x00 的产物 URI
  抛 ValueError 导致任务误判失败,改为同时捕获;
- nodes/subtitle_correction.py:生产代码依赖测试包解析 SRT,
  改用生产模块 nodes/srt.py。

真实模型/服务集成测试按外部状态跳过:新增 tests/shared/gpu_memory.py
(运行时探测显存、CUDA OOM 转跳过)与 tests/shared/llm_service.py
(无 Key / 余额 / 限流转跳过)。全量 477 passed。
2026-09-13 15:40:56 +08:00