"""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: ""}