Files
vrsub/scripts/build_segment_eval.py
cat-shark 8f6083f8cf feat: 全系统统一 Whisper V2 权重并移除 demo 工作流
确认所有运行时引用均使用 V2(V3 已停用),并修复一处真实不一致:

- 工作流数据文件:learn-translate 用 faster-whisper-large-v2、
  zh-direct 用 whisper-large-v2-translate-zh-v0.2-st-ct2(原本即 V2);
- 本地库的 demo 最新版本仍指向 large-v3:workflows/demo.json 早已改为 V2,
  但 seed 对已存在工作流刻意跳过,导致旧库停留在历史上用 V3 保存的定义,
  即本机跑 demo 实际加载 V3 权重。按用户决定移除 demo 工作流及其关联的
  6 个 run、1 个批量任务与 431 条明细(媒体库中已放置的 6 个字幕成品保留);
- nodes/whisper.py 候选与远端兜底本就是 large-v2;
- V3 权重目录保留在盘上仅作对照实验,文档标注为废弃;
  scripts/compare_whisper_v2_vs_v3.py 保留用于对照。

顺带修复与清理:
- src/wov_app/scheduler.py:_file_size 补捕 ValueError(见上一条提交说明
  的真实缺陷,此处为同一批改动);
- .gitignore:data/ 改为 /data/,避免连带忽略 tests/**/data/;
- scripts/*:评测集路径改到 scripts/data/translate_eval/;
- 代码注释与文档同步移除 demo 引用(历史调研文档保留说明性引用)。

验证:全量 477 passed;新库 seed 只创建 3 个 V2 工作流。
2026-09-13 15:41:58 +08:00

106 lines
4.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""从最终中文字幕反查各类"片段窗口",供人工挑选评测用例。
背景与目的
----------
评测翻译模型需要"原文 + 该原文对应的真实含义"的成对基准。本仓库同一部视频
(CJOD-255,2 小时)有两条真实产物:
- 日语原文:run_d386ccf124f7learn-translate)的 whisper ASR
1161 条 cue(约每 1-4 句一条);
- 中文基准:run_479b411f299docr-subtitle)过滤后的烧录字幕 1144 条
——烧录字幕是画面的一部分,也可视为官方/人工字幕,但它**是一条条按屏幕
显示合并的整行**(约 2-8 秒一条),与 whisper 的 cue 切分不同。
因此不能逐条对照。正确口径是**窗口级**:取中文基准一条字幕的时间窗,收集该窗
内覆盖的日语 cue(1-3 条),窗口即为一个评测用例——比较"模型对这几条 cue 的
译文合起来"是否表达了该中文行。本脚本把这类窗口导出,供人工确认与挑选。
用法:
uv run python scripts/build_segment_eval.py --dump-window 1150 # 看某处的窗口细节
uv run python scripts/build_segment_eval.py --export scripts/data/translate_eval/windows.jsonl
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
# 两条真实产物(相对仓库根)。
DEFAULT_JA = PROJECT_ROOT / "data/storage/runs/run_d386ccf124f7/steps/asr/transcript.srt"
DEFAULT_ZH = PROJECT_ROOT / "data/storage/runs/run_479b411f299d/steps/filter/filtered.srt"
def seconds(ts: str) -> float:
"""SRT 时间戳 -> 秒。"""
hours, minutes, rest = ts.split(":")
secs, millis = rest.split(",")
return int(hours) * 3600 + int(minutes) * 60 + int(secs) + int(millis) / 1000
def build_windows(ja_path: Path, zh_path: Path, max_ja: int = 3, max_duration: float = 15.0) -> list[dict]:
"""返回可用评测窗口列表。
窗口 = 一条中文基准字幕 + 其时间窗内的日语 cue 组(1..max_ja 条)。
过滤条件:窗口时长 <= max_duration(避免整段合并的长窗口无法逐句核对)、
cue 数在 1..max_ja 之间(0 条说明 OCR 行无对应日语,>max_ja 说明配对太碎)。
"""
from nodes.srt import parse_srt
ja_cues = parse_srt(ja_path.read_text(encoding="utf-8"))
zh_cues = parse_srt(zh_path.read_text(encoding="utf-8"))
windows: list[dict] = []
for zh in zh_cues:
start, end = seconds(zh.start), seconds(zh.end)
group = [c for c in ja_cues if seconds(c.start) < end and start < seconds(c.end)]
if not 1 <= len(group) <= max_ja or (end - start) > max_duration:
continue
windows.append(
{
"zh_start": zh.start,
"zh_end": zh.end,
"zh_ref": zh.text,
"ja_start": group[0].start,
"ja_end": group[-1].end,
"ja_lines": [c.text for c in group],
"ja_ids": [ja_cues.index(c) + 1 for c in group],
}
)
return windows
def main() -> None:
parser = argparse.ArgumentParser(description="导出窗口级翻译评测候选")
parser.add_argument("--ja", type=Path, default=DEFAULT_JA)
parser.add_argument("--zh", type=Path, default=DEFAULT_ZH)
parser.add_argument("--export", type=Path, default=None, help="写出 windows.jsonl")
parser.add_argument("--dump-window", type=float, default=None, help="打印指定秒附近的窗口")
parser.add_argument("--limit", type=int, default=0)
args = parser.parse_args()
windows = build_windows(args.ja, args.zh)
print(f"可用窗口 {len(windows)} 个(1-3 条日语 cue 且窗口 <=15s")
if args.export:
args.export.parent.mkdir(parents=True, exist_ok=True)
with args.export.open("w", encoding="utf-8") as fh:
for index, item in enumerate(windows, 1):
fh.write(json.dumps({"id": index, **item}, ensure_ascii=False) + "\n")
print(f"已写入 {args.export}")
if args.dump_window is not None:
for item in windows:
if abs(seconds(item["zh_start"]) - args.dump_window) <= 12:
print(json.dumps(item, ensure_ascii=False, indent=1))
for item in (windows if args.limit <= 0 else windows[: args.limit]):
joined = " | ".join(item["ja_lines"])
print(f"{item['zh_start']} JA[{len(item['ja_lines'])}]: {joined!r} -> {item['zh_ref']!r}")
if __name__ == "__main__":
main()