feat: VRSub 单体应用(WOV 单机版)初始提交

为视频生成 VR 双眼字幕的单体实现:FastAPI 后端、调度器与全部节点
(提音/转写/翻译/ASS/抽帧/OCR/LLM 过滤)在单进程内运行。

- 节点协议(wov_sdk 数据模型)与分布式版保持一致,预留回退桥梁
- 工作流即数据:DAG 存于 workflows/*.json,模型/链路改动只改数据
- 调度器:拓扑顺序执行、断点续跑(产物重建)、任务暂停/继续
- 抽帧按帧间隔(select 按帧号精确取帧),VLM OCR 与 LLM 过滤使用
  自适应线程池弹性并发,并打印数据处理速度进度日志
- 100% 行覆盖率(pytest --cov-fail-under=100)
This commit is contained in:
2026-08-16 23:58:25 +08:00
commit 4746e0363f
75 changed files with 10969 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
"""真实模型集成测试。
复用 testdata/speech_60s.wav(真实语音 WAV,一次性生成、入库,避免每次
测试从视频提取)。使用真实 faster-whisper 模型端到端验证 whisper 节点的
分块转写与 SRT 生成。本地缺少模型或测试资产时自动跳过;具备条件时必须
执行,作为对假模型单元测试的校准。
约定(见 AGENTS.md「测试与覆盖率」):单元测试允许在模型推理这一 I/O
边界使用返回真实结构的薄桩,但必须配套本集成测试验证真实行为。
"""
from pathlib import Path
import pytest
from nodes.whisper import invoke
from wov_sdk.models import InvokeRequest
# 单体根目录:tests/ 的上一级。
WORKSPACE = Path(__file__).resolve().parent.parent
MODEL_DIR = WORKSPACE / "model" / "faster-whisper-large-v3"
TEST_AUDIO = WORKSPACE / "testdata" / "speech_60s.wav"
@pytest.mark.integration
def test_whisper_real_model_chunked_transcription(tmp_path) -> None:
"""复用 testdata 语音 + 真实模型:分块转写产出真实 SRT,时间不越出素材范围。"""
if not (MODEL_DIR / "model.bin").is_file():
pytest.skip("本地无 faster-whisper-large-v3 模型,跳过真实模型集成测试")
if not TEST_AUDIO.is_file():
pytest.skip("缺少 testdata/speech_60s.wav 测试资产,跳过真实模型集成测试")
response = invoke(
InvokeRequest(
run_id="integration_1",
node_instance_id="",
inputs={"audio_uri": str(TEST_AUDIO)},
params={"language": "ja", "chunk_seconds": 60, "vad_filter": False},
output_dir=str(tmp_path / "out"),
)
)
assert response.status == "completed", response.error
srt_path = Path(response.outputs["srt_uri"])
assert srt_path.is_file()
srt = srt_path.read_text(encoding="utf-8")
time_lines = [line for line in srt.splitlines() if "-->" in line]
# 60s 语音若含可识别内容,则应有字幕,且时间轴不越出素材时长(允许少量超窗)。
if time_lines:
last_end = time_lines[-1].split(" --> ")[1].replace(",", ".")
hours, minutes, seconds = last_end.split(":")
total = int(hours) * 3600 + int(minutes) * 60 + float(seconds)
assert total < 90