Files
vrsub/docs/operations.md
T
cat-shark dcdc5e8604 feat: 批量分块流水线、本地模型显存让渡与任务列表分工
批量引擎改为「分块流水线」:视频按 WOV_BATCH_STAGE_GROUP_SIZE(默认 8)分组,
组内按 DAG 拓扑序跑完全部视频(全部 extract → 全部 ASR → 全部翻译 → 全部 ASS)
再进入下一组,本地模型每组只加载一次、卸载一次,而不是每个视频来回加载卸载;
产物仍按组增量落到视频旁。调度器新增 execute_run(run_id, stop_after=节点):
该节点完成后任务保持 RUNNING 不收尾,下一次调用从产物表跳过已完成节点继续,
用于实现阶段边界。

- nodes/llm.py:翻译节点结束释放本机 Ollama 显存(node 参数 unload_after >
  LLM_UNLOAD_AFTER > 本机 loopback 端点默认卸载,云端端点不卸载;卸载失败只告警),
  新增 keep_model.flag 语义(阶段内保持常驻)与 release_local_model();
  新增节点内暂停(按批 20 行检查 paused.flag,抛 PauseRequested,调度器保持 PAUSED)。
- src/wov_app/batch.py:分组阶段执行与阶段末统一释放显存;失败视频只在它失败
  节点的那个阶段重试(避免 LLM 已常驻时重跑 ASR 抢显存);任务没有明细时保持
  QUEUED 等登记完成、仍有未完成视频时置回 QUEUED 自愈(原先留 RUNNING 会卡死:
  引擎只拾取 QUEUED,任务停在“运行中但没人推进”);无失败视频时删除任务级空目录;
  每个阶段开始前清理 paused.flag / keep_model.flag,避免强杀残留影响后续阶段。
- src/wov_app/config.py:新增 WOV_BATCH_STAGE_GROUP_SIZE(设为 1 即旧的每视频全链路)。
- 任务列表与批量页分工:GET /api/runs 默认排除 source=batch(一个批量任务会产生
  N 条单视频 run,会把 20 条窗口占满;且任务管理页的暂停/重试/删除对批量 run
  语义不成立),需要排查时用 include_batch=1;作为补偿批量页详情新增阶段列
  (阶段 i/N · 中文标签,由该视频 run 的 current_node_id 在 DAG 拓扑序中的位置
  推导,节点类型映射中文标签)。阶段只有节点边界粒度,句级进度不落库、只在日志。
- 顺带纳入此前未提交的批量僵尸状态恢复:recover_interrupted_batch_jobs 除 RUNNING
  外也把「COMPLETED 但仍含未结束视频」的任务置回 QUEUED;fix_zombie_batch_jobs.py
  改为按条件扫描并支持 --apply 预览;批量页明细只列本批真正处理过的视频。

测试新增/更新:分块流水线调用顺序(组内按节点跑完再下一组)、每组只释放一次模型、
阶段内保持常驻标志、翻译按批暂停、失败视频不跨阶段推进、任务无明细/中途登记视频时
置回 QUEUED、任务工作空间与残留信号清理、任务列表默认过滤批量 run、详情阶段字段、
前端阶段列渲染;全量 507 passed(唯一失败为既有素材缺失的 integration 用例)。
2026-09-18 10:31:52 +08:00

14 KiB
Raw Blame History

运维

孤儿数据清理

