fix: SRT 按 cue 解析并按 ID 回填译文,OCR 空帧分段与失败重试

This commit is contained in:
2026-09-11 17:19:25 +08:00
parent 3a612919f7
commit 6eb65e4356
9 changed files with 391 additions and 326 deletions
+66
View File
@@ -0,0 +1,66 @@
"""R06:真实图片清单上的临时网络故障、空帧与跨空白字幕段回归。"""
import json
from pathlib import Path
import pytest
from nodes import subtitle_ocr
from wov_sdk.models import InvokeRequest, InvokeResponse
def test_identical_text_separated_by_blank_is_two_cues():
"""A、空白、A 不能合并,否则字幕会覆盖原本无文字的时段。"""
manifest = [{"time": i * 0.5} for i in range(4)]
assert subtitle_ocr._merge_kept(manifest, ["你好", "你好", "", "你好"]) == [
(0.0, 0.5, "你好"), (1.5, 1.5, "你好")]
@pytest.mark.parametrize("raises", [False, True])
def test_failed_frame_retries_and_resume_preserves_success(monkeypatch, tmp_path, raises):
"""失败帧单独重试,持续故障不存为空;下次只补失败帧,成功空帧不重做。"""
assets = Path(__file__).resolve().parent.parent / "testdata"
image = assets / "ocr_text.png"
empty = assets / "ocr_notext.png"
if not image.is_file() or not empty.is_file():
pytest.skip("缺少真实 OCR 图片")
manifest = tmp_path / "frames.json"
manifest.write_text(json.dumps([{"time": 0, "image_uri": str(empty)},
{"time": 0.5, "image_uri": str(image)}]))
out = tmp_path / "out"
request = InvokeRequest(run_id="r", node_instance_id="", inputs={"frames_manifest": str(manifest)}, output_dir=str(out))
calls = []
broken = True
def invoke(node_id, req):
uri = req.inputs["image_uri"]
calls.append(uri)
if uri == str(image) and broken:
if raises:
raise TimeoutError("timeout")
return InvokeResponse(status="failed", error="timeout")
return InvokeResponse(status="completed", outputs={"text": "" if uri == str(empty) else "你好"})
monkeypatch.setattr("wov_app.registry.invoke", invoke)
response = subtitle_ocr.invoke(request)
assert response.status == "failed"
assert calls.count(str(image)) == 2
assert calls.count(str(empty)) == 1
partial = [json.loads(line) for line in (out / "ocr_partial.jsonl").read_text().splitlines()]
assert partial == [{"frame": 0, "text": "", "status": "completed"}]
assert not (out / "subtitle.srt").exists()
broken = False
calls.clear()
response = subtitle_ocr.invoke(request)
assert response.status == "completed"
assert calls == [str(image)]
assert "你好" in Path(response.outputs["srt_uri"]).read_text()
def test_legacy_empty_checkpoint_is_rechecked(tmp_path):
"""旧版空串可能来自超时,不能当成确认无文字;旧版非空成功结果可复用。"""
(tmp_path / "ocr_partial.jsonl").write_text(
json.dumps({"frame": 0, "text": ""}) + "\n" +
json.dumps({"frame": 1, "text": "你好"}, ensure_ascii=False) + "\n" +
json.dumps({"frame": 2, "text": "", "status": "completed"}) + "\n")
assert subtitle_ocr._load_partial(tmp_path) == {1: "你好", 2: ""}