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
+82
View File
@@ -0,0 +1,82 @@
"""FastAPI 应用入口。
负责组装数据库、进程内节点注册表、调度器与静态前端,并在应用生命周期内
管理后台线程的启动与清理。单体版所有节点在同一进程内直接调用。
"""
from __future__ import annotations
# 先加载 .env(含 LLM API Key 等本地配置),再导入读取环境变量的 config。
from dotenv import load_dotenv
load_dotenv()
import os # noqa: E402
from contextlib import asynccontextmanager # noqa: E402
from pathlib import Path # noqa: E402
from fastapi import FastAPI # noqa: E402
from fastapi.middleware.cors import CORSMiddleware # noqa: E402
from fastapi.staticfiles import StaticFiles # noqa: E402
from wov_app import registry
from wov_app.config import DB_PATH, STORAGE_DIR, WORKSPACE_ROOT
from wov_app.db import Database
from wov_app.maintenance import OrphanCleaner
from wov_app.routers import apps, workflows
from wov_app.scheduler import WorkflowScheduler
from wov_app.seed import seed_default_workflows
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期:启动时初始化数据、注册节点和后台服务,退出时回收资源。"""
# 确保数据与存储目录存在,避免首次启动写文件失败。
db = Database(DB_PATH)
STORAGE_DIR.mkdir(parents=True, exist_ok=True)
# 静态注册全部内置节点到进程内注册表(内存态,不落库)。
registry.register_all()
# 默认创建演示工作流,可关闭便于测试。
if os.getenv("WOV_AUTO_SEED", "1") == "1":
seed_default_workflows(db)
scheduler = WorkflowScheduler(db, STORAGE_DIR)
# 调度器默认开启,处理排队中的任务;测试可关闭后手动执行。
if os.getenv("WOV_SCHEDULER_ENABLED", "1") == "1":
scheduler.start()
cleaner = OrphanCleaner(db, STORAGE_DIR)
# 孤儿数据清理默认开启,定时清除死数据;测试可关闭。
if os.getenv("WOV_CLEANUP_ENABLED", "1") == "1":
cleaner.start()
# 共享对象挂到 app.state,路由通过 Depends 延迟获取。
app.state.db = db
app.state.scheduler = scheduler
app.state.cleaner = cleaner
yield
# 退出时先停调度器与清理器,避免残留后台线程。
cleaner.stop()
scheduler.stop()
app = FastAPI(title="VRSub API(单体版)", version="0.1.0", lifespan=lifespan)
# MVP 阶段不做鉴权,允许跨域便于本地调试与静态页面访问。
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(workflows.router)
app.include_router(apps.router)
@app.get("/health")
def health() -> dict:
"""进程存活探针,供部署环境与前端检测后端可用性。"""
return {"status": "ok", "service": "wov-api", "mode": "monolith"}
# 静态前端目录位于仓库根下的 web,由 FastAPI 直接挂载。
FRONTEND_DIR = WORKSPACE_ROOT / "web"
app.mount("/", StaticFiles(directory=FRONTEND_DIR, html=True), name="frontend")