应用内置后台清理器(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 进入时直接返回保持暂停, 必须用户显式 resumePAUSED → QUEUED)后才真正执行;运行中被暂停的任务在每个 节点边界检查状态停下保持 PAUSED(当前节点执行完后才停);继续时从产物表 (restore_run_outputs,剥去"节点ID."前缀还原输出名)重建已完成节点的输出, 跳过已完成节点断点续跑,最后补做 final_outputs 收尾。
  • 重启恢复:进程被杀/重启时遗留的 RUNNING 任务在启动时被 recover_interrupted_runs 恢复为 QUEUED(保留产物),调度器自动断点续跑; PAUSED 任务保持不变,等待显式 resume。
  • 节点级断点(subtitle-ocrOCR 每帧完成后立即把 {frame, text} 追加到 steps/ocr/ocr_partial.jsonl(多线程下加锁串行化)。invoke 启动时读取存档, 只对未处理帧调用 vlm-ocr,存档文本与新增结果合并后组装 SRT——2 小时视频级 OCR 任务中断/重启后不重复已处理帧,产物与一次跑完逐字节一致。
  • 节点内暂停响应(subtitle-ocr:暂停接口(POST /api/runs/{run_id}/pause 除置 PAUSED 外还向 run 根目录写入 paused.flagOCR 工作线程逐帧检查该 信号,存在即立即中止(不 OCR、不入存档,恢复时重跑该帧),invoke 返回 failed;调度器捕获节点异常时若任务已是 PAUSED 则保持 PAUSED 不标 FAILED, resume 时清除信号并从断点存档继续——点击暂停后 OCR 秒级停下,不再等整个 节点跑完。继续/重试接口与调度器执行前都会清理残留信号。
  • 前端:任务管理页为 QUEUED/RUNNING 提供"暂停"、PAUSED 提供"继续"按钮。

进度日志(数据处理速度)

  • 调度器:每节点完成打印"任务 X 进度 i/N 节点: Y 耗时 Zs, 运行累计 Ws"
  • subtitle-ocrOCR 进度 X/Y 帧 (Z 帧/s, 平均 Ws/帧, 线程 N/M) llm-filter字幕判定进度 X/Y 条 (Z 条/s, 平均 Ws/条, 线程 N/M) ——W 为最近窗口平均单任务耗时(窗口未满时回退累计平均),N 为当前目标 线程数、Mpool_max_workers 上限,用于判断多线程是否因单次处理过慢 (窗口平均 ≥ pool_fast_threshold)而未扩容(线程池 on_progress 回调, 每任务完成触发);
  • whisper:分块转写打印"分块 X/Y 完成 offset=... 耗时 Zs (Nx 实时, 累计 ...s)"
  • frame-extractffmpeg -progress 输出解析 frame=N,打印"抽帧进度 X/Y 帧 (Z 帧/s)"。

文件夹批量处理

本地版核心能力:不把视频上传到工作目录,直接读取用户所选文件夹下的全部 视频,逐个执行所选流水线。入口为批量处理页(web/batch.html,导航"批量处理"), 后端为 src/wov_app/batch.pyBatchWorker(单线程轮询线程,处理 source=batch 的运行,与主调度器互不抢占)与 routers/batch.py

  • 路径选择:批量页点击"选择文件夹…"按钮弹出目录树选择器(懒加载),选完 回填只读路径框。浏览器拿不到所选文件夹的绝对路径,因此由本地后端提供目录 浏览:GET /api/batch/rootsWindows 盘符 / 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.srtmovie.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 拓扑执行、产物表登记与断点续跑逻辑。
  • 任务级工作空间清理:任务全部完成且无失败视频时,连任务级目录 storage/batch/<job_id>/ 一并删除(每个视频的工作空间已在收尾时各自删完, 任务级目录只剩空壳);有失败视频时保留(它们的中间产物供断点重试)。 paused.flag/keep_model.flag 在每个阶段开始前清理,避免进程被强杀后的 残留影响后续阶段。
  • 任务管理与批量页的分工:批量 runsource=batch默认不出现在任务管理页 GET /api/runs 默认排除,排查时用 ?include_batch=1)——一个批量任务会产生 N 条单视频 run,混进 20 条窗口会把用户自己提交的任务挤出去,且任务管理页的 暂停/继续/重试/删除对批量 run 语义不成立(暂停会被引擎下一次断点续跑静默复位, 删除被 422 拒绝)。作为补偿,批量页详情表补阶段列:阶段 2/4 · 转写,由该视频 run 的 current_node_id 在 DAG 拓扑序中的位置推导、节点类型映射中文标签。 粒度限制:progress 只有节点边界粒度,句级进度(转写分块、翻译批次、OCR 帧) 不落库、只在控制台日志里。
  • 分块流水线执行(本地模型只加载一次):批量引擎把待处理视频按 WOV_BATCH_STAGE_GROUP_SIZE(默认 8)分组,组内按节点顺序跑完全部视频 (先全部 extract、再全部 ASR、再全部 LLM 翻译、最后 ASS)再进入下一组。每个 视频的 run 在阶段边界保持 RUNNINGexecute_run(stop_after=节点)),下一阶段 从产物表跳过已完成节点继续,因此本地模型每组只加载一次、卸载一次,而不是每个 视频来回加载卸载;产物仍按组增量落地。LLM 阶段执行时引擎在 run 根目录写 keep_model.flag,节点据此不在每次调用后卸载模型(nodes/llm.py),阶段 结束由引擎调 release_local_model() 统一释放显存,让下一组的 ASR 拿到 GPU (否则本地模型常驻显存会让 whisper 直接 CUDA OOM)。设为 1 即回到「每个视频 跑完整链路」的旧行为。
  • 失败视频不跨阶段推进:某阶段失败的视频只在它失败节点的那个阶段重试 (下一次引擎循环从断点续跑),不会在后续阶段里重跑前序节点——避免本地 LLM 已 常驻时重跑 ASR 抢显存;视频仍按既有语义记 FAILED,任务在没有其他待处理视频时 以 failed>0 收尾。
  • 暂停/继续POST /api/batch/jobs/{id}/pause 把任务置 PAUSED 并暂停当前 run(写 paused.flagwhisper 分块间检查、OCR 逐帧检查、llm-translate **按批(20 行)**检查后中止,当前节点执行完才停);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)用红色徽章醒目标示。
  • 完成任务判定_run_job 置 COMPLETED 前校验全部非 SKIPPED 视频都已 结束(无 PENDING/PAUSED 残留),否则把任务置回 QUEUED 交引擎下一轮续跑: 留 RUNNING 是错的——next_queued_batch_job 只拾取 QUEUED,任务会停在“运行中 但没人推进”。这条路径主要出现在任务创建与明细写入的竞态:任务行先于视频 明细写入(create_job 逐条插入),引擎可能在登记完成前就拾起任务,本轮只看到 已写入的那部分视频(实测 batch_959e510259f6524 条明细中只看到最初 4 个非 SKIPPED 视频),剩下的留到下一轮;已登记明细全部尚未写入时(一条明细都没有) 同样保持 QUEUED,不能按空任务收尾。旧行为留下“N 个 PENDING 待处理却已完成” 的假完成(batch_969fabe74b83 等 3 个任务实测),或停在运行中无人推进。 崩溃恢复:重启时除 recover_interrupted_runs 外, recover_interrupted_batch_jobs 把 RUNNING 的批量任务恢复为 QUEUED (否则停在 RUNNING 的批量任务永远不会被 next_queued_batch_job 再次拾起, 未处理完的 PENDING 永久残留);同一恢复也会把被提前标记 COMPLETED 但仍有 未结束视频的僵尸任务置回 QUEUED(完成标记先于视频收尾写出的旧数据, batch_351833b7d446 实测:COMPLETED/done=0 却仍有 1 个 PENDING), 否则只靠 fix_zombie_batch_jobs.py 手动修数据,重启也不会自动诊好。
  • 产物下载GET /api/batch/jobs/{id}/videos/{vid}/download?alias=<文件名> 解析并返回视频旁的字幕文件;旧版 batch.done.json 完成标记里的语义别名 (位于旧 work_dir)仍兼容可下载。详情/创建响应里每个视频的 finals 合并上述 两处来源。
  • 详情明细展示:任务列表的“详情”只列出本批实际处理过的视频行,SKIPPED (视频旁已有字幕、创建时即被跳过)不出现在明细表里;整批都已被跳过时提示 “无待处理视频”。
  • 孤儿清理保护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

相关文档