Pavo Drama (Agnes AI API 短剧生产技能)
Changelog
- v0.9.8 (2026-08-10) — 资产质量体系升级(硬校验 + 资产包 + 存量重审计). P0 四需求全部落地。
- G1 文件级硬校验层(新增
scripts/asset_inspector.py):审计从单层升级为两层闸门——
先 Inspector 确定性校验(格式白名单 png/jpeg 禁 webp/透明残缺、per-type 尺寸
character 1024~1536 正方形 |w-h|≤64、scene 1024x768、prop 768x768、文件大小、
可解码性;P1 人脸占比 InsightFace 懒加载失败降级不阻断),失败即 rejected 零模型成本。 - G2 多模态硬惩罚(
asset_auditor.py):quality prompt 升级返回
{"score","face_ratio","defects","notes"};defects非空(杂斑/水印/文字/贴纸,
中英双语词库)→ DEFECT_DETECTED 直接拒,不被总分稀释;_parse_score兼容旧格式
(无 defects 默认 []);rejected 记录reject_reason;audit_notes 扩展 defects/face_ratio/inspection。 - 角色资产包(
asset_planner.py/style_cinema.json/character_card.json):
characterimage_size768x1024(9:16)→ 1024x1024 正方形;poses 默认 6 项
(front_anchor 主锚 / side_45 / full_body_riding / expression_serious|angry|calm);
逐 pose 注入中英双语构图指令composition_suffix+pose_templates(去 9:16);
manifest/index 记录新增pose_role(= pose,供画廊归组 + 主锚标识);
global_negative_prompt追加杂斑/水印/文字缺陷词(中英双语)。 - 存量重审计(新增
scripts/reaudit.py):reaudit <run_dir> <run_id>确定性模式
(Inspector + 历史分数,零 API 成本),三档 FAIL(质量硬伤,强制重生成)/
UPGRADE_NEEDED(仅文件级/尺寸未达新标准,建议重生成)/ PASS;输出
assets/reaudit_report.json+regenerate_queue;可选--re-score重调多模态。
对runs/mangtu_ch001实跑验证:3 FAIL(q=0.45/0.55/0.65 低分资产)+ 7 UPGRADE_NEEDED。 -
测试:新增 test_asset_inspector(16) / test_planner_pack(9) / test_reaudit(13);
全量回归 295 测试不破(仅 dashboard_server/direct_command 既有环境类 error 维持基线)。 -
v0.9.7 (2026-08-09) — agnes-2.5-flash 推理模型适配 + voice-clone 时长保真 + Demucs GPU(《莽途》ch001 实战).
ch001《墨痕》从小说到成片全链路实战中发现的 9 个缺陷全部修复。 - 🔥 P0 推理模型空响应(4 处):
agnes-2.5-flash是推理模型,max_tokens=200会被
reasoning_content吃光(实测 2485 字符)→content=""空响应、finish_reason=length、text_tokens=0,
且无异常、难排查。修复:asset_auditor.py / fidelity_gate.py / character_lock.py / prop_lock.py /
subtitler.py 全部chat_text(..., max_tokens=N)→enable_thinking=False。规则:任何 agnes-2.5-flash
调用禁用小 max_tokens cap + 关 enable_thinking。 - writer.py 健壮性:①
enable_thinking=False+max_tokens=32768(8192 装不下 24 镜 JSON 被截断);
② 3 次指数退避重试(10s×attempt,区分 empty/non-JSON/exception);③ S6 道具铁律去硬编码——
原来硬编码moyu_shujian_rolled/unrolled_v1旧项目 ID,新 run 必挂missing_asset;改为
_prop_asset_rule(catalog)从真实 catalog 动态生成(任何 run 用真实道具 ID)。 - storyboard.py beat_ref 归一化:
B01/b01/BEAT_01/beat_01全部归一为beat_01(兼容新旧剧本),
writer S4 提示也强制beat_id小写格式。 - orchestrator.py shot_id 类型失配(3 处):
script.json的 shot_id 是 int,render_manifest.json
是 str,stage_finalize_episode/stage_voice_clone_episode/_assemble_with_voice_replace
的by_id查不到 → 症状「no completed shot clips」「shot video not found」。修复:by_id统一
str(shot_id)化。⚠️ 这是本仓库反复出现的坑,跨模块 match 一律 str()。 - 🎙️ voice-clone 时长保真(重要):原 Step 3 用
[0:v]setpts=PTS*scale把视频缩放到配音时长 T
(旁白句 T=1.4-4.9s)→ 24 镜全片被压到 67.9s(原 121s)。修复:[0:v]copy保持原时长 + 配音
apad=pad_dur={vdur},atrim=0:{vdur}对齐 vdur → 时长完全保真。 - 🎙️ 旁白也可克隆:
stage_voice_clone_episode无对白镜自动补{"role":"narr","text":narration},
整集 24 镜全克隆(narr 旁白 + 角色对白统一音色)。 - ⚡ Demucs GPU(利用率 14%→32%):Demucs 原硬编码 CPU(v4.1.0 GPU segment-alignment bug 默认 CPU)。
demucs_separate_vocals支持PAVO_DEMUCS_DEVICE环境变量(默认 cuda)+--segment 4(实测稳定,
8GB 显存无压力)+ cuda 失败自动回退 cpu。 -
测试:全量回归 295 测试全绿(上述修复未破坏既有用例);ch001 实战验证 24 镜出片 + 4 版成片。
-
v0.9.6 (2026-08-09) — 进度可见性 + 多 venv 配置化(P1-2/P1-4).
两路并行实施,QA 独立回归 + 缺陷修复闭环(295 测试全绿)。 - P1-2 进度可见性:
progress.json落盘(task_queue.write_progress)——counts/ETA/elapsed/current_shot/
每镜状态(language/similarity),produce/resume 全程实时更新(渲染前 initial → 每镜 checkpoint 更新 →
集末 finalize 收敛);ETA 默认 120s/镜(对齐限速,env/pavo_config 可调);resume 场景 elapsed 以
本次会话 session_start 锚定(不被旧 checkpoint 时间戳污染);submit_failed/blocked(keyframe-prep/
fidelity/face-fidelity/prop)全部写 checkpoint 不漂移;episode_report.html集末生成(每镜状态表 +
汇总 + blocked/non_chinese/poll_failed/budget_exceeded 修复建议);Dashboard 新增「进度」tab
(run 选择/状态卡片/每镜迷你表/fetch progress.json,失败降级显示路径引导)。 - P1-4 多 venv 配置化:新增
scripts/pavo_config.py+defaults/pavo_config.json——配置优先级
单键 env(GPT_SOVITS_ROOT/DEMUCS_PYTHON/SOVITS_PYTHON)> PAVO_CONFIG JSON > defaults > 内置默认;
字段级回落、损坏不崩;voice_replace.py/gptsovits_clone.py硬编码路径全部改读配置
(clone subprocess 解释器改 SOVITS_PY,缺失回落 sys.executable)。 - 安全加固(dashboard_server.py):修复 translate_path 路径穿越(/runs/ /outputs/ /tmp/ 三分支 +
无前缀 fallback 统一走_safe_resolve:unquote+normpath+commonpath 约束,10 类穿越请求全 404);
query string 剥离;绑 0.0.0.0 下不再可任意读本机文件。 -
测试:新增 test_progress(24) / test_dashboard_server(18) / test_pavo_config(12);全量
295 测试全绿(QA 独立回归 + 集成探针)。 -
v0.9.5 (2026-08-09) — 后渲染质量闭环(字幕/语音/锁脸)+ 体验闭环(评审回写/一键编排).
路线图第 2、3 步落地。两路并行实施,QA 独立回归闭环(241 测试全绿)。 - P0-4 字幕时间轴校验器(
subtitler.py):parse_srt()+validate_srt()——4 种 issue:
text_mismatch(复用 fidelity_gate_verbatim_present边界语义)/ time_reversed / out_of_shot /
duration_mismatch(单向:仅字幕超长报,偏短合法)。纯本地零成本,把「字幕靠猜」变机器可判定。 - P0-5 本地 ASR 语音语言闭环(
whisper_check.py新增 +shot_director.py接入):
faster-whisper 检测每镜语音语言(设备 auto→CUDA/CPU),渲染后每镜打language=zh/non_chinese/unchecked
到 render_manifest;非中文 log warning 提示人工复核,不阻断渲染、不改 status(检测+告警闭环;
自动 reseed 重生成留 P2)。whisper 缺失/失败优雅降级 unchecked。 - P0-6 锁脸尾帧 + 覆盖率(
shot_director.py):InsightFace 闸门首帧+尾帧同验
(任一 blocked 即拦);新增ep_dir/gate_coverage.json——每集人脸验证覆盖率
(total/verified/skipped_no_chars/skipped_multi_char/skipped_no_dna/blocked/verified_rate/coverage_log),
多角色/无人脸/无 DNA 镜不再静默。 - P0-3 评审交互闭环(
asset_gallery.py+orchestrator.py):review.html 注入内联 JS——
勾选 localStorage 持久化、全部通过/导出评审 JSON/清空三按钮、run_id 转义防 script 截断;
build-assets --apply-review <review.json>仅重渲染 regen 资产(keep 不动、dry-run 零调用、
旧+新按 id 合并防丢资产、regen→keep 回写)。评审人效预期 +60-80%。 - P1-1 一键编排
direct(orchestrator.py):pavo-drama direct <run_dir> <run_id> <spec>
——plan→build→audit→show(生成 review.html)→ 无--auto-approve停等人工评审 →
逐集 produce(失败自动 resume 一次,produce_rc 不被覆盖)→ assemble(--voice-clone)→ report.html
每集状态表。assemble-episode新增--voice-clone CFG(import-guarded 降级)。 - 安全加固:asset_gallery
_card_html全用户字段html.escape(防破版/注入);file:// 下
Blob 下载差异已文档化。 - 测试:新增 test_subtitle_validator(26) / test_whisper_check(13) / test_shot_director_coverage(19) /
test_review_apply(21) / test_direct_command(12);全量 241 测试全绿(QA 独立回归 + 集成探针)。 -
环境:faster-whisper 1.2.1 装入 pavo-quality venv;whisper tiny 模型本地缓存
C:/Users/owlco/.cache/whisper-tiny-local(sandbox 下 HF 下载被拦,用 requests 直连);onnxruntime_old_bak 残留已清理。 -
v0.9.4 (2026-08-09) — 视频提交限速门 + 断点续传状态机补全(可靠性底座,三路研究实施).
把「文档承诺、代码缺失」的两个 P0 可靠性缺口落地成代码,QA 四轮回归闭环(150 测试全绿)。 - P0-1 视频提交限速门(
agnes_client.py):新增进程级wait_video_submit_slot()
——视频提交前强制 ≥120s 间隔(PAVO_VIDEO_SUBMIT_INTERVAL可配,非数字兜底 120);
_post_json重试参数化max_retries,视频路径max_retries=1禁用 2/4/8s 快速重试(避免 429 时烧配额);
429/5xx 走视频级指数退避(base 30s cap 180s,3 次上限)。 - P0-2 断点续传状态机补全(
task_queue.py+shot_director.py+orchestrator.py):
① checkpoint 新增submitted态——submit 成功后立即持久化 video_id;
②create_checkpoint改为 load-or-merge(复用现有 cp,保留 submitted/failed/blocked 状态,
仅补缺失镜 + 重置「completed 但文件缺失/0 字节」为 pending);
③ resume 时should_repoll_shot优先——已提交未完成的镜先 re-poll 已知 video_id,
不重新 submit(杜绝网络抖动导致的重复付费);
④cmd_resume_episode补传VideoBudget(修复 resume 时预算闸门失效)+ spec.json 加载兜底 500;
⑤ 磁盘文件跳过统一 size>0 校验(0 字节损坏文件不再误判完成);
⑥ skip 分支同步收敛 checkpoint:仅 pending/None/submitted-无video_id 收敛为 completed,
blocked/failed 保持权威状态不翻转。 - 测试:新增
tests/test_video_submit_gate.py(7 个)+tests/test_task_queue_resume.py(8 个)
共 15 个新用例;全量 150 测试全绿(135 旧 + 15 新,QA 四轮独立回归含集成探针)。 -
429 语义修正:429 移出
RETRYABLE_STATUS,_post_json/_get_json显式 raise
AgnesQuotaError(GET poll 路径 429 立即抛不重试;视频外层长退避行为不变)。 -
v0.9.2 (2026-08-01) — GPT-SoVITS V4 零样本音色克隆(声优闭环).
GPT-SoVITS V4 在本机跑通:四模型(GPT V3 s1v3.ckpt 149MB + SoVITS V4 s2Gv4.pth 734MB -
BigVGAN vocoder.pth 56MB + bigvgan_generator.pt 430MB)+ BERT/HuBERT/G2PW/fastText。
V2 → V4 升级:采样率 32kHz→48kHz,声码器 HifiGAN→BigVGAN,真人声克隆节奏显著更自然。
推理脚本模板:C:/Users/owlco/.workbuddy/GPT-SoVITS/tts_demo.py;关键坑:
sys.path 需加GPT_SoVITS/(匹配 api.py 约定)、change_sovits_weights是生成器需传
prompt_language、onnxruntime CUDA 13.x vs PyTorch 12.4 不影响 TTS(仅 G2PW 走 CPU fallback)。
环境: pavo-sovits venv (Python 3.11.15, torch 2.6.0+cu124, GPU: RTX 2070 Super 8GB)。
下一步: 接入voice_replace.py的--voice-cloneflag,用指定角色音色样本驱动整集换声。 -
v0.9.3 (2026-08-04) — GPT-SoVITS V4 多音色克隆落地为
--voice-clone后端(实测 EP20 莽途 20 镜跑通).
把 v0.9.2 的"下一步"真正实现,并把 EP20 踩的坑固化进技能。 - 新增
scripts/gptsovits_clone.py:隔离的批量克隆助手。GPT-SoVITS 的 import 很脆弱
(必须cwd=repo root 且sys.path含GPT_SoVITS/,否则 import 时HFValidationError),
故拆成独立文件由voice_replace.py以subprocess(cwd=repo_root)驱动,所有脆弱依赖锁在一处。 voice_replace.py --voice-clone <cfg.json>:整集多音色克隆。流程:
① 每镜 Demucs--two-stems=vocals分离,保留no_vocals(环境音+音效)、丢弃vocals(原生旁白);
② GPT-SoVITS V4 按role→ref池逐句克隆 → 每镜dub.wav;
③ 视频setpts缩放到配音时长 T,再amix normalize=0把 配音(1.0)+no_vocals(0.3) 混音;
④ 20 镜 concat → 成片。绝无双声重叠。- 参考音来源(合规):用 edge-tts 联网合成 6 角色参考音(规避真人声音版权),存
refs/ref_*.wav+.txt。 - 固化三个 EP20 踩坑(见失败模式表):① 语音重叠——若直接把 GPT-SoVITS 配音叠到
原始音轨上,原音轨里的原生中文旁白会和配音双声齐发;必须用 Demucs 剥离原生 vocals;
② 英文词崩溃——配音文本含英文(如 "AI")会触发 GPT-SoVITS 英文分支import wordsegment
(venv 未装 →ModuleNotFoundError),须把英文概念改纯中文(如"人工智能")或pip install wordsegment;
③ Windows 路径——脚本参数须用C:/Users/...,不能用 MSYS 风格/c/Users/...(被 Python 解析成
C:\c\Users\...找不到文件)。 - 环境:克隆在
pavo-sovitsvenv 跑(torch);Demucs 在pavo-qualityvenv,跨 venv 用
subprocess调demucs_python。成片 448×832 竖屏、69s、含环境音、单条 aac。 -
Agnes 模型对齐(2026-08-04):文本模型
agnes-2.0-flash→agnes-2.5-flash
(agnes_client.py的MODELS["text"]已更新;SKILL.md 文档同步)。图片模型保持
agnes-image-2.1-flash、视频模型agnes-video-v2.0不变。 -
v0.9.1 (2026-08-01) — InsightFace 本地锁脸 + Demucs 音色分离替换.
P0 InsightFace 本地人脸验证 (Gate 3.5): 在 I2I 绑定(Layer 2)与 AI 多模态闸门(Layer 3)
之间插入一道本地、确定性、零成本的人脸一致性校验。使用 InsightFace buffalo_l 模型
(99.83% 准确率),CPU 推理 <50ms/帧,显存占用 ~300MB。无人脸/远景/多角色镜头自动跳过。
嵌入向量在lock-characters阶段自动提取并缓存到character_dna/embeddings/。
P1 Demucs 音色分离替换: 解决 v0.4.0 "禁止静音+配音" 约束。Demucs htdemucs_ft 分离
人声与环境音/SFX → edge-tts 生成新中文语音 → FFmpeg 混音保留环境音 → mux 回视频。
assemble-episode --voice-replace一键完成整集换声。单镜处理 2-5s(GPU),显存 ~5-6GB。
v0.9.2:--voice-clone接入 GPT-SoVITS V4 零样本声优克隆,用角色真声样本驱动整集配音。
新增模块: face_verify.py, voice_replace.py。
修改: shot_director.py (Gate 3.5), orchestrator.py (lock-characters 嵌入提取 + --voice-replace 集成)。
已知限制: Demucs GPU segment-alignment bug (v4.1.0),默认 CPU;InsightFace onnxruntime-gpu 需 CUDA Toolkit。
测试: 26 单元测试全通过 (15 face_verify + 11 voice_replace)。
Web Dashboard:http://localhost:8800/outputs/dashboard.html— 项目总览、镜头画廊(含人脸验证得分)、
配音样本试听、流水线架构图。 -
v0.9.0 (2026-08-01) — 跨镜头锁脸系统 + 小说自动导入 + 断点续传 + 视频模式扩展.
P0 锁脸: 角色长相、穿搭、五官、发型在整部剧所有镜头中保持严格一致——
不是靠 prompt 文字描述,而是靠四层结构锁:
① 角色 DNA 参考图(多角度)先行生成并锁定;
② I2I 强制绑定——每镜 input_images 必须包含角色参考图;
③ 面部保真闸门逐镜对比多模态校验;
④ 后期色彩统一(FFmpeg 色彩分级 + 时代滤镜)。
P0 小说导入:scan-novel命令读取小说原文,LLM 两轮提取角色/场景/道具,
输出合法 spec.json(大幅降低建库人工成本)。
P1 断点续传: JSON checkpoint 每镜写入,resume-episode跳过已完成镜,
仅重做失败/未完成的(解决 Agnes 1RPM 限速下的中断痛点)。
P1 视频模式: grid_4/grid_6/grid_9(宫格漫剧)和 reference(锚定视频)
两种新模式。
新增模块: character_lock.py, color_grade.py, novel_scanner.py, task_queue.py。
新增命令: lock-characters, scan-novel, resume-episode, grade-episode。
参考源: ArcReel v0.16.0 的角色设计先行 + 异步引擎 + 小说导入。 -
v0.8.0 (2026-07-31) — Stage 2 从「软提示」升级为「结构闸门」:吸收 ToonFlow 六铁律.
用户实测三问题(①剧本没按小说来 ②道具没引入 ③AI 幻觉太多)诊断后落地。
根因:Stage 1 有人工评审闸门,Stage 2(script→成片)零闸门直通 ——
writer 让 LLM 自由创作、consistency_checker只查 asset_ids 存在 + 禁词 + 时长,
_build_motion_prompt里的禁令只是写进 prompt 的否定句(概率性、不可验证)。
本版把约束全部改成机器可判定、失败即阻断的结构闸门,软提示退回审美兜底。
1. 新增 storyboard 阶段(闸门 1) —scripts/storyboard.py:
build_storyboard()把剧本编译成结构化分镜(panel / beat_ref / carry_over /
state / 绑定资产),validate_storyboard()把 ToonFlow 六铁律写成代码规则:
台词逐字 diff(零删改,_verbatim_present引号边界精确匹配,防「那就留下吧」
这类子串绕过)、出场人物完整、action 只描述动作状态(词表 lint)、beat_ref
必须存在且单调递增(防跳节拍 / 无中生有)、carry_over必填、
资产必须是 catalog 里的真实 ID(禁编造)。
orchestrator.stage_build_storyboard()插在write_episode与shoot_episode
之间,产出episodes/<n>/storyboard.json/baseline.json/
storyboard_report.json;任何 error 都抛StoryboardGateFailed中止(返回码 3)
—— 一帧都不渲染。
2. 资产结构化绑定(闸门 2) —shot_director.bind_shot_assets()成为
唯一合法资产通道(对齐 ToonFlow 的associateAssetsIds)。绑定资产(含
道具母图)全部注入 i2iinput_images,首帧与尾帧都进,并生成@图N
引用图例写进 prompt —— 道具从「prompt 里一句话」变成图像条件。
道具合成失败由静默兜底改为 fail-fast(抛ShotBlocked),不再出现
「引用了却没画出来」。同时删掉_build_motion_prompt里
if any("moyu_shujian" in ...)的硬编码特判,改为泛化遍历
associate_asset_ids中type == "prop"的条目。
3. 提交前忠实性校验(闸门 3) —scripts/fidelity_gate.py:
gate_shot()在create_video提交之前跑 A.1–A.7 确定性校验 ——
资产真实、台词逐字、时长/镜序、禁编造、禁时间逆转(30 词词表)、
状态不漂移、跨镜一致。blocked 的镜直接continue,绝不提交,
记进render_manifest.blocked_shots。
Part B 多模态视觉验收(看渲染帧核对道具是否真上镜)已实现接口,
默认关闭(visual=False/--visual-gate开启),因为它要发网络请求。
4. 跨镜基准表锁定(闸门 4) —scripts/continuity_baseline.py:
build_baseline()一次编译锁定 position / facing / costume + 剧情状态机
(书灵 dormant→faint_stir→… 只许前进不许倒退,也不许抢跑;作者提前声明会报
state_declared_early)。inject_staging()是纯模板拼接、不过 LLM,
保证跨镜文案一致,杜绝「每镜重新想一遍」的漂移。
5. writer 吃原著(治问题①的源头) —writer.SYSTEM_PROMPT加 S1–S9
原著保真铁律;新增build_source_block(),从episode_spec读
source_doc(原著/修正 shot table 路径)、source_facts(不可协商事实)、
beats(节拍表)、forbidden(反事实清单)注入提示词。
schemas/episode_script.json扩展beats/state_machine/beat_ref/
position/facing/costume/carry_over/associate_asset_ids。
6. 测试 —scripts/tests/test_gates.py(79) +test_gates_adversarial.py(24),
python -m unittest discover -s tests实测 103 passed。对抗测试专攻绕过路径
(子串台词、状态抢跑、逗号分句、carry_over 覆盖),QA 判定无可绕过缺陷。
> 设计原则(写给未来的自己):约束要么是结构闸门(schema / 词表 / diff /
> 状态机 / 多模态验收,机器可判定,失败即阻断),要么就不算约束。写进 prompt 的
> 否定句只能做审美兜底,不要再用软提示去修可验证的问题。 -
v0.7.1 (2026-07-30) — 幻觉压制 + 道具强制上镜(软提示层,已被 v0.8.0 结构化取代).
用户实测:shot_05「摔马后时光逆转」、多镜书简不见。定位:脚本层清白
(script 与 motion prompt 都无「逆转」字样、23/24 镜已挂 moyu_shujian)
→ 属渲染层 AI 幻觉 + 资产未真正上镜。当时的修法是在
_build_motion_prompt末尾追加全局禁令「禁止时光逆转/时间倒流/画面倒放…」
与「墨玉书简须清晰完整稳定呈现」。这是软提示,不可验证 ——
v0.8.0 已用 fidelity gate 词表 + 图像条件绑定把它结构化,此处保留为历史记录。 -
v0.7.0 (2026-07-30) — 音色升级为一等资产.
问题:每镜由 Agnes 独立生成语音、无固定声纹 → 音色逐镜漂移。
修法:spec 新增voices[]数组(如voice_wangmang/voice_narrator),
每项带audio_prompt(锁定音色描述);shot 带voice_id;
shot_director._build_motion_prompt(voices=...)把对应audio_prompt
追加到台词之后,Agnes 据此出原生中文语音。
⚠️ prompt 级锁定非 100% 保证(Agnes 仍非确定性),要彻底统一音色 + 口型
需外接 TTS + Wav2Lip 唇形重绘(未实现)。 -
v0.3.5 (2026-07-29) — 修复「图生视频首帧为原图」:开场帧场景化重渲染 + 去头裁剪.
深度研究结论:I2V 首帧条件化是模型的设计特性(Agnes/Seedance/Luma 都把
第 0 帧钉死在提交图上;Seedance 甚至以@Image1 as the first frame作为卖点),
且 Agnes/videos请求体无任何 motion/首帧贴合度旋钮(对照 ToonFlow 后端
serve/app.js通用厂商路径证实:body 仅 model/content/ratio/duration/
resolution/watermark/generate_audio)。所以只要把原始资产图直接当首关键帧
提交,视频开场就必然是原图——修复必须在提交前后两侧做:
① FIX A(主,默认开启):shot_director._render_opening_frame把首关键帧
改为「按frame_start场景化重渲染的电影开场帧」(i2i from 资产图,融入
场景/光线/景深/胶片颗粒,硬否定 isolated studio shot / product photo)——
第 0 帧从机制上不再可能是孤立资产原图;keyframes 与 single 两模式都接入;
逃生开关shot.keep_first_frame:true(资产图本身就是理想开场帧时用)。
实测(墨玉书简,5s keyframes):原图 vs 视频@0 SSIM 0.355(修复前
0.582),开场帧已是西汉书房场景(烛光/书案/手入画);开场帧 vs
原图 SSIM 0.354(资产图与场景化首帧已完全不同)。
② FIX B(辅,独立工具):新增scripts/head_trim.py,SSIM 采样检测开场
「近定格头」(与第 0 帧相似度 ≥0.90 的头部时长),≥0.2s 才裁、上限 1.0s,
重编码保留原生语音音轨,支持 --dry-run / 就地(.bak) / -o 输出。keyframes
固有的 ~0.5s 首帧停留可用它剪掉,开场即动。 - v0.3.4 (2026-07-29) — 关键帧模式行为说明 + 单图逃生通道.
复测确认「前几帧出现原图、视频似乎全部用尾帧生成」是 Agnes keyframes
插值的固有行为(首帧=起始关键帧、尾帧被冲过之后定格),非代码 bug:
首→尾过渡被压进前 ~1s、之后定格尾帧 4s(SSIM 实测:首vs0.5s≈0.58 还停
原图,1s 后平台在尾帧构图)。对照单图模式运动均匀铺满整 5s(全程
SSIM 0.26–0.29 持续变化),是修复"冻结/定格"的正确路径。
用户决定 keyframes 维持默认(接受其固有行为)。本版据此:①shot
新增可选mode字段("keyframes"默认 /"single"逃生通道,单镜走
连续运动);②shot_director按mode选择first_image+last_image或
单image提交,结果记录实际mode;③episode_script.jsonschema 加
mode;④ 本文档如实记录 keyframes 的"前持原图+尾定格"特性及单图替代。 - v0.3.3 (2026-07-29) — 修复图生视频「前几帧贴原图/不融合」.
实测发现首尾帧镜视频偶尔整段接近静帧、源图出现在前几帧。根因:①
视频 prompt 只用action(偏静态描述),模型倾向「抱住」首帧;②若
frame_end缺失,shot_director退化成first==last,keyframes 没有插值
目标 → 冻结。本版:① 新增_build_motion_prompt,把shot_direction+
frame_start→frame_end过渡 +action+ 显式「动态、非静照」指令合成强
运动 prompt;②_shot_keyframes在frame_end缺失时不再复用首帧,
而是合成默认尾帧提示(推近+轻微旋转+暖调),保证首尾帧始终不同;③视频
负向词加入static image, freeze frame, still photo, no motion, duplicate frame。实测(墨玉书简道具,5s/121帧/24fps):首帧已是重渲染关键帧
(与源图 SSIM≈0.54),0.5s 即明显运动(SSIM 0.26)、1s 强动画(0.17),
且 MP4 自带原生中文 AAC 音轨。 - v0.3.2 (2026-07-28) — 资产展示环节 + 首尾帧 + 短剧暂不产出.
① 新增资产评审环节:build-assets自动生成自包含 HTML 画廊
assets/review.html(base64 内嵌全部资产图),新增show-assets命令;
run默认停在资产评审(不再自动进视频生成),等用户确认/修改后再
用produce-episode/assemble-episode推进。② 镜视频改为首尾帧模式:
agnes_client.generate_video支持first_image+last_image→ 发送
image=[首帧,尾帧]+mode="keyframes"(已实测接受并完成);writer每个
镜头新增frame_start/frame_end,shot_director据此生成首帧+尾帧并做
image-to-image 生成尾帧。③ 短剧暂不产出:produce-episode/run
默认只产出「带原生语音的镜视频(首尾帧)」,不拼接短剧成片;成片组装
改由独立的assemble-episode(或run --drama/produce_drama:true)负责。 - v0.3.1 (2026-07-28) — 中文语音机制修正. 之前错误地认为语音来自
独立 TTS(于是加了 edge-tts 配音阶段)。经核验 ToonFlow 后端源码确认:
Agnes 视频模型(agnes-video-v2.0)在POST /videos传generate_audio: true
时会原生生成中文语音,返回的 MP4 自带音轨;ToonFlow 的ttsRequest是空壳
(return ""),语音与 TTS 无关。因此本版:① 重写agnes_client.generate_video
走真实/videos接口并默认audio=True;②shot_director传 base64 图 +
audio=True;③finalize改为保留音轨拼接;④ TTS/dub 降级为默认关闭的可选
「换声」覆盖。 -
v0.4.0 (2026-07-30) — 三项生产修正(用户看片后实测).
1. 原生音轨必须保留(禁止静音+配音). 原生audio=True返回的音轨
是 语音 + 环境音 + 音效 三位一体(风声、马蹄、布料摩擦等都在里面)。
实测:改audio=False静音再用 edge-tts 重新配音,会彻底丢失环境音
与音效,是对成片的硬伤。→ 永远保留原生audio=True音轨;edge-tts
的dub只是「换声覆盖」且会丢掉原生环境音,仅在没有环境音需求时
才考虑。
⚠️ 但若要「换声」(GPT-SoVITS 克隆 / edge-tts 重新配音),必须先用
Demucs 把原生音轨拆开、只保留no_vocals(环境+音效)、丢弃vocals(原生
旁白),再叠新的 AI 配音。原因是原生音轨里的「语音」就是模型原生念的
中文旁白——直接把 AI 配音混到原始音轨上,两道人声会双声齐发(语音重叠)。
v0.9.3 的voice_replace.py --voice-clone已默认这么做(Demucs 剥离 + no_vocals
垫底),切勿回到"配音直接叠原音"的旧做法。
2. 原生语音语言不可控 → 在 prompt 层修正,不靠静音. 原生语音是模型按
prompt 文本做语言检测生成的,无language参数。实测:同一集里只有
第 1 镜是中文、其余 2–24 镜跑成英文(纯靠运气不稳定)。修法:每段视频
prompt 以中文台词/旁白主导开头 + 显式追加「全程使用中文普通话配音;所有
台词与旁白用中文朗读」指令;个别仍跑偏的镜头用换 seed / 微调 prompt
单镜重生成,绝不用「静音+配音」绕路。
3. 道具必须建主资产 + i2i 合成引用,禁止每镜文本重生. 之前PROP只是文本
模板拼进 prompt,每镜从文本重新生图 → 墨玉书简每镜长相漂移。修法:先生成
一张主资产图(如墨玉书简母版);每镜以双图 i2i 把主资产合成进
场景静帧(input_images=[base静帧, 主资产URI],prompt 要求「保持角色/场景
不变、仅以完全一致外观渲染道具」),全集体道具一致;角色/场景镜(无命中道具)
不碰。shot_director新增prop_i2i参数(orchestrator._build_prop_i2i
从 catalog 的type=="prop"记录自动构建)承接此机制。
4. head_trim 不得用于连续剧流水线. 它会裁掉开场近定格头,但首句台词
往往在开场 0.5s 内,裁剪必切掉台词、打断镜头连续性。v0.3.5 的
_render_opening_frame(首帧场景化重渲染)已让第 0 帧不再是资产原图,
所以「前几帧原图」问题本就不该靠 head_trim 解决 —— 连续剧流水线直接弃用。 -
v0.5.0 (2026-07-29) — 时代红线从「文档约束」升级为「代码强制」.
用户要求:道具/场景时代约束加强,不能出现时空混乱,非剧本里跨时代产物不能出现。
此前「西汉/莽途」时代红线只写在下方 Project red lines(纯文档),生成 prompt
里其实没落地。本次把红线做成可执行的代码约束,单一事实源 =
defaults/style_cinema.json的era块(label/positive/negative
双语负面清单)。
1. 负向约束注入所有生成链路.asset_planner.plan()与shot_director
shoot_episode()(经_merge_era_constraints)把era.negative折叠进
global_negative_prompt、era.positive折叠进global_prompt_suffix——
于是角色/场景/道具静帧、关键帧、开场帧、视频 motion prompt 及其 negative
全部继承时代红线(无需逐个调用点改)。artists.build_all读的是已烘焙进
manifest 的 negative,自动继承。
2. writer 被明确禁止发明跨时代器物.writer.write_episode新增
style_preset参数;SYSTEM_PROMPT 加第 10 条「时代红线(最高优先级)」硬规则,
USER_TEMPLATE 注入时代约束(红线)块(含完整 forbidden 清单)。模型不准
凭空写出现代/工业/电子制品、拉丁字母/阿拉伯数字标识、玻璃幕墙、电灯、机动车、
霓虹、空调、监控、现代服装等;宁可复用已批准资产。
3. consistency-checker 新增时代闸口.check_episode新增
forbidden_era_terms参数(orchestrator 从style.era.negative注入)。
_check_era_consistency扫描每个镜头的 action / frame_start / frame_end /
dialogue / narration / shot_direction:视觉字段命中即 error(意味着错乱会
真的渲染进视频),台词/旁白命中仅 warn(角色可「讨论」现代概念而不上镜)。
匹配按完整禁用词(如「钟表」「手机」「塑料」),不按松散子串,故「钟声」等
符合时代的词不会误伤。
4. 道具模板去现代化.per_type_overrides.prop.prompt_template由
"Product shot of {name}: ... studio softbox lighting, neutral seamless
background" 改为「authentic ancient Han-dynasty artifact ... no modern
studio setup」,杜绝现代电商棚拍观感。
→ 红线现已端到端强制:生成负向约束 + writer 硬规则 + consistency 闸口三重保险。
无需改剧本即可生效;若未来要做其他朝代/架空设定,只需换style_cinema.json的
era块(或新建 style 预设),代码零改动。 -
v0.6.0 (2026-07-30) — 时代红线改为「按场景 / 按剧集放行」,不再一个列表用到头.
用户纠正:莽途小说本身是穿越设定(王莽带现代知识),跨时代产物是剧情需要,
不能一个全局黑名单禁到底。修正为 per-scene / per-episode release:
1.allowed_cross_era放行字段. 在 场景卡(scenes[].allowed_cross_era)
和 剧集设定(episodes[].allowed_cross_era)上声明该场景/剧集允许出现
的跨时代器物清单。也可在单个 shot 上用shot.allowed_cross_era覆盖。
2. 生成端按镜放宽.shot_director.shoot_episode新增scene_cards/
episode_allowed参数;每镜经_resolve_shot_allowed汇总本镜放行权,再用
_apply_shot_allowed把放行词从global_negative_prompt中剔除(不再抑制
该器物),并在global_prompt_suffix追加「本镜依剧本设定允许出现跨时代器物:
X、Y;其余仍须符合西汉风貌」的显式放行注记。未放行的词仍全局禁。
3. 闸口按镜放行.consistency_checker._check_era_consistency扫描前先减去本镜
放行权,只对未放行的跨时代词报警(视觉字段=error,台词/旁白=warn)。已声明的
跨时代器物不再误报。
4. writer 知情.writer.write_episode的era_block现在会把「本集 / 各场景
放行的跨时代器物」列给模型看,硬规则第 10 条改为「按场景 / 按剧集放行」语义:
清单内的可在对应 scene_id / 本集出现,其余仍禁。
→ 默认仍是「全禁」:只要场景卡 / 剧集没声明allowed_cross_era,红线对全片生效;
一旦某场景/剧集显式放行,只有该处放宽。配置零改动即可切换到「全禁」或「按需放行」。
→ 真实剧本(莽途 ch004/简光、ch005/边警)实测:静态清单零命中、LLM 考据判「符合时代
无跨时代器物」;早期英文开发测试脚本里漏网的 modern precision / shorts / modern
knowledge 已补进era.negative(并修掉裸词「枪」误伤「明枪暗箭」、英文 camera/drone
撞车分镜指令两处误报)。 -
v0.6.1 (2026-07-30) — 把「按场景/剧集放行」接到真实莽途 run + 修闸门子串误报.
用户选「扫描真剧本、自动抽取跨时代产物放行」。结论与落地:
1. 真实莽途 run 的跨时代产物已定位并放行.pavo-drama-runs/wangmang-001这部
「王莽穿越者实锤与翻车全纪录」现代解说短剧有意使用跨时代道具:游标卡尺(vernier caliper):palace_hall场景 shot_01;短裙令(short skirt decree):palace_hall(shot_01) 与policy_cards_bg(shot_02);王莽新钱(new_coin):历史上王莽币制改革是真事(货布/货泉),非跨时代物,全局
黑名单本就不含钱币,无需放行。
文学版莽途(ch004 简光 / ch005 边警,已产出的 EP01 墨痕初现)纯西汉、只有墨玉书简法器,
无任何跨时代器物,保持默认全禁即可。已写入两份声明:wangmang-001/episodes/1/episode_spec.json加allowed_cross_era(剧集级放行);wangmang-001/spec.json的scenes[]:palace_hall/policy_cards_bg场景卡带
allowed_cross_era(按场景放行),disaster_field/ending_table留空(仍全禁);
episodes[]也带剧集级放行。
→ 注意:标准生效路径是剧集级(episode_spec.allowed_cross_era,orchestrator 透传为
episode_allowed)+ 场景卡级(spec.scenes[].allowed_cross_era,导演按
shot.scene_id查卡);writer不会把shot.allowed_cross_era透传到 script,故
每镜覆盖需另行接线,一般不依赖。
2. 把短裙/short skirt加进全局黑名单再放行. 为贴合「全局封、按场景放」语义,
将短裙/short skirt补入style_cinema.json的era.negative(原仅有短裤/
shorts)。短裙视觉上会被短裤类负向词抑制,故在本穿越剧显式放行,其余朝代/剧集仍禁。
3. 修闸门子串误报(car⊂card). 原_check_era_consistency用t in text子串匹配,
短英文词条car会在 "policy card" 里被误判成汽车、gun在 "begun" 里误判等。新增
_term_in_text:ASCII 词条改词边界匹配
((?<![A-Za-z0-9])term(?![A-Za-z0-9])),CJK 词条仍精确子串(不会误嵌)。
验证:真实 wangmang-001 剧本不放行时正确拦下 游标卡尺/短裙令;放行后 0 误报;
控制项(手机)即便在放行状态仍被拦(默认封禁仍生效);场景卡级放行独立生效。
> 注:真实可用的视频端点是 POST /videos(轮询 GET /agnesapi?video_id=)。
> POST /contents/generations/tasks 是 ToonFlow 自有中继路径,在
> apihub.agnes-ai.com 上返回 404,本技能不使用。
Overview
This skill drives the Agnes AI API (PavoAPI) at https://apihub.agnes-ai.com/v1
to produce vertical short dramas end-to-end, with explicit attention to the four
classic AI-drama pain points:
- Character consistency (跨镜头角色不穿帮)
- Scene constraints (场景不漂移)
- Prop consistency (道具不掉戏)
- Voice consistency (配音一致性)
It is a two-stage pipeline with explicit asset-first design:
Stage 1 — Asset Pipeline Stage 2 — Episode Production (v0.8.0 四道结构闸门)
asset-planner writer (吃原著: source_doc/facts/beats/forbidden)
character-artist ─┐ ↓
scene-artist ──┼→ asset-auditor ─┐ consistency-checker (禁词/时代/时长)
prop-artist ─┘ ↓ ↓ ↓
asset-REVIEW gallery (HTML) 【闸门1】build-storyboard 六铁律校验
← HUMAN SIGNOFF HERE 失败 → StoryboardGateFailed 中止(rc=3)
↓
【闸门4】continuity baseline 锁定
↓
shot-director:【闸门2】bind_shot_assets → i2i 首尾帧
↓
【闸门3.5】InsightFace 本地人脸验证
↓
【闸门3】fidelity_gate.gate_shot
↓ pass ↓ blocked
POST /videos 不提交, 记 blocked_shots
│
produce-episode → shot videos (each carries native voice)
│ (短剧成片默认不产出)
assemble-episode → finalize (concat) + optional dub
→ --voice-replace: Demucs分离 + TTS换声(保留环境音)
> Chinese voice comes from the VIDEO MODEL, not a TTS. The Agnes video
> model (agnes-video-v2.0) synthesizes speech natively when you pass
> generate_audio: true to POST /videos — the returned MP4 already contains
> a Chinese audio track, with no separate TTS stage. Verified 2026-07-28
> against ToonFlow's own backend: its ttsRequest is a stub that returns
> "", and the voice in its storyboard videos is emitted by the video model
> itself (via generate_audio). So pavo-drama needs no TTS to get voiced
> output; tts + dubbing are kept only as an OPTIONAL voice replace when
> dub: true. Shot videos are generated in first+last frame mode
> (mode=keyframes, image=[首帧,尾帧]) so each clip animates from its
> opening frame to its closing frame.
>
> ⚠️ v0.4.0 关键约束(用户看片实测):
> 1. 原生音轨 = 语音 + 环境音 + 音效,三位一体,必须保留。 绝不要为了
> 「修正语言」而改 audio=False 静音再用 edge-tts 重新配音 —— 那会彻底
> 丢失风声/马蹄/布料等环境音与音效,是对成片的硬伤。保留原生
> audio=True 音轨;edge-tts dub 是「换声覆盖」且会丢掉原生环境音,
> 仅在没有环境音需求时才考虑。
> ⚠️ 要「换声」(GPT-SoVITS 克隆 / edge-tts 重配)时,必须 Demucs 剥离
> 原生 vocals(旁白)、只留 no_vocals(环境+音效) 垫底——否则原生旁白与
> AI 配音双声齐发(语音重叠)。v0.9.3 --voice-clone 已默认如此。
> 2. 原生语音语言按 prompt 检测,无 language 参数,不可控。 实测同一集
> 仅第 1 镜中文、其余跑成英文。修法(不靠静音):每段视频 prompt 以中文
> 台词/旁白开头主导 + 显式追加「全程使用中文普通话配音;所有台词与旁白用
> 中文朗读」指令;个别仍跑偏的镜头用换 seed / 微调 prompt 单镜重生成。
> 3. 道具一致性靠主资产 i2i,不靠文本重生 —— 见下方「Prop consistency」。
Assets are versioned, audited, and catalogued. Each episode draws
from the same catalog, giving you cross-episode consistency without
regenerating assets every time.
Relationship to ToonFlow (结合 ToonFlow)
ToonFlow (AI 短剧工厂) is a desktop orchestrator app that wraps the
exact same Agnes backend this skill uses:
| Capability | pavo-drama (this skill) | ToonFlow |
|---|---|---|
| Endpoint | https://apihub.agnes-ai.com/v1 (direct) |
same endpoint, via openai vendor |
| Auth | Bearer $PAVO_API_KEY |
local HTTP API, Bearer <self-signed JWT> |
| Text model | agnes-2.5-flash |
agnes-2.5-flash |
| Image model | agnes-image-2.1-flash |
agnes-image-2.1-flash |
| Video model | agnes-video-v2.0 |
agnes-video-v2.0 |
| Voice / TTS | native from the video model (generate_audio: true → Chinese audio inside each clip) |
native from the video model (generate_audio); ttsRequest is a stub |
| Orchestration | Python scripts here | app UI + local SQLite DB |
| Asset/storyboard store | assets/index.json on disk |
%APPDATA%\toonflow\data\db2.sqlite |
| Storyboard gates | v0.8.0: 六铁律 code-enforced (storyboard.py + fidelity_gate.py, hard abort) |
六铁律 enforced in its production agent |
| Asset↔shot binding | v0.8.0: bind_shot_assets() → i2i image conditions |
associateAssetsIds (structural) |
Both ultimately call Agnes. Choose per need:
- Use pavo-drama directly when you want full scripted control, custom
auditing, and a portableassets/index.jsoncatalog (default mode). - Use ToonFlow mode (below) when you already run a ToonFlow project and
want to reuse its UI, project DB, asset library, and the native
"分镜图→视频" steps — or when you want pavo-drama to push generated
assets/storyboards into ToonFlow.
⚠️ Image model name mismatch (verify before relying on it): this skill
defaults agnes-image-2.1-flash; ToonFlow's live image model is
agnes-image-2.1-flash. Both are configured under the same openai vendor
in ToonFlow's o_vendorConfig. If a direct call fails with model not found,
try agnes-image-2.1-flash (override via the model= arg of
agnes_client.generate_image). The 2.1 variant is what ToonFlow actually
ships and is the safer default for ToonFlow-mode work.
ToonFlow Integration (本地编排模式)
All calls below are against ToonFlow's local HTTP API. A ready-made helper
lives in scripts/toonflow_bridge.py (stdlib-only, no Agnes key needed):
python scripts/toonflow_bridge.py discover # find ToonFlow's live port
python scripts/toonflow_bridge.py tokenkey # read o_setting.tokenKey
python scripts/toonflow_bridge.py extract --project-id <pid> --script-ids 1,2
python scripts/toonflow_bridge.py backfill --script-id <sid> --project-id <pid> rows.json
python scripts/toonflow_bridge.py genimage --script-id <sid> --project-id <pid> --ids 1,2,3
python scripts/toonflow_bridge.py db "SELECT count(*) FROM o_storyboard"
1. Discover the live port (动态端口)
ToonFlow listens on a dynamic port (observed 49366; docs' 10588/60000
were NOT live on this machine). Discover it each run — never hard-code:
$pids = (Get-Process -Name ToonFlow -ErrorAction SilentlyContinue).Id
Get-NetTCPConnection -State Listen |
Where-Object { $_.OwningProcess -in $pids } |
Select-Object -ExpandProperty LocalPort
2. Self-sign the JWT (鉴权)
ToonFlow's API uses Authorization: Bearer <JWT>, HS256, secret =
o_setting.tokenKey (this machine 234001a6). Self-sign:
header = base64url({"alg":"HS256","typ":"JWT"})
payload = base64url({ any claims, e.g. {"sub":"pavo","iat":<now>} })
sig = HMAC-SHA256(secret, header + "." + payload) # base64url
jwt = header + "." + payload + "." + sig
toonflow_bridge.py does this for you (sign_jwt). The login password
(admin/admin123) is not the API auth — use the JWT.
3. Read the token key (and verify) from the DB
DB: %APPDATA%\toonflow\data\db2.sqlite (WAL). Always open
read-only (mode=ro) so you never lock ToonFlow's writes:
import sqlite3
con = sqlite3.connect("file:.../db2.sqlite?mode=ro", uri=True)
key = con.execute("SELECT value FROM o_setting WHERE key='tokenKey'").fetchone()[0]
agnes_client/this skill never writes ToonFlow's DB directly — prefer the
HTTP API. The API correctly handles track grouping, asset association, and
row IDs.
4. Asset extraction (资产提取)
POST /api/script/extractAssets
body: {"scriptIds":[...],"projectId":<id>,"groupSize":4}
Runs async (calls the text LLM). Poll o_script.extractState:
2=排队, 0=提取中, 1=完成, -1=失败.
5. Storyboard backfill (分镜表回写主库)
When ToonFlow has a storyboard in o_agentWorkData(key=productionAgent).
storyboardTable but o_storyboard is empty, push it via:
POST /api/production/storyboard/batchAddStoryboardInfo
body: { "data":[ {prompt,duration,track,state,src,videoDesc,shouldGenerateImage,associateAssetsIds} ], "scriptId":<sid>, "projectId":<pid> }
This auto-creates o_videoTrack per track, backfills o_storyboard.trackId,
and links o_assets2Storyboard. Full recipe (prompt formatting, the
景别/运镜 trap, red-line appending) is in the sibling skill
toonflow-backfill-storyboard — load it for that step.
6. Image / video generation + the CRITICAL 0-byte bug ⚠️
POST /api/production/storyboard/batchGenerateImage
body: { "storyboardIds":[全量], "scriptId":<sid>, "projectId":<pid>, "concurrentCount":<n> }
Known failure (verified on this machine, 2026-07-26): ToonFlow's openai
vendor imageRequest is a stub (async ()=>{ return ""; }) in
resources/data/vendor/openai.ts. The Agnes image model returns a URL
(b64_json:null), but the adapter only accepts base64 — so it writes a
0-byte file yet marks o_image.state = 已完成. Symptom: UI shows "完成"
but no image; the OSS jpgs are 0 bytes.
Fix (one-time, requires admin): implement imageRequest to
axios.post('{baseUrl}/images/generations', {model,prompt,n:1,size}) and take
item.b64_json or item.url (url → convert to base64 via the existing
urlToBase64). Hot-reloads because ToonFlow re-reads vendor/openai.ts on
every call. The corrected file is at
C:\Users\owlco\WorkBuddy\2026-07-26-16-12-02\_openai_fixed.ts; copy it as
admin:
Copy-Item -Force "C:\Users\owlco\WorkBuddy\2026-07-26-16-12-02\_openai_fixed.ts" `
"C:\Program Files\ToonFlow\resources\data\vendor\openai.ts"
Until that copy is done (ToonFlow install dir is admin-only), ToonFlow-mode
image generation fails silently — prefer pavo-drama's direct Agnes calls for
images, or finish the fix first. Note: ToonFlow's video voice comes from the
video model itself (generate_audio), so its ttsRequest stub is irrelevant to
voiced output — only the imageRequest stub blocks image generation.
Prerequisites
PAVO_API_KEYenvironment variable must be set.
Get a key from <https://agnes-ai.com/> developer console.
On Windows:setx PAVO_API_KEY "sk-...". On macOS/Linux:
export PAVO_API_KEY="sk-..."in your shell profile.
Never hard-code the key in any script. All scripts read it
fromos.environ['PAVO_API_KEY'].- Python 3.10+ with
requestsinstalled (pip install requests).
(scripts/toonflow_bridge.pyis stdlib-only and needs norequests.) - Outbound HTTPS to
apihub.agnes-ai.comand at least one free
image host (0x0.st,litterbox.catbox.moe,sm.ms,imgbb).
Preferred workaround: pass input images as base64data:URIs
directly — Agnes image & video endpoints accept data-URI input, which
avoids flaky free hosts entirely.
For ToonFlow mode you also need ToonFlow running locally
(thetoonflow_bridge.pyhelper talks to its local API only). - Approx 500 seconds/day video-generation quota if you're on a
Token Plan (free tier has lower RPM). The orchestrator enforces
a per-run video-budget guard — seedefaults/audit_rules.yaml. - (Optional) a TTS provider for voice replace. By default the
Chinese voice is generated by the Agnes video model itself
(generate_audio: true→ an audio track inside each clip), so no TTS
is required for voiced output.tts.py+dubbing.pyare only used
when you setdub: trueto replace the native voice (e.g. with
edge-tts:pip install edge-tts).ffmpegmust be on PATH for the
finalize (concat) and optional dub (mux) stages.
Inputs
Assets and episodes are configured via JSON files described in
schemas/. The skill accepts inputs in two ways:
- Inline at invocation — the user provides a JSON spec via the
skill's first argument (file path or inline JSON). - Multimodal chat — the user describes what they want in
natural language; the skill's orchestrator routes the request
toasset_plannerwhich drafts the cards and asks the user to
confirm before generation.
Run modes
pavo-drama plan-assets <spec.json> # Stage 1.1: plan only, no generation
pavo-drama build-assets <spec.json> # Stage 1: full asset pipeline + review gallery
pavo-drama show-assets <run_dir> <run_id> # (re)render the asset-review HTML gallery
pavo-drama scan-novel <novel.md> [--output spec.json] [--title "..."] [--era ...]
# P0: 小说自动导入 — LLM 自动提取角色/场景/道具卡片
pavo-drama lock-characters <spec.json> <run_dir> <run_id> [--style cinema]
# v0.9.0 锁脸: 为所有角色生成多角度参考图 (CharacterDNA)
pavo-drama lock-props <spec.json> <run_dir> <run_id> [--style cinema]
# 道具锁定: 为所有道具生成多角度参考图 (PropDNA)
# 锁定材质/颜色/形状/纹理, 防止"每镜换材质"
pavo-drama produce-episode <run_dir> <run_id> <ep_spec.json> [spec.json]
# write → check → 【storyboard gate】 → shoot
# shot videos (FIRST+LAST FRAME); drama NOT assembled
# v0.9.0: 自动加载 CharacterDNA + 面部保真闸门 + 断点续传
# exit code 3 = storyboard gate rejected the episode
pavo-drama resume-episode <run_dir> <run_id> <episode_id>
# P1: 从断点继续渲染 — 跳过已完成镜, 只重做失败的
pavo-drama grade-episode <run_dir> <run_id> <episode_id> [--style cinema]
# v0.9.0 色彩统一: 用 FFmpeg 对所有镜应用统一色彩分级
pavo-drama assemble-episode <run_dir> <run_id> <episode_id> [--dub] [--voice-replace] [--voice-clone <cfg.json>]
# concat shot videos -> final drama mp4
# --dub: edge-tts dub over the whole track (loses ambient/SFX)
# --voice-replace: v0.9.1 Demucs separation + edge-tts TTS, preserves ambient/SFX
# --voice-clone: v0.9.3 GPT-SoVITS V4 多音色克隆, Demucs 剥离原生旁白 + no_vocals 垫底
# cfg.json 见下「GPT-SoVITS V4 克隆配置」
pavo-drama run <full_spec.json> [--continue|--drama] # Stage 1 + asset review; --continue shoots, --drama assembles
pavo-drama continue-from <prev_run_id> <new_spec.json> # Reuse a previous run's assets
Asset review is a hard checkpoint. build-assets and run (default)
generate assets and stop, rendering assets/review.html for you to inspect.
Review the gallery; if anything needs to change, adjust the spec and re-run
build-assets (or just show-assets to re-render). Only after you approve do
you call produce-episode (shot videos) and later assemble-episode
(final drama). This avoids burning video budget on assets you'd reject.
Card-based inputs
Every run is governed by three card types:
characters[]— seeschemas/character_card.jsonscenes[]— seeschemas/scene_card.jsonprops[]— seeschemas/prop_card.json
Plus a top-level style field (see defaults/style_cinema.json)
and an episodes[] array (per-episode theme + shot budget).
Workflow
Phase 1 — Asset Pipeline
-
asset_planner reads the input spec, validates card schemas,
expands each card into anasset_manifest.jsonlisting every
key visual to be generated. 资产质量体系升级(v0.9.8):
- character 默认 1024x1024 正方形 + 6 pose 资产包(front_anchor
主锚大头照 /side_4545°侧脸 /full_body_riding全身骑马立绘 /
expression_serious|angry|calm情绪特写),用户可用poses覆盖。
- 每个 pose 注入中英双语构图指令(胸像、人脸居中占 60-70%、正面平视、
无杂斑水印文字贴纸),manifest item 带pose_role(= pose)。
- 输出:assets/manifest.json。 -
artists (parallel) runs three workers in parallel:
-character-artist— generates character key visuals via
agnes-image-2.1-flash. Initial pass is text-to-image; if
audit fails, retry via image-to-image (extra_body.image)
with the previous attempt as reference plus the audit notes
as the new prompt.
-scene-artist— generates scene environment visuals.
-prop-artist— generates prop visuals for戏份重道具 only.
Each artist saves PNGs toassets/{type}/{id}/v{n}.pngand a
sidecarmeta.jsonwith seed, prompt, negative_prompt, pose_role. -
upload ships every approved PNG to a free image host
(multi-backend with fallback). The resulting public URL is
what gets passed toagnes-video-v2.0in Stage 2.
Seescripts/upload.pyfor backend priority. -
asset-auditor 两层闸门审计每个资产(先硬后软、失败即短路):
- G1 文件级硬校验(scripts/asset_inspector.py,确定性、零模型成本) —
格式白名单 png/jpeg(禁 webp/透明残缺)、per-type 尺寸
(character 1024~1536 正方形 |w-h|≤64;scene 1024x768;prop 768x768)、
文件大小 ≤10MB、可解码性、(P1)人脸占比 60-70%。失败即 rejected
(error_code 分类),不调多模态。
- G2 多模态三通道(agnes-2.5-flash) — quality(含杂斑/水印/文字/
贴纸缺陷词硬惩罚:defects非空 → DEFECT_DETECTED 直接拒)、
card_compliance、style_consistency。
Failed assets are queued for regeneration withaudit_notes
appended to the prompt. Max retries: 3 (configurable). -
Catalog — approved assets are written to
assets/index.jsonwith this schema per entry:
json
{
"id": "char_alice_front_anchor_v1",
"type": "character",
"ref_id": "alice",
"pose": "front_anchor",
"pose_role": "front_anchor",
"version": 1,
"local_path": "assets/characters/alice/v1.png",
"public_url": "https://0x0.st/X/Y/abc.png",
"seed": 1234567,
"prompt_used": "...",
"negative_prompt_used": "...",
"tags": ["alice", "front", "anchor"],
"quality_score": 0.91,
"audit_notes": "{\"defects\": [], ...}",
"reject_reason": null,
"verified_at": "2026-04-28T12:00:00Z"
}
assets/index.json is the single source of truth for every
downstream call.
资产包 pose 角色语义(消费侧约定)
| pose_role | 用途 | 消费方 |
|---|---|---|
front_anchor |
主锚点大头照,首要人脸锁定图(人脸占 60-70% 正面平视) | lock-characters 写 reference_sheet.front_face;全镜头强制参考 |
side_45 |
45° 侧脸,防转头五官崩坏 | 转头/侧视角镜头 |
full_body_riding |
全身骑马立绘,固定服饰/马匹/装备样式 | 全身/骑马镜头 |
expression_serious / expression_angry / expression_calm |
情绪特写(严肃/发怒/平静) | 台词表情匹配(writer 的 emotion 字段) |
审计错误码(跨文件共享,见架构 §11)
# 文件级(Inspector,G1)
FORMAT_UNSUPPORTED # 格式不在白名单(webp/透明残缺/无法解码)
DIMENSION_TOO_SMALL # 宽或高 < min(per-type)
DIMENSION_TOO_LARGE # 宽或高 > max(per-type)
NOT_SQUARE # |w-h| > square_tolerance(仅 character)
FILE_TOO_LARGE # 文件字节数 > max_file_size
UNREADABLE_IMAGE # 损坏/无法解码
FACE_RATIO_OUT_OF_RANGE # P1:人脸占比 ∉ [min_face_ratio, max_face_ratio]
# 多模态级(Auditor,G2)
QUALITY_BELOW_THRESHOLD # quality score < min_quality_score
DEFECT_DETECTED # defects 命中杂斑/水印/文字/贴纸(硬惩罚,与分数无关)
CARD_COMPLIANCE_BELOW_THRESHOLD
STYLE_BELOW_THRESHOLD
存量重审计(reaudit)
对历史 run 的 assets/index.json 用升级后规则重审计(只读 index.json,
仅新增 reaudit_report.json,不修改生产数据、不重调生成 API):
cd scripts
python reaudit.py <run_dir> <run_id> # 确定性模式(默认,零 API 成本)
python reaudit.py <run_dir> <run_id> --re-score # 重调多模态重打分(可选)
- 三档:
FAIL(质量硬伤:q<0.7 或 character 备注命中缺陷词 → 强制进
regenerate_queue,forced=true)/UPGRADE_NEEDED(仅文件级不达标或尺寸
未达新标准目标尺寸 → 建议重生成,forced=false)/PASS。 - 输出:
assets/reaudit_report.json(summary / items / regenerate_queue),
重生成队列含original_prompt_used+seed+target_size,可直接喂给
dashboard「保留 / 重生成」机制(同 prompt 换 seed 重跑 + 走新审计)。 - 实测:
mangtu_ch001→ 3 FAIL(char_suicong_horseback 0.45 /
char_suicong_front_half_body 0.55 / char_punu_front_half_body 0.65)+
7 UPGRADE_NEEDED。
Asset Review(资产评审)— 人工确认检查点
build-assets 与 run 在完成 audit 后,会自动生成一份自包含
资产评审画廊 assets/review.html:
- 每个资产以 base64 内嵌的方式直接渲染在 HTML 中,无需任何
图床或本地服务器——双击用浏览器打开即可查看全部角色 / 场景 /
道具图。 - 画廊按
character/scene/prop分组,每张卡片展示类型、
资产 ID、Ref、pose、seed、状态、prompt,以及「保留 / 重生成」
勾选框与「修改意见」文本框。 - 同时产出机器可读的
assets/review.json(keep/regen勾选
与notes字段),供下游脚本读取。
流程约定(硬性检查点):
1. 用户打开 assets/review.html,逐张审查。
2. 若全部满意 → 直接进入下一步(镜视频生成)。
3. 若有不满意资产 → 在 asset spec 中调整(prompt、pose、
negative_prompt 等),重新执行 build-assets 仅重渲染被标记
regen 的资产,再次评审。
4. 确认无改动后,再调用 produce-episode(产首尾帧镜视频)
或 assemble-episode(组装短剧成片)。
画廊路径会打印在 build-assets / run 的输出中,并在
run_manifest.asset_review 字段记录。
Phase 2 — Episode Production
-
writer runs
agnes-2.5-flashwith a system prompt that
forces structured output: a shot table
(episodes/<n>/script.json) where every shot references one or
moreasset_idsfromassets/index.json, plus dialogue, action
description, shot direction, and an estimated duration.
v0.8.0: the SYSTEM_PROMPT carries 原著保真铁律 S1–S9, and
build_source_block(episode_spec)injectssource_doc/
source_facts/beats/forbiddenso the model writes FROM the
source novel instead of improvising. See "Structural Gates" above. -
consistency_checker verifies every shot's
asset_idsexist
in the catalog and that the right character/scene/prop is
present for the script's action. Mismatches are reported back to
the writer for one retry. (Era red line is also gated here.)
2b. 【GATE 1】 stage_build_storyboard (v0.8.0, hard abort) compiles the
script into storyboard.json and validates it against the ToonFlow 六铁律
(逐字台词 / 人物完整 / action 词表 / beat_ref 单调 / carry_over / 真实资产 ID).
Then 【GATE 4】 continuity_baseline.build_baseline() locks
position/facing/costume + the story state machine into baseline.json.
Any error raises StoryboardGateFailed and the episode aborts with
exit code 3 — before one frame is rendered.
-
shot-director for each shot — FIRST+LAST FRAME (mode=keyframes) by default.
v0.8.0 wraps every shot in two more gates: 【GATE 2】
bind_shot_assets()is the ONLY legal asset path (bound assets, props
included, are injected as i2iinput_imageson BOTH keyframes, with an
@图Nlegend in the prompt); 【GATE 3】fidelity_gate.gate_shot()
runs immediately beforecreate_videoand a blocked shot is never
submitted (recorded inrender_manifest.blocked_shots):
- Resolve the shot's base reference (single asset → its image; 2+ assets
→agnes-image-2.1-flashmulti-image fusion), then re-render the
opening keyframe in-scene (v0.3.5_render_opening_frame): i2i from
the asset usingframe_start+ cinematic film-still directives, so
frame 0 is a scene-integrated movie frame — never the raw asset 原图
(I2V first-frame conditioning pins frame 0 to the submitted image by
design; no API knob exists to loosen it). Escape hatch:
shot.keep_first_frame: truesubmits the raw asset/fusion image as-is.
Applies to BOTHkeyframesandsinglemodes.
- Resolve the shot's last frame: the writer emitsframe_end
(closing-frame visual description).shot_directorgenerates it by
image-to-image from the first frame (generate_imagewith
input_images=[first_frame]+ theframe_endprompt), preserving
character/scene continuity while changing the end pose. If
frame_endis absent, a default closing-frame prompt is synthesized
(push-in + slight rotation + warmer lighting) so the two keyframes
always differ.
- SubmitPOST /videoswithimage=[first_frame, last_frame],
mode="keyframes",model=agnes-video-v2.0,generate_audio:true
(native Chinese voice),num_frames(8n+1, ≤441),frame_rate,
width/height(multiples of 64),negative_prompt, and aseed
derived from the shot ID for reproducibility.
- PollGET /agnesapi?video_id=<id>untilstatus == completed
orfailed. On503/429/5xx, retry with exponential backoff.
- Download the resulting MP4 (carries a native voice track) to
episodes/<n>/videos/shot_XX.mp4.
- Images are sent as base64 data URIs (Agnes accepts them, and it
avoids flaky free image hosts — no upload step required).
- Video budget guard: before submitting any video task,
sum the planned seconds against the run's remaining quota;
abort early with a clear message if exceeded.
- ⚠️ keyframes 固有行为(已知,v0.3.4 实测):Agnes 的 keyframes
插值会把「首→尾」过渡压进前 ~1s,之后整段定格在尾帧(即前几帧仍
接近首帧原图、1s 后几乎全是尾帧构图)。这是模型行为、非代码 bug。
v0.3.5 起首帧已场景化重渲染,前 ~0.5s 停留的是「电影开场帧」而非
资产原图;若连这段停留也不要,用scripts/head_trim.py检测并裁掉
(python head_trim.py clip.mp4 [--dry-run],保音轨重编码)。
若某镜需要连续、铺满整段的运镜(不接受冻结/定格),给该 shot 设
"mode": "single":只传单张参考图,模型自由生成连续运动(代价是结尾
构图由模型决定,不保证精确落在手持/桌上等指定场景)。
- Per-shotmode:shot.mode∈{"keyframes"(默认), "single"}。
shot_director据此选择first_image+last_image或单image提交,
render 结果记录实际mode。 -
subtitler produces
episodes/<n>/subtitles/episode.srtby
runningagnes-2.5-flashover the shot table (dialogue lines
+ per-shot actual durations frommetadata.seconds). -
manifest — every episode writes
episodes/<n>/manifest.jsonlisting all shots, asset refs,
seeds, and file paths, so the episode can be reproduced or
extended. -
finalize + dub (DRAMA ASSEMBLY — OFF BY DEFAULT). The final
short-drama MP4 is not produced byproduce-episode/runby
default (per the user's "短剧暂不产出" request).produce-episodestops
after the per-shot videos (each already carries a native Chinese voice
track from the video model'sgenerate_audio). To assemble the drama:
-assemble-episode <run_dir> <run_id> <episode_id>→
stage_finalize_episodeconcatenates the shot clips into one 9:16 MP4
(episodes/<n>/episode.mp4), preserving each clip's native audio; then
stage_dub_episode(only if--dub) replaces the voice with
tts.synth_episode+dubbing.dub_episode.
- Or runrun --drama/ setproduce_drama: trueto assemble inline.
-dubis off by default (the native video-model voice is used).
Enable withdub: truein the episode/run spec,PAVO_DUB=1, or
assemble-episode --dub.
Chinese Voice — native from the video model (not a TTS)
Key fact (verified 2026-07-28 against ToonFlow's own backend): the
Agnes video model (agnes-video-v2.0) generates speech itself. You get a
voiced clip by passing generate_audio: true to the video task. The real
endpoint is POST /videos (the POST /contents/generations/tasks path is a
ToonFlow relay that 404s on apihub.agnes-ai.com):
# Single-image mode (one starting frame):
POST {BASE_URL}/videos
{ "model":"agnes-video-v2.0", "prompt":"<shot prompt / dialogue>",
"image":"<first data: URI>", "width":576, "height":1024,
"num_frames":121, "frame_rate":24, "generate_audio":true, "audio":true }
# -> { "video_id": "<taskId>" }
# FIRST+LAST FRAME mode (animates opening -> closing frame):
POST {BASE_URL}/videos
{ "model":"agnes-video-v2.0", "prompt":"<shot prompt / dialogue>",
"image":["<first data: URI>", "<last data: URI>"], "mode":"keyframes",
"width":576, "height":1024, "num_frames":121, "frame_rate":24,
"generate_audio":true, "audio":true }
# -> { "video_id": "<taskId>" }
GET {BASE_URL}/agnesapi?video_id=<taskId>
# status "completed" -> <video_url> (MP4 WITH native Chinese audio)
ToonFlow confirms this mechanism: its vendor/toonflow.ts and backend
set body.generate_audio = true for audio-capable video models, and its
ttsRequest is a stub (return "") — i.e. the voice is not produced
by a TTS engine. The same openai/Agnes vendor is what pavo-drama uses,
so agnes_client.generate_video(..., audio=True) reproduces ToonFlow's
voiced storyboard output exactly. agnes_client.generate_video takes
first_image+last_image and emits the mode=keyframes body above.
Optional TTS voice replace (only when dub: true), via tts.py:
| Provider | Setup | Notes |
|---|---|---|
edge-tts (default) |
pip install edge-tts |
Free Microsoft Chinese neural voices. No key. Loses ambient/SFX when used in dub. |
gpt-sovits (v0.9.2) |
venv pavo-sovits, 6 models ready |
Zero-shot voice clone from 3-8s real-voice ref. V4 48kHz + BigVGAN, GPU RTX 2070S. See setup below. |
azure |
pip install azure-cognitiveservices-speech |
Stub — implement SSML call. |
aliyun / tencent / minimax / cosyvoice |
set TTS_PROVIDER + env |
Placeholder stubs. |
GPT-SoVITS V4 setup (one-time, done on this machine):
- Venv: C:/Users/owlco/.workbuddy/binaries/python/envs/pavo-sovits/ (PyTorch 2.6.0+cu124)
- Models under C:/Users/owlco/.workbuddy/GPT-SoVITS/GPT_SoVITS/pretrained_models/:
GPT s1v3.ckpt (149MB), SoVITS gsv-v4-pretrained/s2Gv4.pth (734MB),
vocoder gsv-v4-pretrained/vocoder.pth (56MB), BigVGAN bigvgan_generator.pt (430MB),
BERT chinese-roberta-wwm-ext-large/ (1.3GB), HuBERT chinese-hubert-base/ (1.5GB),
G2PW GPT_SoVITS/text/G2PWModel/ (635MB), fastText fast_langdetect/ (125MB).
- Deps: soundfile, opencc-python-reimplemented (already installed in pavo-sovits).
- Inference: cd to repo root, version=v4, sys.path must include GPT_SoVITS/.
- Template: tts_demo.py — change_gpt_weights → drive change_sovits_weights generator → get_tts_wav → sf.write.
- v0.9.3 批量克隆用 scripts/gptsovits_clone.py(隔离助手,voice_replace.py
以 subprocess(cwd=repo_root) 驱动)。⚠️ 所有路径用 C:/Users/...,绝不用
/c/Users/...(MSYS 风格会被 Python 解析成 C:\c\Users\...)。
GPT-SoVITS V4 克隆配置(传给 voice_replace.py --voice-clone)示例:
{
"shot_videos": ["s01.mp4", "s02.mp4"],
"dialogues": [
[{"role": "narr", "text": "旁白第一句"},
{"role": "wangmang", "text": "王莽:寡人受命于天"}],
[{"role": "narr", "text": "旁白第二镜"}]
],
"refs": {
"narr": {"wav": "refs/ref_narr.wav", "text": "参考音对应文本"},
"wangmang": {"wav": "refs/ref_wangmang.wav", "text": "参考音对应文本"}
},
"role_to_ref": {"narr": "narr", "wangmang": "wangmang", "wanghuang": "wangshe"},
"output": "episode_cloned.mp4",
"amb_vol": 0.30, "voice_vol": 1.0
}
refs的音色样本建议用 edge-tts 联网合成(合规,规避真人声版权),见 EP20gen_ep20_refs.py。- ⚠️
dialogues文本必须纯中文:含英文词(如 "AI")会触发 GPT-SoVITS 英文分支
import wordsegment(未装 → 崩溃)。英文概念改成中文(如"人工智能")。
Per-character voices: pass voice_map = {character_id: "zh-CN-XXXNeural"}
(or set per-card voice_traits). The narrator uses zh-CN-YunyangNeural.
Mux command (what dubbing.py runs, only when replacing the native voice):
ffmpeg -i episode_base.mp4 -i full_audio.mp3 \
-c:v copy -c:a aac -shortest episode_dubbed.mp4
> The full audio track is built shot-by-shot in the SAME order as the
> concatenated base video, so the two line up by construction — no
> manual timeline offsets needed.
Episode Duration & Storyboard Discipline (时长是剧本/分镜问题)
A short drama's length is decided at the script + storyboard stage,
NOT by the video render parameters. The render just executes the
storyboard. Common failure: a 4-shot, 13s episode — far below the
60–90s standard for vertical micro-dramas.
Defaults raised in this skill (writer + orchestrator):
- shots_per_episode 6 → 12
- seconds_per_episode 30 → 60
- Single shot target duration 5s (num_frames=121 @ 24fps — the
shortest single-shot video this pipeline has verified is 5s).
Rules the writer now enforces (SYSTEM_PROMPT):
- Every shot MUST carry dialogue[] or a narration line. Silent
shots only allowed with explicit silent: true.
- The summed duration_seconds must reach (not merely stay under)
the episode budget; if short, split beats into more shots.
- The orchestrator retries the writer once if the storyboard is too
short (shot count < 60% of target, or summed duration < 60% of budget)
before spending any render budget.
Target for a qualified vertical episode:
- ≥ 60s, ideally 75–90s (ToonFlow's own EP01 reference is 3m30s).
- 12–18 shots × 5s, each carrying dialogue or narration.
Structural Gates (v0.8.0) — 四道闸门,失败即阻断
> 核心原则:约束要么是结构闸门(机器可判定、失败即阻断),要么就不算约束。
> 写进 prompt 的否定句只能做审美兜底 —— 可验证的问题不要再用软提示去修。
> 这套设计吸收自 ToonFlow 的多闸门管线(o_novel → o_script → extractAssets →
> 分镜表(六铁律) → 分镜面板(associateAssetsIds + 基准表 + 忠实性校验) → 分镜图 → video)。
| # | 闸门 | 实现 | 拦什么 | 失败行为 |
|---|---|---|---|---|
| 1 | 分镜结构闸门 | scripts/storyboard.py validate_storyboard() |
台词删改、人物遗漏、action 越界、beat 跳跃/编造、carry_over 缺失、假资产 ID | 抛 StoryboardGateFailed,整集中止(rc=3),零渲染 |
| 2 | 资产结构化绑定 | shot_director.bind_shot_assets() |
道具"引用了但没画出来" | 绑定失败抛 ShotBlocked(不再静默兜底) |
| 3 | 提交前忠实性校验 | scripts/fidelity_gate.py gate_shot() |
假资产、台词漂移、时长/镜序错、编造情节、时间逆转(30 词表)、状态漂移、跨镜不一致 | 该镜 continue 不提交,记入 blocked_shots |
| 4 | 跨镜基准表锁定 | scripts/continuity_baseline.py build_baseline() |
position/facing/costume 漂移、剧情状态倒退或抢跑 | 报 state_declared_early / carry_over 不一致 → 走闸门 1 |
闸门 1:storyboard(六铁律代码化)
orchestrator.stage_build_storyboard() 插在 stage_write_episode 与
stage_shoot_episode 之间,产物落在 episodes/<n>/:
storyboard.json 结构化分镜(panel / beat_ref / carry_over / state / 绑定资产)
baseline.json 跨镜基准表(position / facing / costume / 状态机)
storyboard_report.json 校验报告(即使通过也写盘,便于审阅)
ToonFlow 六铁律 → 代码规则:
- 台词零删改 — 逐字 diff。
_verbatim_present()按引号 / 分隔符做边界精确匹配,
「留下吧。」不会被那就留下吧。蒙混过关(朴素子串会)。台词统一用「」包裹。 - 出场人物完整 — 剧本里说话/在场的人必须都在该 panel 的绑定资产里。
- 只描述动作状态 — action 走词表 lint,禁光影/色调/配乐等非动作描述。
- 长台词拆镜 — 按时长预算校验,超长必须拆。
- 资产真实 — 只允许引用 catalog 里已存在的 ID,禁止编造。
- 依剧本顺序、不遗漏不新增 —
beat_ref必须存在于beats且单调递增。
闸门 3:fidelity_gate 的两部分
- Part A(默认开启,纯本地、确定性) A.1–A.7:资产真实 / 台词逐字 / 时长镜序 /
禁编造 / 禁时间逆转词表 / 状态不漂移 / 跨镜一致。零网络调用,跑得飞快。 - Part B(默认关闭) 多模态视觉验收 —— 把渲染出的首/尾帧喂给
agnes-2.5-flash
核对「道具是否真的在画面里」。需要网络请求,故默认visual=False。开启方式:
shot_director.py --visual-gate,或在 spec / episode_spec 里写"visual_gate": true。
让 writer 吃原著(治「剧本没按小说来」的源头)
在 episode_spec 里声明这四个字段,writer.build_source_block() 会把它们注入提示词:
{
"source_doc": "docs/莽途_EP01_修正shot_table.md",
"source_facts": ["蒲奴的书简是从衣襟滑落,不是坠马摔落", "李贵是王府采买管事,不是西市商人"],
"beats": ["B01 蒲奴驿道遗简", "B02 张二车轮碾到", "..."],
"forbidden": ["EP01 不得出现书灵睁眼/大盛", "EP01 不得出现『天意』台词(属 EP02 钩子)"]
}
source_doc 是权威修正 shot table(原著事实 + 节拍表 + 逐字台词 + 状态机 +
资产绑定 + 禁止词表)。这是治问题①的关键 —— 光靠 SYSTEM_PROMPT 的铁律不够,
必须把原著本体送进上下文,并用闸门 1 逐字校验产出。
运行测试
cd ~/.workbuddy/skills/pavo-drama/scripts
python -m unittest discover -s tests # 103 tests (79 规则 + 24 对抗)
对抗测试(test_gates_adversarial.py)专攻绕过路径:子串台词、状态抢跑、
逗号分句、carry_over 覆盖等。改闸门代码后必须重跑,绿了才算数。
Project red lines (historical / consistency constraints)
When the subject is a period / historical drama (e.g. the user's 莽途
project — a Western-Han 西汉 setting), the era red line is now ENFORCED IN
CODE (v0.5.0), and since v0.6.0 it is released per-scene / per-episode
rather than one global ban. The single source of truth is the era block in
defaults/style_cinema.json (label / positive / negative) — the master
blacklist of cross-era objects. A cross-era object is forbidden EVERYWHERE by
default, EXCEPT in a specific scene/episode that explicitly declares it via
allowed_cross_era (see below). It is folded into every generation prompt's
negative list + suffix (asset_planner, shot_director), injected as a hard
rule into the writer agent (writer.write_episode SYSTEM_PROMPT rule 10 +
USER_TEMPLATE 时代约束 block), and gated by the consistency checker
(consistency_checker._check_era_consistency → error on visual fields, warn on
spoken text, minus any allowed_cross_era for that shot). The rules below
describe WHAT the code enforces:
- 人物一致性 (character consistency): each character's appearance must
match their reference character sheet exactly across every shot; do not
alter looks or add features not in the setting. - 时代一致性 (era consistency) + per-scene / per-episode release: for a 西汉
background, forbid ANY non-Western-Han / cross-era / modern object — glasses,
clocks, phones, plastic, glass curtain walls, power lines, electric lights,
motor vehicles, neon, modern signage, Latin letters / Arabic-numeral
signage, AC outdoor units, etc. Theera.negativelist is the enforced,
editable master blacklist; add/remove terms there (bilingual preferred) and
every pipeline stage picks them up automatically.
但莽途是穿越设定,跨时代器物可能是剧情需要 —— 因此实行「按场景 / 按剧集放行」:
在 场景卡scenes[].allowed_cross_era或 剧集设定
episodes[].allowed_cross_era(或单个shot.allowed_cross_era)上列出该处
允许出现的跨时代器物,它们只在该 scene_id / 本集被放行,其余未放行处仍全局禁。
未声明放行时,红线对全片生效(默认全禁)。
Per-asset prompt rules (from toonflow-backfill-storyboard):
role → self-reference the character sheet; scene/tool → add the
anti-cross-era constraint. Apply these before generation so the audit pass
(Phase 1.4) can verify compliance.
Configuration
defaults/style_cinema.json
Holds the default visual style prompt fragments and negative
prompt blacklist shared across all generations. Key keys:
global_prompt_suffix/global_negative_prompt— appended to every
generation prompt. v0.5.0: the era red line is folded into these two at
runtime from theerablock below, so do NOT hand-duplicate era terms here.era— the enforced era red line (v0.5.0). Editlabel(human-readable
period),positive(a【严格历史时代约束】directive appended to prompts),
andnegative(bilingual blacklist of cross-era / modern objects). Changing
this block changes era enforcement across the whole pipeline with no code
edits. To set a different period (e.g. Tang, or a fantasy setting), swap this
block or create a newstyle_*.jsonpreset and reference it via the run
spec's"style"field.
defaults/audit_rules.yaml
Holds audit thresholds (min resolution, max artifact score, etc.)
and per-action retry counts. v0.9.8 升级:白名单去 webp、尺寸 1024~1536、
per-type dimension_rules(含正方形容差 + nominal_size 目标尺寸)、face 段
(P0 enabled=false 多模态估算 / P1 insightface 硬校验)、defect_keywords
(中英双语杂斑/水印/文字/贴纸词库)、quality prompt 返回 score+face_ratio+defects。
审计侧配置源;生成侧常量在 style_cinema.json,两处必须一致(架构 §11)。
Free image host backends (priority order)
0x0.st— 48h anonymous retention, 100MB max. Default.litterbox.catbox.moe— 72h anonymous retention.sm.ms— requires free API token (optional).imgbb— requires free API token (optional).
Backends are tried in order; the first to return 200 wins. A 30s
timeout per attempt; 3 attempts per backend before falling through.
Failure modes
| Symptom | Likely cause + remedy |
|---|---|
KeyError: PAVO_API_KEY |
Environment variable not set. Run setx PAVO_API_KEY "sk-..." (Windows) or export PAVO_API_KEY=... (macOS/Linux). |
401 Unauthorized |
Wrong API key, or key has been rotated. Re-set the env var. |
429 Too Many Requests |
RPM hit. Free tier is 20 RPM text; back off or upgrade to Token Plan. |
image model not found |
You passed agnes-image-2.1-flash but the endpoint only has agnes-image-2.1-flash (ToonFlow's live model). Override the model= arg. |
Video task failed |
Most often caused by an inaccessible image URL — verify the public URL returns 200 with image/* content type. |
Video submit 429 / 503 video_queue_full |
Agnes Video V2.0 is heavily rate-limited in practice (~1 request/min) and the server queue saturates. Submit videos serially (never parallel), space submits ≥120s, and wrap submission in exponential backoff (base 30s, cap 180s) that retries on 429/503/502/500/timeout/SSL EOF/ProxyError. Disable _post_json's internal 2/4/8s retry (agnes_client.DEFAULT_MAX_RETRIES=1) or it burns the quota before your slow backoff kicks in. |
| Video source resolution inconsistent (e.g. 448×832 & 704×1280) | The video model returns "≈9:16" sizes, not exact. Never -c copy concat mixed resolutions (broken frames / misalignment). Scale+pad every segment to one uniform size (e.g. scale=576:1024:force_original_aspect_ratio=decrease,pad=576:1024:(ow-iw)/2:(oh-ih)/2) then re-encode. |
Poll GET ProxyError: Remote end closed connection |
Transient proxy drop during result polling. Wrap get_video_result in retry-on-transient (same predicate as above). If a submit already returned a task_id but polling died, use a rescue script that re-polls the known task_id instead of re-submitting (re-submitting wastes a queue slot and may create a duplicate task). |
| All image hosts fail | Either no network, or all free hosts are down. Preferred workaround: pass the input image as a base64 data: URI directly (extra_body.image=[data_uri]) — Agnes image & video endpoints both accept data-URI input, which is more reliable than any free host. |
400 num_frames must follow 8n+1 |
Use the helper in agnes_client.py::closest_valid_num_frames. |
400 width/height must be multiple of 64 |
Use closest_valid_dimension in agnes_client.py. |
| All image hosts fail | Either no network, or all free hosts are down. Configure a paid backend (sm.ms/imgbb) by setting SMMS_TOKEN or IMGBB_TOKEN env vars. |
| Video exceeds 500s/day quota | The orchestrator's budget guard aborts before submission. Either wait until tomorrow (UTC reset), upgrade the Token Plan, or reduce seconds_per_shot / num_shots. |
| ToonFlow image "完成" but 0 bytes | Known ToonFlow bug: openai vendor imageRequest stub writes empty files. Fix = implement imageRequest + admin-copy openai.ts (see ToonFlow Integration §6). Until fixed, generate images via pavo-drama direct calls. |
ToonFlow API 401 |
Missing/ bad JWT. Re-sign with the correct o_setting.tokenKey (toonflow_bridge.py tokenkey). Port may have changed — re-run discover. |
| ToonFlow connection refused | ToonFlow not running or port changed. Run toonflow_bridge.py discover; start ToonFlow if needed. |
| Episode has no voice / silent output | By default the clip ships with a native audio track from the video model (generate_audio) — this track carries voice + ambient + SFX together (wind, hoofbeats, cloth rustle, etc.). If a clip is silent: (1) confirm agnes_client.generate_video(..., audio=True) was used; (2) confirm finalize/_normalize_clip preserved the audio (_clip_has_audio). Do NOT fix silence by switching to audio=False + edge-tts dub — that drops all ambient/SFX (see below). |
ImportError: edge-tts not installed |
Only needed if you set dub: true to replace the native video-model voice. The default native voice needs no TTS provider. ⚠️ edge-tts dub discards the native ambient/SFX track — only use when ambient is not required. |
| 视频语音是英文 / 非中文(部分或全部镜头) | 原生语音语言按 prompt 文本检测,无 language 参数,不可控(实测同一集仅第 1 镜中文、其余英文)。修法(保留原生音轨,不要静音):① 每段视频 prompt 以中文台词/旁白开头主导;② 显式追加指令「全程使用中文普通话配音;所有台词与旁白用中文朗读」;③ 个别仍跑偏的镜头用换 seed / 微调 prompt 单镜重生成。绝不用「静音+配音」绕路(会丢环境音)。 |
| 静音+配音导致环境音/音效丢失 | 把 audio=False 静音视频再用 edge-tts 重新配音,会彻底丢失原生音轨里的环境音与音效(风声、马蹄、布料摩擦等)。这是成片硬伤,禁止此方案。 保留原生 audio=True 音轨,语言问题只在 prompt 层修正(见上条)。edge-tts dub 仅作「换声覆盖」且会丢环境音,只在无环境音需求时考虑。 |
| 语音重叠(双声齐发) | 给原生视频做 AI 配音(GPT-SoVITS / edge-tts)时,若直接把配音混到原始音轨上,原音轨里的原生中文旁白会和 AI 配音同时说话 → 两道人声重叠。根因:原生 audio=True 音轨 = 旁白+环境+音效 三位一体,不是纯环境音。修法(v0.9.3 已默认): 先用 Demucs --two-stems=vocals 分离,只保留 no_vocals(环境+音效)、丢弃 vocals(原生旁白),再把 AI 配音叠到 no_vocals 上(amb_vol≈0.3)。voice_replace.py --voice-clone 自动这么做。 |
GPT-SoVITS 英文词崩溃 ModuleNotFoundError: wordsegment |
配音文本含英文 token(如 "AI"、"CPU")时,GPT-SoVITS 走英文分支会 import wordsegment,而 venv 没装 → 合成整句崩。修法: ① 把英文概念改纯中文("AI"→"人工智能");② 或 pip install wordsegment;③ 或预处理把英文词替换。EP20 就是 s17 一句"AI助理"触发,改成"人工智能助理"即过。 |
Windows 路径 /c/Users/... 找不到文件 |
在 Git Bash / MSYS 里写的 /c/Users/owlco/... 传给 Windows Python 时,会被解析成 C:\c\Users\owlco\... → 文件不存在。修法: 脚本参数一律用带盘符的 C:/Users/owlco/...(正斜杠即可)。GPT-SoVITS 克隆尤其要注意。 |
| 道具每镜长相不一样 / 道具漂移 | PROP 仅是文本模板拼进 prompt,每镜从文本重新生图 → 道具每镜漂移。修法:先生成一张主资产图(如墨玉书简母版);每镜以双图 i2i 把主资产合成进场景静帧(input_images=[base静帧, 主资产URI],prompt 要求「保持角色/场景不变、仅道具外观完全一致」),全集体道具一致;角色/场景镜不碰。orchestrator._build_prop_i2i 从 catalog 自动构建 prop_i2i,shot_director.shoot_episode(prop_i2i=...) 承接收下。 |
| 视频开场是资产原图 / 首帧像贴了张静态照片 | I2V 首帧条件化是模型设计特性,无 API 旋钮可关。v0.3.5 已默认把首关键帧场景化重渲染(_render_opening_frame),第 0 帧不再是资产原图;个别镜头仍显静止开场时单镜重生成即可。⚠️ 连续剧流水线不得用 scripts/head_trim.py 去头:它会裁掉开场近定格头,但首句台词常在开场 0.5s 内,裁剪必切掉台词、打断镜头连续性。要保留资产原图作首帧时设 shot.keep_first_frame: true。 |
StoryboardGateFailed / 退出码 3 |
闸门 1 拦下了整集(这是设计行为,不是 bug)。打开 episodes/<n>/storyboard_report.json 看 errors[]:dialogue_mismatch=台词被改、beat_ref_missing/out_of_order=节拍跳跃或编造、asset_not_in_catalog=编造资产 ID、carry_over_missing=跨镜衔接没写、state_declared_early=剧情状态抢跑。修剧本或补 episode_spec.beats/source_facts 后重跑,不要绕过闸门。 |
某几镜 blocked、成片缺镜 |
闸门 3(fidelity_gate)在提交前拦下了它们,见 render_manifest.blocked_shots 与日志里的 fidelity gate BLOCKED。常见码:time_reversal_term(时间逆转 30 词表命中)、asset_not_bound(道具没进 i2i)、dialogue_drift、state_drift、carry_over_inconsistent。这是幻觉被挡住了,不是渲染失败 —— 修分镜/台词后重跑该镜。 |
ShotBlocked: prop composite failed |
闸门 2 的 fail-fast。v0.8.0 前道具合成失败会静默兜底(于是"引用了却没画出来")。现在直接阻断。检查该 prop 的 public_url/data_uri 是否可用(母版是否 0 字节、图床是否挂了)。 |
| 想开视觉验收(看帧核对道具真上镜) | fidelity_gate Part B 默认关闭(要发网络请求)。开启:shot_director.py --visual-gate,或 spec/episode_spec 写 "visual_gate": true。 |
| 改了闸门代码 | 必须重跑 cd scripts && python -m unittest discover -s tests(103 tests,含 24 个对抗性绕过测试)。绿了才算数。 |
| Episode too short (< 60s) | This is a script/storyboard problem, not a render parameter. Raise target_shots (default now 12) and duration_budget_seconds (default now 60) in the spec; ensure every shot carries dialogue/narration. The orchestrator retries the writer once if the storyboard is < 60% of budget. Don't fix length by padding num_frames alone. |
| Single shot < 5s looks choppy | Set per-shot duration ≥ 5s (e.g. 5s) — the shortest single-shot video this pipeline has verified. Wider 9:16 buckets observed: 448×832 / 704×1280. |
推理模型空响应(content="" 但无异常) |
agnes-2.5-flash 是推理模型:小 max_tokens cap(200/2048)会被 reasoning_content 吃光 → content=""、finish_reason=length、text_tokens=0,JSON 解析报 Expecting value: line 1 column 1 或审计全 0 分。无 traceback、极难排查。修法:所有 chat 调用 max_tokens=小值 → enable_thinking=False(reasoning 仍在但不再抢占正文);长输出(writer 24 镜 JSON)需 max_tokens=32768。已修复:asset_auditor / fidelity_gate / character_lock / prop_lock / subtitler / writer。 |
shot_id int/str 失配(跨模块查不到) |
script.json 的 shot_id 是 int(1,2,3…),render_manifest.json 是 str("1","2"…)。任何 by_id = {s["shot_id"]: s} + by_id.get(shot["shot_id"]) 都会查空 → 症状:finalize「no completed shot clips」、voice-clone「shot N video not found」、_assemble_with_voice_replace 同样。修法:by_id 的 key 统一 str(...) 化(已修复 orchestrator 三处)。这是仓库反复出现的坑,跨模块 match 一律 str()。 |
| voice-clone 后全片时长骤减(如 121s→68s) | voice_replace.py Step 3 原用 [0:v]setpts=PTS*scale 把视频缩放到配音时长 T:旁白句 T 只有 1.4-4.9s → 每镜被压短,24 镜全片只剩 57%。修法(v0.9.7):[0:v]copy 保持视频原时长 + 配音 apad=pad_dur={vdur},atrim=0:{vdur} 对齐 vdur。注意配音短于视频时会静音填充,长于视频会裁剪(对齐后集末统一收束)。 |
| voice-clone 只换了对白、旁白还是原声 | stage_voice_clone_episode 只从 shot["dialogue"] 取行,无对白镜跳过 → 旁白镜保留原生(不稳定的)Agnes 语音。修法(v0.9.7):无对白但有 narration 的镜自动补 {"role":"narr","text":narration},cfg 里 role_to_ref 配 narr→narr 参考音即可全克隆。 |
| Demucs 全程 CPU、GPU 闲置(利用率 <15%) | Demucs 原硬编码 device="cpu"(v4.1.0 GPU segment-alignment bug 默认 CPU),24 镜分离是 voice-clone 最大耗时。修法(v0.9.7):demucs_separate_vocals 支持 PAVO_DEMUCS_DEVICE(默认 cuda)+ --segment 4(实测 8GB 显存稳定,规避 segment bug)+ cuda 失败自动回退 cpu。实测 GPU 利用率 14%→32%、显存 812MB→2935MB。 |
_load_style 找不到 style 预设 |
asset_planner._load_style 只在 run_dir/defaults/ 下找 style_<name>.json,不查 skill 的 defaults/。新 run 直接 build-assets 会 FileNotFoundError: no style preset found。修法:先把 skill defaults/style_cinema.json(+audit_rules.yaml)拷到 <run_dir>/<run_id>/defaults/ 再跑。orchestrator 默认 run_dir 是 skill 的 ../runs/,也需显式传项目 runs 路径。 |
When NOT to use this skill
- Single static image generation — just call
agnes-image-2.1-flashdirectly via curl; you don't need this
orchestrator. - Real-time / interactive video — Agnes Video V2.0 is async
and takes minutes per shot; this skill is for offline batch
production. - Long-form films (>18s per shot, >500s total) — model and
quota limits make this impractical. Split into multiple runs
on multiple days. - 莽途-type novel projects in ToonFlow — note that in ToonFlow a
projectType=scriptproject may be a novel script, not an AI
short drama (e.g. 莽途, projectId1785072535570, synced from
192.168.2.125). Don't assume every ToonFlow project is a
short-drama pipeline; verify before driving video generation.
Cross-episode consistency
To produce Episode 2 reusing Episode 1's assets:
pavo-drama continue-from <prev_run_id>
This re-opens assets/index.json from the previous run, exposes
it to the writer, and the writer is instructed to reuse existing
asset_ids wherever possible. New characters/scenes/props trigger
an incremental Stage 1 run before Stage 2 begins.
Safety
- Never hard-code
PAVO_API_KEYin any file. Always read from
os.environ. - Never log the full API key in any output.
agnes_client.py
masks the key tosk-***...***in all log lines. - Generated videos and images may include model-generated content
subject to Agnes AI's terms of service; respect their usage
policy when redistributing. - ToonFlow mode drives a third party: image/video generation
sends content to Agnes via ToonFlow. Confirm cost and rights
before triggering generation through the ToonFlow API.