把 AGENTS.md(545 行)中的项目知识按性质拆开,只留下对代理的要求: - AGENTS.md(176 行)只写规则:读文档指引、提交许可、等待规则、TDD、 测试规则(模块边界/三段结构/功能覆盖优先)、注释规范、目标运行环境、 PowerShell 规则、文档维护规则; - 项目信息新建 docs/ 专题:architecture、node-protocol、workflows、 configuration、operations、testing、decisions; - README 改为项目索引(定位、快速开始、文档导航、目录、工作流、结论摘要); - 修正旧文档错误:README 的"详细约定见 AGENTS.md"与"100% 行覆盖率" (pytest 已移除该门槛);环境变量表补齐 WOV_AUTO_VAD 等 3 项。 新增 scripts/check_doc_links.py 校验相对链接与锚点,当前 14 个文档全部可达。
10 KiB
10 KiB
运维
孤儿数据清理
应用内置后台清理器(src/wov_app/maintenance.py),按周期自动清理死数据:
- 自动删除:无任务记录的上传/步骤残留目录;COMPLETED 且产物文件全部丢失、 超过宽限期(默认 1 小时)的任务记录(下载已全部 404)。
- 绝不自动删除:FAILED 任务(可重试)、QUEUED/RUNNING 任务、宽限期内的任务、 仍有产物文件的任务。
- 手动删除任务仅通过删除接口(
DELETE /api/runs/{run_id})或管理界面进行。
任务暂停/继续(2026-08)
- 状态机:
QUEUED / RUNNING / PAUSED / COMPLETED / FAILED。排队中或运行中的 任务可暂停(POST /api/runs/{run_id}/pause),PAUSED 可继续 (POST /api/runs/{run_id}/resume→ 恢复 QUEUED)。 - 调度器语义:
next_queued_run只取 QUEUED——PAUSED 任务不会被调度器 自动拾起(修复回归:此前 PAUSED 被拾起后execute_run先置 RUNNING 再检查, 节点循环读到的是刚改的 RUNNING,"暂停检查"永远不成立 → 任务被复活继续跑, 表现为"点击暂停反而开始任务")。execute_run以 PAUSED 进入时直接返回保持暂停, 必须用户显式 resume(PAUSED → QUEUED)后才真正执行;运行中被暂停的任务在每个 节点边界检查状态停下保持 PAUSED(当前节点执行完后才停);继续时从产物表 (restore_run_outputs,剥去"节点ID."前缀还原输出名)重建已完成节点的输出, 跳过已完成节点断点续跑,最后补做 final_outputs 收尾。 - 重启恢复:进程被杀/重启时遗留的 RUNNING 任务在启动时被
recover_interrupted_runs恢复为 QUEUED(保留产物),调度器自动断点续跑; PAUSED 任务保持不变,等待显式 resume。 - 节点级断点(subtitle-ocr):OCR 每帧完成后立即把
{frame, text}追加到steps/ocr/ocr_partial.jsonl(多线程下加锁串行化)。invoke 启动时读取存档, 只对未处理帧调用 vlm-ocr,存档文本与新增结果合并后组装 SRT——2 小时视频级 OCR 任务中断/重启后不重复已处理帧,产物与一次跑完逐字节一致。 - 节点内暂停响应(subtitle-ocr):暂停接口(
POST /api/runs/{run_id}/pause) 除置 PAUSED 外还向 run 根目录写入paused.flag;OCR 工作线程逐帧检查该 信号,存在即立即中止(不 OCR、不入存档,恢复时重跑该帧),invoke 返回 failed;调度器捕获节点异常时若任务已是 PAUSED 则保持 PAUSED 不标 FAILED, resume 时清除信号并从断点存档继续——点击暂停后 OCR 秒级停下,不再等整个 节点跑完。继续/重试接口与调度器执行前都会清理残留信号。 - 前端:任务管理页为 QUEUED/RUNNING 提供"暂停"、PAUSED 提供"继续"按钮。
进度日志(数据处理速度)
- 调度器:每节点完成打印"任务 X 进度 i/N 节点: Y 耗时 Zs, 运行累计 Ws";
- subtitle-ocr:
OCR 进度 X/Y 帧 (Z 帧/s, 平均 Ws/帧, 线程 N/M); llm-filter:字幕判定进度 X/Y 条 (Z 条/s, 平均 Ws/条, 线程 N/M)——W为最近窗口平均单任务耗时(窗口未满时回退累计平均),N为当前目标 线程数、M为pool_max_workers上限,用于判断多线程是否因单次处理过慢 (窗口平均 ≥pool_fast_threshold)而未扩容(线程池on_progress回调, 每任务完成触发); - whisper:分块转写打印"分块 X/Y 完成 offset=... 耗时 Zs (Nx 实时, 累计 ...s)";
- frame-extract:ffmpeg
-progress输出解析frame=N,打印"抽帧进度 X/Y 帧 (Z 帧/s)"。
文件夹批量处理
本地版核心能力:不把视频上传到工作目录,直接读取用户所选文件夹下的全部
视频,逐个执行所选流水线。入口为批量处理页(web/batch.html,导航"批量处理"),
后端为 src/wov_app/batch.py 的 BatchWorker(单线程轮询线程,处理
source=batch 的运行,与主调度器互不抢占)与 routers/batch.py。
- 路径选择:批量页点击"选择文件夹…"按钮弹出目录树选择器(懒加载),选完
回填只读路径框。浏览器拿不到所选文件夹的绝对路径,因此由本地后端提供目录
浏览:
GET /api/batch/roots(Windows 盘符 / POSIX 根 + 家目录)、GET /api/batch/dirs?path=(列直接子目录,隐藏目录过滤;不存在/不可读返回 空列表不报 500)。只暴露目录名,不返回文件内容。 - 创建任务时一次性定位(2026-09 起):
POST /api/batch/jobs {folder, workflow_id, recursive}只扫描一次文件夹并把每个视频登记为batch_videos明细(PENDING/RUNNING/PAUSED/COMPLETED/FAILED/SKIPPED)。视频所在目录 (视频旁)若已存在文件名含视频名的字幕文件(.srt/.ass/.ssa/.vtt,list_sidecar_subtitles判定,如movie.CN.srt、movie.CN_dual_eye.ass), 说明该视频已有字幕,创建即记 SKIPPED——不为它触发任何流水线。运行时BatchWorker只消费这批已定位的明细,不再重新扫描文件夹(运行期间新增/ 删除的视频不会改变本次任务的范围)。校验失败(文件夹不存在/未发布工作流/ 无版本/一个视频都没有)返回 422。 - 产物放在视频旁:每个视频处理完成后,把工作流
final_outputs对应的最终 产物文件(字幕流水线即中文.srt与双目.ass)复制一份到视频所在目录, 与 .mp4 放在一起(_place_products)。文件名对齐媒体库既有约定:中文字幕 存为<视频名>.CN.srt、双目字幕存为<视频名>.CN_dual_eye.ass(稳定无时间戳,_sidecar_product_name映射,其余扩展名产物保留原文件名;同名目标直接覆盖)。 文件名含视频主名,下次批量扫描会命中"已有字幕"规则直接跳过该视频。 - 成品放置校验(审查 R03):先预检全部
final_outputs对应的记录与文件, 缺任一项即失败,不开始覆盖视频旁成品;全部齐备后逐文件原子替换。复制失败时 视频记 FAILED,保留 run、工作空间和已放置的完整成品,供修复后幂等重试。 原子替换保证单文件完整,不代表多个成品的跨文件事务或断电持久性。详见 代码审查问题跟踪.md。 - 过程文件清理(2026-09 起):视频收尾完成后删除该视频的整个工作空间与
run 记录(音频/分块/帧图/节点产物不留残),防止媒体库把切片数据当视频入库。
工作空间位于应用私有目录
data/storage/batch/<job_id>/<bv_id>/(不再放视频同名文件夹),与用户视频库天然隔离;暂停/失败的视频保留工作空间 以便断点续跑。per-video 的WorkflowScheduler实例以该目录为 storage—— 完整复用 DAG 拓扑执行、产物表登记与断点续跑逻辑。 - 暂停/继续:
POST /api/batch/jobs/{id}/pause把任务置 PAUSED 并暂停当前 run(写paused.flag;whisper 分块间检查、OCR 逐帧检查后中止,当前节点 执行完才停);resume恢复 QUEUED,引擎从断点继续——PAUSED 视频的 run 显式 resume 后从产物表续跑,未开始的视频接着处理。重启进程后 RUNNING 残留 run 由recover_interrupted_runs恢复,暂停的继续处理。 - 失败容错:单个视频失败(节点失败/文件缺失)记为 FAILED,批量任务继续 处理后续视频,结束后统计 done/failed;DAG 解析/任务级异常把任务置 FAILED。 重跑保留产物(2026-08,修复 run_e2b74e89e232 实测):FAILED 视频重新处理 时不再 reset_run 清空产物记录,而是保留已完成节点的 artifacts 恢复 QUEUED, execute_run 从产物表跳过已完成节点、只重跑失败节点——extract/ocr 等长耗时 成果不浪费;配合 llm-filter/OCR 的节点级断点存档,失败节点自身也只重判未完成 条目。前端对"部分失败"(COMPLETED 且 failed>0)用红色徽章醒目标示。
- 完成任务判定(2026-09 修复):
_run_job置 COMPLETED 前校验全部非 SKIPPED 视频都已结束(无 PENDING/PAUSED 残留),否则保持 RUNNING 交引擎 下一轮续跑——修复僵尸状态:引擎串行处理到 9.9GB 大视频时中断,_run_job无条件收尾把任务置 COMPLETED,留下"N 个 PENDING 待处理却已完成"的假完成 (batch_969fabe74b83 等 3 个任务实测:遗留的 10 个 PENDING 完全相同且卡在 kiwvr-887 大文件前)。崩溃恢复:重启时除recover_interrupted_runs外, 新增recover_interrupted_batch_jobs把 RUNNING 的批量任务恢复为 QUEUED (否则停在 RUNNING 的批量任务永远不会被next_queued_batch_job再次拾起, 未处理完的 PENDING 永久残留)。历史僵尸数据修复脚本见scripts/fix_zombie_batch_jobs.py(把误标 COMPLETED 的任务置回 QUEUED 续跑)。 - 产物下载:
GET /api/batch/jobs/{id}/videos/{vid}/download?alias=<文件名>解析并返回视频旁的字幕文件;旧版batch.done.json完成标记里的语义别名 (位于旧 work_dir)仍兼容可下载。详情/创建响应里每个视频的finals合并上述 两处来源。 - 孤儿清理保护:
source=batch的运行跳过自动清理——其 run 位于私有storage/batch/...下,普通孤儿逻辑会误判删除,且_remove_run还会删除input_uri的父目录(用户的整个视频文件夹)。详见 代码审查问题跟踪.md。 - 删除任务:
DELETE /api/batch/jobs/{id}清理数据库记录(含关联 run)与 应用私有工作空间残留;视频旁已放置的产物属于用户数据,保留不删。 - 环境变量:
WOV_BATCH_ENABLED(默认 1)、WOV_BATCH_INTERVAL_SECONDS(默认 1.0),完整列表见 configuration.md。
相关文档
- 环境变量全表:configuration.md
- 产物命名与 finalization 规则:workflows.md
- 历史缺陷与修复记录:代码审查问题跟踪.md