From 4868721e26d3154f43790dbd086eede9e0ea9fa2 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Tue, 15 Sep 2026 22:33:44 +0800 Subject: [PATCH 01/25] =?UTF-8?q?docs(expert-wx-channel):=20=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E9=93=BE=E6=8E=A5=E6=94=B9=E7=94=A8=E6=88=B7=E5=90=8E?= =?UTF-8?q?=E8=A1=A5=20+=20=E8=AF=9D=E9=A2=98=E6=A0=87=E7=AD=BE=E6=AE=B5?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E7=A1=AC=E7=BA=A6=E6=9D=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wechat-channels-publish: 删 Step 9 分享面板取链接(审核/转码中常不可用,实践中难走通);Step 8 不必等转码,确认作品上列表即 close 回报;首次入库不传 --publish-url,用户后补链接后相同 --source-folder 重跑 upsert 升级(同 wx-mp-publisher 机制) - 话题标签段硬约束(工具 Step 6 + workflow Step 4 两端设防):只能半角 # + 空格分隔,禁分组前缀(【三层标签】)、分隔符(/)、全角 #——混入即整行标签不被视频号识别;DNA 分层标签策略只用于挑标签,结构标记不进正文 - content-production: Step 6/7 同步链接后补机制 - published-track: 修正交叉引用 Step 5 → Step 6(视频描述填写步骤) --- .../tools/wechat-channels-publish/SKILL.md | 28 ++++++++----------- .../workflows/content-production.md | 12 ++++---- crews/main/skills/published-track/SKILL.md | 2 +- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/crews/main/skills/expert-wx-channel/tools/wechat-channels-publish/SKILL.md b/crews/main/skills/expert-wx-channel/tools/wechat-channels-publish/SKILL.md index 0aca54fd..feb342c2 100644 --- a/crews/main/skills/expert-wx-channel/tools/wechat-channels-publish/SKILL.md +++ b/crews/main/skills/expert-wx-channel/tools/wechat-channels-publish/SKILL.md @@ -12,7 +12,7 @@ description: 通过 camoufox-cli 持久化 session wechat-channel 发布视频 **业务页 `snapshot` 一律加 `-s "wujie-app"` 作用域**(下文简写 `snapshot -s`):wujie 页面里主文档与子应用各有一个 body,整页 `snapshot` 会报 `locator('body') resolved to 2 elements` strict violation,拿不到任何 ref。只有登录页(无 wujie)可整页 snapshot。`click` / `fill` / `type` 按 ref 操作;`upload` 支持 ref 或 CSS 选择器(底层 Playwright setInputFiles,穿透 shadow DOM),无需 CDP hack。 **输入**:本地视频文件(`.mp4` / `.mov` / `.avi` / `.webm`)、**视频描述**(含话题标签,最长约 300 字)、**短标题**(6-16 字)。发布页改版后两项都能填,官方明确「填写短标题会获得更多流量」,因此**两项都必须填**,都由 main agent 拟定后交给本工具。 -**输出**:视频号已发布作品;能取到时附带公开链接(`https://weixin.qq.com/sph/xxxx`)。 +**输出**:视频号已发布作品。本工具**不抓取作品公开链接**——管理后台分享面板路线实践中不可靠(审核 / 转码中常不可用),链接由用户后补,调用方入库时升级(见文末「入库衔接约束」)。 > **短标题只在发布页存在**:作品管理页与 `wx-channel-engagement` 抓取都拿不到短标题,所以入库与匹配一律只用视频描述(见文末「入库衔接约束」)。 @@ -118,6 +118,13 @@ camoufox-cli --session wechat-channel --persistent --json upload "input[type=fil - 校验不过(缺段/串位):重跑 1 聚焦后 execCommand('selectAll') + execCommand('delete') 清空,再从 2 重插 ``` +> **话题标签段格式(硬约束)**:标签段只能是半角 `#` + 空格分隔的 `#标签` 项,整行不得出现任何其他字符——不加前缀或分组标题(如「【三层标签】」)、不加分隔符(/、/、—、顿号)、不用全角 `#`。混入这些字符会导致**整行标签全部不被视频号识别渲染(失效)**。DNA 的分层/分组标签策略只用于挑标签,结构标记不进正文。 +> +> - ✅ `#AI员工 #开源工具 #影视解说 #Wiseflow #一人成团 #Agent327` +> - ❌ `【三层标签】#影视解说 #反转 #悬疑短片 / #AI商机监控 #AI客服 #自媒体获客 / #遗憾 #十年` +> +> 定稿文案已含违规字符时,先清洗成纯标签行再插入,不要照插。 + **短标题**——真实 ``(placeholder 含「短标题」,官方提示形如「填写短标题有机会获得更多流量」),aria snapshot 有独立 ref,直接填: ``` @@ -148,24 +155,11 @@ camoufox-cli --session wechat-channel --persistent --json upload "input[type=fil 等待 4 秒后检查(`url` 命令 + `snapshot -s "wujie-app"`): - 页面自动跳转到视频管理列表页 - 或 URL 变为 `https://channels.weixin.qq.com/platform/post/list` -- 刚发表的作品通常在第一个。但可能处于转码中——封面缩略图为灰色,转圈。每隔 5 秒 snapshot -s 看转码是否完成(封面缩略图出现),完成后才能取链接。 - -### Step 9: 获取已发布视频链接 - -发布成功后,在视频号管理后台的视频列表页获取视频公开链接: - -``` -1. snapshot -s "wujie-app" 找到刚发布的视频(列表第一条,或按完整视频描述匹配)ref -2. snapshot -s 找该视频的"分享"按钮 ref → click -3. snapshot -s 在弹出的分享面板中找"复制视频链接"按钮 ref → click -4. eval 从剪贴板读取链接: - camoufox-cli --session wechat-channel --persistent --json eval "navigator.clipboard.readText()" - 链接格式通常为 https://weixin.qq.com/sph/xxxxxx(sph 即视频号拼音缩写) -``` +- 刚发表的作品通常在第一个;处于转码中(封面缩略图灰色、转圈)属正常,不影响发布成功判定,**不必等转码完成**。 > 管理页若 `-s "wujie-app"` 报作用域不存在/为空(该页未走 wujie 容器),退回整页 snapshot。 -> **注意**:如果刚发布的视频还在审核中,"分享"按钮可能不可用。此时可先完成发布记录(publish_url 留空),待审核通过后再补充链接。 +确认发布成功后 close session 并回报。**不要尝试在管理后台抓取作品链接**——分享面板路线实践中不可靠(审核 / 转码中常不可用),已从本流程移除。回报时告知用户作品已提交,请其后续在手机端或后台「分享」中获取作品链接(`https://weixin.qq.com/sph/xxxx`)提供给我们,由调用方按「入库衔接约束」补录升级。 --- @@ -282,3 +276,5 @@ camoufox-cli --session wechat-channel --persistent --json upload "input[type=fil > **`published-track record --platform wx_channel --title` 必须传 Step 6 填的完整视频描述**(含 hashtag,最长约 300 字);**短标题不入库**。 原因:作品管理页只展示视频描述,`wx-channel-engagement` 抓取匹配用的也是视频描述。`pub_wx_channel.title` 是数据库字段名,语义为完整视频描述;把短标题写进去会导致后续抓取匹配失败。短标题只留在作品目录的 `publish-copy.md` 里备查。 + +> **首次入库不传 `--publish-url`**:本工具不抓取作品链接,链接由用户后补(同 `wx-mp-publisher` 的 URL 升级机制)。用户提供链接后,用相同 `--source-folder` 重跑 `published-track record` 补 `--publish-url`——upsert 语义升级记录,不重复插行。`wx-channel-engagement` 抓取不依赖 `publish_url`,留空不影响日常数据采集。 diff --git a/crews/main/skills/expert-wx-channel/workflows/content-production.md b/crews/main/skills/expert-wx-channel/workflows/content-production.md index 6fb5f631..f0501ef6 100644 --- a/crews/main/skills/expert-wx-channel/workflows/content-production.md +++ b/crews/main/skills/expert-wx-channel/workflows/content-production.md @@ -165,7 +165,7 @@ DNA 约束的是选题与观看理由、短标题与视频描述写法、内容 视频号发布页改版后可同时填 **视频描述** 与 **短标题**,官方明确「填写短标题会获得更多流量」,因此**两项都必须有、发布时都必须填**,且都由 main agent 拟定。 1. **短标题**:按 DNA template 的「短标题模式」写,6-16 字,承担身份信号与价值预告;给 2-3 个候选。 -2. **视频描述**(≤300 字,含 2-5 个话题标签,标签直接写在描述中):按 DNA template 的「视频描述结构」写;给 2-3 个候选,说明各自的观看理由与标签策略。 +2. **视频描述**(≤300 字,含 2-5 个话题标签,标签直接写在描述中):按 DNA template 的「视频描述结构」写;给 2-3 个候选,说明各自的观看理由与标签策略。**标签段只能是半角 `#` + 空格分隔的 `#标签` 项**(如 `#AI员工 #开源工具 #影视解说`),不得带分组前缀(「【三层标签】」之类)、分隔符(/ 等)或全角 `#`——否则整行标签不被视频号识别、全部失效;DNA 的分层标签策略只用于挑标签,结构标记不进正文(硬约束详见 `wechat-channels-publish` Step 6)。 3. 有参考视频或用户文案时,必须参考其文案内容(如有),但不得照抄。 4. 用户已指定文案或原则时,候选必须在该约束内生成,不得偷换方向。 5. 描述可提及产品或业务,但不放明显引流信息;禁止二维码、联系方式与谐音绕检测,按 DNA 的 `interaction-cta` 只保留一个主行动引导。 @@ -244,11 +244,12 @@ main 负责监督并推动 CP 的进度,卡住时追问而不是替它做。 3. **设置封面**:作品目录有定稿 `cover.jpg` 时按工具说明「Step 5: 设置封面」上传(用户明确要用视频帧时在弹窗胶片条选帧代替)。 4. **视频描述与短标题都要填**(均取 Step 4 定稿):视频描述含话题标签,短标题按发布页字段单独填;两项都不得留空。 5. 点击“发表”;弹出原创声明弹窗时按工具说明处理。发布后用户会在手机端补充原创声明等事项。 -6. 发布成功后在视频列表页等转码完成,通过分享面板复制视频链接(`https://weixin.qq.com/sph/xxxx`);审核中拿不到链接时留空,后续补充。 -7. 遇到「session wechat-channel 正忙」(fail-first 队列)时等当前操作完成再重试,不盲试、不 close。 +6. 遇到「session wechat-channel 正忙」(fail-first 队列)时等当前操作完成再重试,不盲试、不 close。 必须等待发布流程完整返回后再判定结果,禁止提前自行判断发布成功。用户要求人工检查后再发布时,按该工具「手动模式」执行。 +**作品链接不抓取、由用户后补**:管理后台分享面板复制链接的路线实践中不可靠(审核 / 转码中常不可用),发布后不去后台取链接。与公众号 `wx-mp-publisher` 同机制:发布后告知用户作品已提交,请其后续在手机端或后台「分享」中获取作品链接(`https://weixin.qq.com/sph/xxxx`)提供给我们,再按 Step 7 补录升级。 + ## Step 7 - 记录 1. 定稿作品目录 `wx_channel/outputs//`: @@ -273,13 +274,12 @@ published-track record \ --platform wx_channel \ --source-folder wx_channel/outputs// \ --account <发布所用账号 alias> \ - --title "" \ - --publish-url "" + --title "" ``` - `--title` 是数据库字段名,**必须传完整视频描述**(含 hashtag)——`wx-channel-engagement` 抓取按它匹配后台作品。 - **短标题不入库**:管理页不展示短标题,写库会造成匹配混淆;短标题只留在作品目录的 `publish-copy.md`。 - `dna_id` 自动从 `dna-meta.json` 读取。 - - 发布时没拿到链接的,后续用户提供或抓取到链接后用相同 `--source-folder` 重跑 `published-track record` 补 `--publish-url`——upsert 语义升级记录,不重复插行。 + - **首次入库不传 `--publish-url`**(发布流程不抓链接,见 Step 6)。后续用户提供作品链接时,用相同 `--source-folder` 重跑 `published-track record` 补 `--publish-url`——upsert 语义升级记录,不重复插行(同公众号 URL 升级机制)。 4. 发布流程到此结束;互动数据由每日定时采集任务统一抓取,复盘走 `review.md`。 diff --git a/crews/main/skills/published-track/SKILL.md b/crews/main/skills/published-track/SKILL.md index 63fb50ab..3522c1f0 100644 --- a/crews/main/skills/published-track/SKILL.md +++ b/crews/main/skills/published-track/SKILL.md @@ -51,7 +51,7 @@ published-track init-db 每张表共享通用字段:`id`(自增主键)、`title`、`content_type`(article/video/post)、`source_folder`(原始文件夹,如 `wx_mp/outputs/xxx`,**不做唯一约束,同内容可同平台多次发布**)、`publish_url`、`publish_date`(YYYY-MM-DD)、`distribute_status`(0=待分发,1=无需分发,2=已分发)、`notes`、`created_at`、`updated_at`。各平台特有互动指标默认 0,另有 `top_comment`(主要留言摘要)。 -> **视频号(`pub_wx_channel`)特例**:`title` 列存的是**完整视频描述**(含 hashtag,最长约 300 字),即 `wechat-channels-publish` Step 5 填的视频描述;`wx-channel-engagement` 抓取按它匹配后台作品管理页。发布页改版后虽可另填**短标题**,但作品管理页不展示短标题,因此**短标题不入库**——调用方调 `record.sh --platform wx_channel --title` 必须传完整视频描述,不要传短标题、也不要把两者拼接。 +> **视频号(`pub_wx_channel`)特例**:`title` 列存的是**完整视频描述**(含 hashtag,最长约 300 字),即 `wechat-channels-publish` Step 6 填的视频描述;`wx-channel-engagement` 抓取按它匹配后台作品管理页。发布页改版后虽可另填**短标题**,但作品管理页不展示短标题,因此**短标题不入库**——调用方调 `record.sh --platform wx_channel --title` 必须传完整视频描述,不要传短标题、也不要把两者拼接。 ### DNA 关联字段(v3 schema) From 29ed864eb92291ede7b2d214770cc9fadbd71a51 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Tue, 15 Sep 2026 22:42:46 +0800 Subject: [PATCH 02/25] =?UTF-8?q?fix(expert-xhs):=20xhs-publish=20body=20?= =?UTF-8?q?=E5=AD=97=E9=9D=A2=E9=87=8F=20\n=20=E5=8F=91=E5=B8=83=E5=89=8D?= =?UTF-8?q?=E5=BD=92=E4=B8=80=E5=8C=96=E4=B8=BA=E7=9C=9F=E5=AE=9E=E6=8D=A2?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 采纳 skill-workshop 提案 xhs-publish-body-newline-normalize-20260915: agent 在 bash 双引号里传 --body 时 \n 是字面量「反斜杠+n」,脚本原样 透传 desc 导致小红书正文全是 \n 文本(09-15 实发事故)。 - publish_xhs.py:新增 normalize_body_newlines(),main() 在长度校验 / extract_topics 之前归一化(字面量 \n 占 2 字符先归一再校验才准; 紧贴 #话题 的字面量 \n 非空白,会污染话题名分词);\r\n 先于 \n 替换避免残留字面量 \r - SKILL.md:--body 换行传参规范(✅ 多行字符串 / $'...',❌ 双引号 字面量 \n),紧邻既有 --body 禁传路径警告 - 新增 test_publish_xhs.py:6 个回归测试(归一化语义 ×4 + 话题提取 顺序 ×1 + main() 接线端到端 ×1),全部通过 --- .../expert-xhs/tools/xhs-publish/SKILL.md | 14 ++++ .../tools/xhs-publish/scripts/publish_xhs.py | 14 ++++ .../xhs-publish/scripts/test_publish_xhs.py | 74 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 crews/main/skills/expert-xhs/tools/xhs-publish/scripts/test_publish_xhs.py diff --git a/crews/main/skills/expert-xhs/tools/xhs-publish/SKILL.md b/crews/main/skills/expert-xhs/tools/xhs-publish/SKILL.md index 977f5fb5..fb3568f8 100644 --- a/crews/main/skills/expert-xhs/tools/xhs-publish/SKILL.md +++ b/crews/main/skills/expert-xhs/tools/xhs-publish/SKILL.md @@ -84,6 +84,20 @@ xhs-publish --mode video --title "笔记标题" --body "正文内容" --video vi > **⚠️ `--body` 必须传实际文字,不能传文件路径或 `$(cat file)`**:exec sandbox 禁用 `$(...)` 命令替换,`--body post.md` 也会被当字面量字符串。把正文直接硬编码进命令。 +> **⚠️ `--body` 换行用真实换行,不要在双引号里写 `\n`**:bash 双引号内的 `\n` 是字面量「反斜杠+n」,发布后正文会全是 `\n` 文本。脚本已兜底把字面量 `\n` 自动归一化为真实换行,但传参仍首选真换行: +> +> ```bash +> # ✅ 多行字符串:引号内直接回车换行 +> xhs-publish --mode image --title "标题" --body "第一行 +> 第二行" --images img.jpg +> +> # ✅ $'...' 转义:\n 被 bash 解释为真实换行 +> xhs-publish --mode image --title "标题" --body $'第一行\n第二行' --images img.jpg +> +> # ❌ 普通双引号里的 \n 是字面量,发布后正文全是 \n 文本 +> xhs-publish --mode image --title "标题" --body "第一行\n第二行" --images img.jpg +> ``` + 成功输出: ```json diff --git a/crews/main/skills/expert-xhs/tools/xhs-publish/scripts/publish_xhs.py b/crews/main/skills/expert-xhs/tools/xhs-publish/scripts/publish_xhs.py index 2ffef2af..7512b257 100755 --- a/crews/main/skills/expert-xhs/tools/xhs-publish/scripts/publish_xhs.py +++ b/crews/main/skills/expert-xhs/tools/xhs-publish/scripts/publish_xhs.py @@ -132,6 +132,16 @@ def cookie_str(cookie_dict: dict) -> str: return "; ".join(f"{k}={v}" for k, v in cookie_dict.items()) +def normalize_body_newlines(body: str) -> str: + r"""把 body 里字面量 \n / \r\n(反斜杠+字母序列)归一化为真实换行。 + + Agent 在 bash 双引号里传 --body 时,引号内 \n 是字面量「反斜杠+n」而非真实换行, + 原样透传会被小红书当普通文本展示,正文全是 \n 文本。 + 先替换 \r\n 再替换 \n,避免残留字面量 \r;已是真实换行的内容不受影响。 + """ + return body.replace("\\r\\n", "\n").replace("\\n", "\n") + + def extract_topics(body: str, extra_topics: list[str] | None = None) -> list[dict]: """Extract #话题 from body text, return AiToEarn-format hash_tag list. @@ -717,6 +727,10 @@ def main() -> None: parser.add_argument("--cookie-file", type=Path, help="Cookie file path") args = parser.parse_args() + # 字面量 \n 归一化须在长度校验 / 话题提取之前:字面量 \n 占 2 字符,先归一化长度才准; + # 且紧贴 #话题 的 \n(非空白字符)会污染 extract_topics 的分词 + args.body = normalize_body_newlines(args.body) + if len(args.title) > 20: err_exit("TITLE_TOO_LONG: title exceeds 20 characters") if len(args.body) > 1000: diff --git a/crews/main/skills/expert-xhs/tools/xhs-publish/scripts/test_publish_xhs.py b/crews/main/skills/expert-xhs/tools/xhs-publish/scripts/test_publish_xhs.py new file mode 100644 index 00000000..6d32bd14 --- /dev/null +++ b/crews/main/skills/expert-xhs/tools/xhs-publish/scripts/test_publish_xhs.py @@ -0,0 +1,74 @@ +"""Regression tests for publish_xhs body newline normalization.""" + +from __future__ import annotations + +import importlib.util +import io +import sys +import unittest +from pathlib import Path +from unittest import mock + + +SCRIPT_PATH = Path(__file__).with_name("publish_xhs.py") +SPEC = importlib.util.spec_from_file_location("publish_xhs", SCRIPT_PATH) +assert SPEC and SPEC.loader +publish_xhs = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(publish_xhs) + + +class NormalizeBodyNewlinesTests(unittest.TestCase): + def test_converts_literal_backslash_n_to_real_newline(self) -> None: + # bash 双引号里 --body "第一行\n第二行" 收到的就是字面量反斜杠+n + self.assertEqual( + publish_xhs.normalize_body_newlines("第一行\\n第二行"), + "第一行\n第二行", + ) + + def test_converts_literal_crlf_without_leaving_stray_cr(self) -> None: + self.assertEqual(publish_xhs.normalize_body_newlines("a\\r\\nb"), "a\nb") + + def test_keeps_real_newlines_untouched(self) -> None: + self.assertEqual(publish_xhs.normalize_body_newlines("a\nb"), "a\nb") + + def test_handles_mixed_literal_and_real_newlines(self) -> None: + self.assertEqual(publish_xhs.normalize_body_newlines("a\nb\\nc"), "a\nb\nc") + + def test_normalizing_before_extraction_keeps_topic_names_clean(self) -> None: + # 字面量 \n 是非空白字符:不先归一化,extract_topics 会把 "职场\n第二行" 整个当话题名 + raw = "#职场\\n第二行正文" + topics = publish_xhs.extract_topics( + publish_xhs.normalize_body_newlines(raw), None + ) + self.assertEqual([t["name"] for t in topics], ["职场"]) + + +class MainBodyNormalizationTests(unittest.TestCase): + def test_main_publishes_body_with_real_newlines(self) -> None: + # 端到端守住 main() 的接线:字面量 \n 的 --body 到达发布函数时已是真实换行 + argv = [ + "publish_xhs.py", + "--mode", "image", + "--title", "标题", + "--body", "第一行\\n第二行", + "--images", "img.jpg", + ] + captured: dict = {} + + def fake_publish(client, cookie_dict, ua, title, body, images, topics, private): + captured["body"] = body + return {"ok": True, "note_id": "x", "url": "u"} + + with ( + mock.patch.object(sys, "argv", argv), + mock.patch.object(publish_xhs, "load_cookies", return_value=({}, "UA")), + mock.patch.object(publish_xhs, "publish_image_note", side_effect=fake_publish), + mock.patch.object(sys, "stdout", new=io.StringIO()), + ): + publish_xhs.main() + + self.assertEqual(captured["body"], "第一行\n第二行") + + +if __name__ == "__main__": + unittest.main() From 132c45bf03d6d3b11a7ec419c2aceb5ac7dbeec8 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Tue, 15 Sep 2026 23:48:13 +0800 Subject: [PATCH 03/25] =?UTF-8?q?refactor(expert-video):=20intent/story=20?= =?UTF-8?q?=E9=99=8D=E7=BA=A7=E2=80=94=E2=80=94=E5=88=A0=20intent-router?= =?UTF-8?q?=EF=BC=8Cstory-develop=20=E5=8D=87=E6=A0=BC=E4=B8=BA=20intake?= =?UTF-8?q?=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基线生产链改为直接从 Brief 开始(Stage 0 intake → Stage 3 script-write),移除从 OpenMontage 移植但已失真的 intent→story→script 前段。OpenMontage 真实模型是 13 条 pipeline manifest 驱动全流程 + 对话式 creative-intake;我们移植时塌缩成 3 档 genre 分类,但 genre 在本包无任何下游消费——intent.json 全字段的唯一真实读者是 story-develop 的模板页眉。 - 删 intent-router.py:3 档分类(narrative/motion/montage)对下游制作无实际作用。 - 删 story-develop.py,升格为 workflows/story-develop.md(intake 类 workflow): Brief 创意模糊时与甲方对话式收敛 Brief(仿 OpenMontage creative-intake), 不产 intent.json/story.md;与 type workflow 正交可组合。 - script-write 入参 story.md → brief.md;character-register 人物段 story.md → script.md (顺带修工具 doc 里 character-register 入参写错的 storyboard.json+brief.md)。 - 删预算(shot_count):script-self-eval 去镜数维(六→五),storyboard 去镜数指令。 - 修 7 个脚本缺 die()(错误路径抛 NameError 而非干净报错)+ slot-plan 对 intent.json 的 dead read(读了不用,且 intent.json 根本无 tone 字段)。 - 编号策略 R2:Stage 3-14 编号与 GATE A/B 位置不变,仅退役 Stage 1-2,churn 最小。 - 同步 SKILL.md / AGENTS.md / video-producer 工具 doc+wrapper / narration-video / reversal-ad(三段结构归 Stage 3 script-write,不再走独立故事阶段)。 范围:本轮仅动 CP 侧,未碰 main DNA framework / Brief 模板。 已知 stale(留待后续轮次):main 侧 4 个 content-production.md + 3 个 video-dna-framework.md + docs/expert-pack-dna-architecture.md 仍有"Stage 1 intent-router 定档位"陈述。 验证:全脚本 py_compile OK;brief.md→script-write→self-eval→storyboard→ shot-decompose→character-register 链路干跑通过、产物齐;retired 子命令干净失败 (未知子命令);全仓 grep 无残留 intent.json/story.md 机器引用。 --- crews/content-producer/AGENTS.md | 2 +- .../skills/expert-video/SKILL.md | 35 +++--- .../tools/video-producer/SKILL.md | 8 +- .../scripts/character-register.py | 10 +- .../video-producer/scripts/intent-router.py | 104 ------------------ .../scripts/reference-concepts.py | 4 +- .../scripts/script-self-eval.py | 10 +- .../video-producer/scripts/script-write.py | 17 ++- .../video-producer/scripts/shot-decompose.py | 6 + .../video-producer/scripts/slideshow-risk.py | 6 + .../tools/video-producer/scripts/slot-plan.py | 12 +- .../video-producer/scripts/story-develop.py | 76 ------------- .../scripts/storyboard-build.py | 7 +- .../tools/video-producer/video-producer.sh | 7 +- .../expert-video/workflows/narration-video.md | 3 +- .../expert-video/workflows/reversal-ad.md | 4 +- .../expert-video/workflows/story-develop.md | 40 +++++++ 17 files changed, 117 insertions(+), 234 deletions(-) delete mode 100644 crews/content-producer/skills/expert-video/tools/video-producer/scripts/intent-router.py delete mode 100644 crews/content-producer/skills/expert-video/tools/video-producer/scripts/story-develop.py create mode 100644 crews/content-producer/skills/expert-video/workflows/story-develop.md diff --git a/crews/content-producer/AGENTS.md b/crews/content-producer/AGENTS.md index cd4ebd22..becd3a66 100644 --- a/crews/content-producer/AGENTS.md +++ b/crews/content-producer/AGENTS.md @@ -19,7 +19,7 @@ | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | `expert-video` | 通用制作流程 + Reversal Ad 细化 | | 甲方交付口播文案或真人口播录音,要合成声画 | `expert-video` | 通用制作流程 + Narration Video 细化 | | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | `expert-video` | 通用制作流程 + Collage B-roll 细化 | -| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走,Stage 1 `intent-router` 定档位(narrative / motion / montage) | +| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走;创意不清时先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 3 `script-write`(叙事 / 动效 / 蒙太奇手法由我据创意自定,不再做三档分类) | | 已有素材要剪辑、修整、拼接、配音、烧字幕 | `expert-video` | 通用制作流程的 Stage 12 工具箱(只做几何级修整) | | "做网页/落地页/APP 界面/品牌视觉体系" | `expert-design` | Web Page / App UI / Brand Visual | diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index 8e737e77..9aca6579 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -55,8 +55,8 @@ metadata: | 层 | 是什么 | 怎么用 | |----|--------|--------| -| **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→14 阶段链、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | -| **类型 workflow**(`workflows/<值>.md`) | 在通用制作流程**之上**对某一类视频的进一步细化与明确化:阶段裁剪、叙事套路约束、声音 / 画面规范、验收补充 | Brief 指定 `workflow` 时必读必用,按其细化执行;细化内容与通用流程冲突时以 workflow 为准,但**闸门与护栏不让步** | +| **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→14 阶段链(Stage 1-2 已退役,基线从 Stage 3 script-write 起)、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | +| **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:创意不足以直接写剧本时触发 | | **工具说明**(`tools/<工具>/SKILL.md`) | 每个子命令的入参、产物路径、退出码与旁路条件 | 调用前查;本文不重复参数细节 | > 通用制作流程**不是**与类型 workflow 并列的第四条路,也**不是**"Brief 没指定类型时的 fallback"。它是底座;类型 workflow 只在底座上细化,产出特定类型的视频。 @@ -72,8 +72,10 @@ metadata: | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 一句文稿 → 一个视觉隐喻 → 静帧 → i2v,三道闸门与 Gate 3 批量调度 | - Brief 指定了 `workflow`:**先读对应文档并直接采用**,不得替换成自创流程。 -- Brief 未指定:仍走通用制作流程,由 Stage 1 `intent-router` 定档位——故事讲述型 narrative / 纯画面动效型 motion / 蒙太奇剪接型 montage。 -- 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。**活儿小也不跳过** Stage 0 Brief 确认、Stage 13 自检与响度归一化、Stage 14 交付三件套。 +- Brief 未指定 `workflow`:仍走通用制作流程;创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 3 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`,**不再做三档分类**(intent-router 已退役)。 +- 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。**活儿小也不跳过** Stage 0 Brief intake、Stage 13 自检与响度归一化、Stage 14 交付三件套。 + +> `story-develop` 是 **intake 类 workflow**(Stage 0 创意澄清,**不是 `Brief.workflow` 取值**),与上表 type 类 workflow 正交:任何类型的视频,创意不清都先走它收敛 Brief,再按通用制作流程 + 对应 type workflow 执行。详见 `workflows/story-develop.md`。 不属于我的活(交回甲方或转其他专家包): @@ -91,7 +93,7 @@ output_videos// # ├── brief.md # 甲方交付(拷贝入档)或 Stage 0 与用户定稿 ├── voiceover.md # 甲方交付的口播文案(如有) ├── reference/ # 可选:甲方给的参考拆解报告与差异化概念 -├── script/ # intent.json(1) / story.md(2) / script.md(3) / self-eval.json(3b) / decisions.json(审计链) +├── script/ # script.md(3) / self-eval.json(3b) / decisions.json(审计链) ├── storyboard/ # storyboard.json(4) / shot_decompose.json(5) ├── characters/ # registry.json(6) + /{front,side,back}.png ├── gates/ # gate-a.md / gate-b.md(含批准人与批准范围) @@ -108,22 +110,19 @@ output_videos// # workflow 文档在技能包内,不是项目目录内容;项目目录只放 Brief、素材、脚本、渲染与交付产物。 -## 通用制作流程(Stage 0→14,两闸门) +## 通用制作流程(Stage 0→14,两闸门;Stage 1-2 已退役为 intake workflow) **我做任何视频都走这条链**;类型 workflow 只在此基础上裁剪与细化。每段的子命令是 `video-producer` 工具下的一个独立脚本,按流程逐个调。 ``` -Stage 0 Brief 确认 模式 A:读甲方 Brief,核对字段,缺口向 Brief owner 澄清 +Stage 0 Brief intake 模式 A:读甲方 Brief,核对字段,缺口向 Brief owner 澄清 模式 B:用户未给 Brief 时引导讨论 → 代拟 brief.md → 发用户确认 - (两种模式的 Stage 0 都是"先把 Brief 定下来",无子命令) -Stage 1 intent-router 定档位(Brief 指定 workflow 时按该 workflow 的约束校验,未给定时从下面三个档位选一个) - narrative 故事讲述型(重情节、有人物弧光、含旁白,默认 3–5 镜/场) - motion 纯画面动效型(重节奏与视觉冲击、少对白,默认 5–8 镜快切) - montage 蒙太奇剪接型(重氛围、抽象、纯视觉,默认 4–7 镜无叙事) -Stage 2 story-develop idea → 故事(受众/类型显式复述、100–200 词梗概、人物、分场) - 甲方已交付口播文案时跳过:叙事以口播稿为准,不另起故事 -Stage 3 script-write 故事 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) - 甲方已交付口播文案时改为落稿锁定:原样落 script/script.md,不重写策略文案 + 创意不足以直接写剧本时 → 走 story-develop intake workflow(workflows/story-develop.md) + 与甲方对话式收敛创意 + 规格,落定 brief.md(无子命令;意图澄清在此消化,不再产 intent.json) + 〔原 Stage 1 intent-router / Stage 2 story-develop 已退役:三档分类对下游制作无实际作用, + 故事膨胀并入 Stage 3;意图澄清升格为 Stage 0 的 story-develop intake workflow〕 +Stage 3 script-write Brief 创意 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) + 基线生产从此开始。甲方已交付口播文案时改为落稿锁定:原样落 script/script.md,不重写策略文案 Stage 3b script-self-eval 脚本自评 N 维打分,任一维 <3 必返工(落稿锁定时只检查不改写) Stage 4 storyboard-build 剧本 → 镜头表(每镜叙事目的/机位复用/位置朝向/不写不可见) Stage 5 shot-decompose 每镜拆首帧静照/尾帧静照/运动描述(variation_type 三档) @@ -162,7 +161,7 @@ Stage 14b 交付 回报成片 + 封面 + final-deliver.md 的绝对 文本产物全齐(脚本 + 分镜 + 机位 + 角色),**停下发甲方审**: -- 呈交摘要:档位或 workflow、场次数、镜数、角色数、关键决策(路径 / 模型 / 风格选择的备选 + 置信度 + 理由) +- 呈交摘要:workflow 或创意定位、场次数、镜数、角色数、关键决策(路径 / 模型 / 风格选择的备选 + 置信度 + 理由) - **结束本轮回复**,不许在同条回复里进 Stage 7 - 批准人是 Brief owner(模式 A = main agent,模式 B = 用户);甲方已在 Brief 中代理批准时,把批准范围落 `gates/gate-a.md` 后继续 - 批准是**逐闸门的**——早先的一句"你继续"不覆盖本闸门 @@ -201,7 +200,7 @@ Stage 14b 交付 回报成片 + 封面 + final-deliver.md 的绝对 | 工具 | 用途 | 命令 | |------|------|------| -| `video-producer` | 阶段链全部原子能力(意图路由、故事 / 剧本 / 分镜、素材 slot 与解析、渲染、混音对齐、拼接合成、动效审计、封面)+ 后期处理(`normalize` **必跑**、`burn-srt` / `duck` / `denoise` / `interp` 可选,全部干湿分离不覆盖输入) | `video-producer <子命令>`;`video-producer help` 列全量 | +| `video-producer` | 阶段链全部原子能力(剧本 / 分镜、素材 slot 与解析、渲染、混音对齐、拼接合成、动效审计、封面)+ 后期处理(`normalize` **必跑**、`burn-srt` / `duck` / `denoise` / `interp` 可选,全部干湿分离不覆盖输入) | `video-producer <子命令>`;`video-producer help` 列全量 | | `collage-broll` | 纸拼贴 B-roll 的环境自检与 Gate 3 批量 i2v 调度(0 全通 / 1 参数错 / 2 部分失败,只重跑失败条目) | `collage-broll check-setup` / `collage-broll gate3 --batch [--dry-run]` | 跨领域公共技能:`aigc-video-gen`(视频片段生成 / i2v 首尾帧插值,Stage 8/10;输出路径须落在 `output_videos/` 下,调用时 workdir 是 Content Producer workspace 根)、`siliconflow-img-gen`(静帧、角色三视图、封面,Stage 6/10/14a)、`awk-tts`(旁白 TTS,带字级时间戳,Stage 11B;`--enable-subtitle` 让火山流式 HTTP 原生返回时间戳)、`bgm-library`(ccMixter 免版税 + 自动 TASL 署名,商用安全,Stage 11C 优先)、`pexels-footage` / `pixabay-footage`(免版税素材与 BGM 搜索)、`video-review`(成片技术自检闸门,Stage 13a)、`video-edit subtitles`(main crew 暴露的烧字幕原子;不可用时向 Brief owner 报工具缺口,不手写 ffmpeg)。 diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md b/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md index 39a5acc5..addec00c 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md +++ b/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md @@ -1,6 +1,6 @@ --- name: video-producer -description: 视频制作原子能力集——意图路由、故事/剧本/分镜、素材 slot 与解析、渲染、混音对齐、拼接合成、动效审计、封面。子命令范式,产物文件存在性即 checkpoint。 +description: 视频制作原子能力集——剧本/分镜、素材 slot 与解析、渲染、混音对齐、拼接合成、动效审计、封面。子命令范式,产物文件存在性即 checkpoint。 --- # video-producer — 工具说明 @@ -19,14 +19,12 @@ description: 视频制作原子能力集——意图路由、故事/剧本/分 | 子命令 | 入 | 出 | 用途 | |--------|----|----|------| -| `intent-router` | brief.md(主题/关键词/类型) | `script/intent.json`(档位+主题) | 意图路由三档:故事讲述型 narrative / 纯画面动效型 motion / 蒙太奇剪接型 montage | | `reference-concepts` | 甲方给的参考拆解报告(可选) | `reference/concepts.md` | 据报告出 2–3 个差异化概念;不做下载/转写/抽帧 | -| `story-develop` | intent.json | `script/story.md` | idea → 故事(受众/类型复述、100–200 词梗概、人物、分场) | -| `script-write` | story.md | `script/script.md`(含 enhancement_cues 六型 + delivery_cues) | 故事 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) | +| `script-write` | brief.md(创意 + 规格) | `script/script.md`(含 enhancement_cues 六型 + delivery_cues) | Brief 创意 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) | | `script-self-eval` | script.md | `script/self-eval.json` | 脚本自评 N 维打分,任一维 <3 必返工 | | `storyboard-build` | script.md | `storyboard/storyboard.json` | 剧本 → 镜头表(每镜叙事目的/机位复用/位置朝向/不写不可见) | | `shot-decompose` | storyboard.json | `storyboard/shot_decompose.json` | 每镜拆首帧静照/尾帧静照/运动描述(variation_type 三档) | -| `character-register` | storyboard.json + brief.md | `characters/registry.json` + 三视图 png | 角色 static/dynamic features 拆分 + front/side/back(调 `siliconflow-img-gen`) | +| `character-register` | shot_decompose.json + script.md | `characters/registry.json` + 三视图 png | 角色 static/dynamic features 拆分 + front/side/back(调 `siliconflow-img-gen`) | | `slot-plan` | storyboard.json + shot_decompose.json | `slots/slot-plan.json` | 素材 slot 规划(template + hero slot + tone→slot 数) | | `asset-resolve` | slot-plan.json | `slots/asset-resolve.json`(含 rejected_picks)+ 素材落 `raw_materials/` | 按 slot 拉素材(Fast path:多源并发搜 + 缩略图人核;调 pexels-footage / pixabay-footage / aigc-video-gen) | | `slideshow-risk` | storyboard.json + slot-plan.json + asset-resolve.json | `slots/slideshow-risk.json` | 六维幻灯风险打分(pre-compose 闸门,≥4.0 fail) | diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py index 943cf7c5..59841771 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py @@ -4,7 +4,7 @@ Usage: python3 scripts/character-register.py -入:project_dir/storyboard/shot_decompose.json(Stage 5)+ story.md(Stage 2 人物段) +入:project_dir/storyboard/shot_decompose.json(Stage 5)+ script.md(Stage 3 出场人物) 出:project_dir/characters/registry.json(每个角色 static/dynamic features) + project_dir/characters//front.png + side.png + back.png(调 siliconflow-img-gen) @@ -34,9 +34,9 @@ def main() -> None: project = Path(args.project_dir).resolve() decompose_path = project / "storyboard" / "shot_decompose.json" - story_path = project / "script" / "story.md" - if not decompose_path.is_file() or not story_path.is_file(): - die("前置缺失: shot_decompose.json 或 story.md 不存在") + script_path = project / "script" / "script.md" + if not decompose_path.is_file() or not script_path.is_file(): + die("前置缺失: shot_decompose.json 或 script.md 不存在") registry_path = project / "characters" / "registry.json" registry_path.parent.mkdir(parents=True, exist_ok=True) @@ -52,7 +52,7 @@ def main() -> None: "stage": 6, "characters": [], "instruction": ( - "agent 据 story.md 人物段 + shot_decompose.json 列出所有出场角色,每个角色填 schema 并调 " + "agent 据 script.md 出场人物 + shot_decompose.json 列出所有出场角色,每个角色填 schema 并调 " "siliconflow-img-gen 生成 front/side/back 三视图落 characters//。" "static features 跨镜不变(发色/衣着/体型/年龄感),dynamic features 随镜变(表情/姿势/光影)。" "best_image_selector 走 agent 看 contact sheet 人核,不引 CLIP。" diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/intent-router.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/intent-router.py deleted file mode 100644 index c8f673d7..00000000 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/intent-router.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -"""Stage 1 — intent-router:把 Brief 意图路由成三档脚本模板。 - -三档(内部 key 保留英文,user-facing 表述用中文名): -- narrative(故事讲述型)——重情节、有人物弧光、含旁白 → 默认 3–5 镜/场 -- motion(纯画面动效型)——重节奏感/视觉冲击/少对白 → 默认 5–8 镜快切 -- montage(蒙太奇剪接型)——重氛围/抽象/纯视觉 → 默认 4–7 镜无叙事 - -Usage: - video-producer intent-router [--user-text "..."] [--report-file path] - -入:project_dir(CP 自建工作区 output_videos//),可选甲方原文或参考拆解报告路径 -出:project_dir/script/intent.json(档位 + 主题 + 受众 + 时长目标 + 备选 + 决策理由) - -产物文件存在性即 checkpoint:intent.json 已存在则打印现状退出,不重生成(用户手改后续跑)。 -""" - -import argparse -import json -import sys -from pathlib import Path - -VALID_GENRES = {"narrative", "motion", "montage"} -DURATION_DEFAULTS = {"narrative": 30, "motion": 25, "montage": 20} -SHOT_DEFAULTS = {"narrative": (3, 5), "motion": (5, 8), "montage": (4, 7)} - - -def die(msg: str) -> None: - print(f"[error] {msg}", file=sys.stderr) - sys.exit(1) - - -def detect_genre(text: str) -> tuple[str, str]: - """从用户文本特征粗判档位。返 (genre, reason)。""" - lower = text.lower() - if any(k in lower for k in ["故事", "情节", "人物", "弧光", "narrative", "story", "plot", "character"]): - return "narrative", "用户提及故事/情节/人物" - if any(k in lower for k in ["节奏", "冲击", "快切", "motion", "beat", "impact", "rhythm"]): - return "motion", "用户提及节奏/冲击/快切" - if any(k in lower for k in ["氛围", "抽象", "纯视觉", "montage", "vibe", "abstract", "atmosphere"]): - return "montage", "用户提及氛围/抽象/纯视觉" - return "narrative", "无明确信号,退默认 narrative(最通用)" - - -def main() -> None: - parser = argparse.ArgumentParser(description="Stage 1 intent-router") - parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") - parser.add_argument("--user-text", default=None, help="甲方原文(Brief 主题句或用户描述)") - parser.add_argument("--report-file", default=None, help="甲方给的参考拆解报告路径(可选)") - parser.add_argument("--genre", default=None, choices=sorted(VALID_GENRES), help="强制档位,跳过自动判定") - parser.add_argument("--duration", type=int, default=None, help="时长目标(秒),不传走档位默认") - parser.add_argument("--audience", default=None, help="受众描述") - args = parser.parse_args() - - project = Path(args.project_dir).resolve() - if not project.is_dir(): - die(f"project_dir 不存在或非目录: {args.project_dir}") - intent_path = project / "script" / "intent.json" - intent_path.parent.mkdir(parents=True, exist_ok=True) - - # checkpoint:产物已存在则不重生成 - if intent_path.is_file(): - existing = json.loads(intent_path.read_text(encoding="utf-8")) - print(f"[checkpoint] intent.json 已存在,沿用:") - print(json.dumps(existing, ensure_ascii=False, indent=2)) - return - - text = args.user_text or "" - if not text and args.report_file: - report = Path(args.report_file) - if report.is_file(): - text = report.read_text(encoding="utf-8")[:2000] - - if not text: - die("需 --user-text 或 --report-file 之一作为意图来源") - - genre, reason = (args.genre, "用户强制档位") if args.genre else detect_genre(text) - duration = args.duration or DURATION_DEFAULTS[genre] - shot_min, shot_max = SHOT_DEFAULTS[genre] - - intent = { - "stage": 0, - "genre": genre, - "topic": text[:200], - "audience": args.audience or "未指定", - "duration_target": duration, - "shot_count": {"min": shot_min, "max": shot_max}, - "decisions": [ - { - "choice": f"genre={genre}", - "alternatives": sorted(VALID_GENRES - {genre}), - "confidence": 0.7 if not args.genre else 1.0, - "reason": reason, - } - ], - } - intent_path.write_text(json.dumps(intent, ensure_ascii=False, indent=2), encoding="utf-8") - print(f"[done] 路由完成:genre={genre} duration={duration}s shots={shot_min}-{shot_max}") - print(f"[done] 产物:{intent_path}") - print(f"[next] 跑 story-develop(Stage 2)") - - -if __name__ == "__main__": - main() diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/reference-concepts.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/reference-concepts.py index 0bd22d78..11443e66 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/reference-concepts.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/reference-concepts.py @@ -74,12 +74,12 @@ def main() -> None: ## 用户选定 -> 呈交甲方(Brief owner)选定一个概念,写入 brief.md。未选定前不进 Stage 2。 +> 呈交甲方(Brief owner)选定一个概念,写入 brief.md。未选定前不进基线生产(script-write)。 """ concepts_path.write_text(stub, encoding="utf-8") print(f"[done] 报告已存档:{archived}") print(f"[stub] concepts.md 模板已落:{concepts_path}") - print(f"[next] agent 据报告填概念 → 呈交甲方选定 → 回通用制作流程(story-develop)") + print(f"[next] agent 据报告填概念 → 呈交甲方选定 → 回通用制作流程(script-write)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py index 44e7b86f..8a958910 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py @@ -7,13 +7,12 @@ 入:project_dir/script/script.md(Stage 3) 出:project_dir/script/self-eval.json(N 维分 1–5 + 总评 + 是否必返工) -N 维(硬约束六条): +N 维(硬约束五条): 1. 可拍化:无不可见物描写 2. 场次划分:同时间同地点一场 3. 对白格式:引号统一 4. enhancement_cues:六型齐 5. delivery_cues:语气/语速/重音齐 -6. 镜头数预算:在 intent.json 的 min-max 区间 agent 据此逐维打分填 self-eval.json。脚本不做 NLP 判分——是 agent 的自检脚手架。 """ @@ -23,13 +22,18 @@ import sys from pathlib import Path + +def die(msg: str) -> None: + print(f"[error] {msg}", file=sys.stderr) + sys.exit(1) + + EVAL_DIMS = [ ("filmable", "可拍化", "无不可见物描写('想起了'/'觉得'改外化动作)"), ("scene_split", "场次划分", "同时间同地点一场"), ("dialog_format", "对白格式", "引号「」统一"), ("enhancement_cues", "enhancement_cues 六型齐", "动作/表情/环境/心理外化/节奏/视觉锚点"), ("delivery_cues", "delivery_cues 齐", "语气/语速/重音/情感控制"), - ("shot_count", "镜数在预算区间", "intent.json 的 min-max"), ] diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py index b08555c5..d4752b93 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 3 — script-write:故事 → 分场剧本。 +"""Stage 3 — script-write:Brief 创意 → 分场剧本。 Usage: python3 scripts/script-write.py -入:project_dir/script/story.md(Stage 2) +入:project_dir/brief.md(甲方 Brief:创意 + 规格;口播类为 voiceover.md 落稿) 出:project_dir/script/script.md(分场剧本,含 enhancement_cues 六型 + delivery_cues) 剧本硬约束: @@ -21,15 +21,20 @@ from pathlib import Path +def die(msg: str) -> None: + print(f"[error] {msg}", file=sys.stderr) + sys.exit(1) + + def main() -> None: parser = argparse.ArgumentParser(description="Stage 3 script-write") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() project = Path(args.project_dir).resolve() - story_path = project / "script" / "story.md" - if not story_path.is_file(): - die(f"前置缺失: story.md 不存在,先跑 story-develop(Stage 2)") + brief_path = project / "brief.md" + if not brief_path.is_file(): + die(f"前置缺失: brief.md 不存在,先完成 Stage 0 Brief intake(创意模糊时走 story-develop intake workflow)") script_path = project / "script" / "script.md" script_path.parent.mkdir(parents=True, exist_ok=True) @@ -41,7 +46,7 @@ def main() -> None: stub = f"""# 分场剧本(Stage 3) -> 据故事梗概({story_path.name})拆成可拍化分场剧本。每场含:场景描述、出场人物、对白、动作、enhancement_cues、delivery_cues。 +> 据 Brief 创意({brief_path.name})拆成可拍化分场剧本。每场含:场景描述、出场人物、对白、动作、enhancement_cues、delivery_cues。 ## 场 1:(场名,如"主角家中—清晨") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py index e1641499..c39631eb 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py @@ -20,6 +20,12 @@ import sys from pathlib import Path + +def die(msg: str) -> None: + print(f"[error] {msg}", file=sys.stderr) + sys.exit(1) + + VALID_VARIATIONS = {"static", "dynamic", "transition"} diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py index de13cf15..2a8ba61b 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py @@ -23,6 +23,12 @@ import sys from pathlib import Path + +def die(msg: str) -> None: + print(f"[error] {msg}", file=sys.stderr) + sys.exit(1) + + RISK_DIMS = [ ("motion_density", "动镜头占比", "静图不算", 0.25), ("shot_variation", "镜种多样性", "特写/中景/远景/航拍/手持混", 0.15), diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py index 4d132185..8462106a 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py @@ -4,7 +4,7 @@ Usage: python3 scripts/slot-plan.py -入:project_dir/storyboard/shot_decompose.json(Stage 5)+ script/intent.json(tone) +入:project_dir/storyboard/shot_decompose.json(Stage 5) 出:project_dir/slots/slot-plan.json(每镜对应 slot:template + hero slot + tone→slot 数) template:slot 模板(如"主角家中—晨光—白T青年") @@ -24,6 +24,12 @@ import sys from pathlib import Path + +def die(msg: str) -> None: + print(f"[error] {msg}", file=sys.stderr) + sys.exit(1) + + TONE_SLOT_TABLE = { "elegy": {"shot_duration": 4.0, "slots_per_min": 15}, "solemn": {"shot_duration": 3.5, "slots_per_min": 17}, @@ -41,7 +47,6 @@ def main() -> None: project = Path(args.project_dir).resolve() decompose_path = project / "storyboard" / "shot_decompose.json" - intent_path = project / "script" / "intent.json" if not decompose_path.is_file(): die(f"前置缺失: shot_decompose.json 不存在") @@ -55,8 +60,7 @@ def main() -> None: print(json.dumps(existing, ensure_ascii=False, indent=2)) return - intent = json.loads(intent_path.read_text(encoding="utf-8")) if intent_path.is_file() else {} - tone = args.tone or "solemn" # narrative 默认庄重 + tone = args.tone or "solemn" # 默认庄重;agent 据 Brief 创意/调性传 --tone tone_cfg = TONE_SLOT_TABLE[tone] stub = { diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/story-develop.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/story-develop.py deleted file mode 100644 index 582ec67d..00000000 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/story-develop.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -"""Stage 2 — story-develop:idea → 故事(分场)。 - -Usage: - python3 scripts/story-develop.py - -入:project_dir/script/intent.json(Stage 0) -出:project_dir/script/story.md(100–200 词梗概 + 人物 + 分场) - -场次划分原则:同时间同地点分一场。agent 按 SKILL.md 工作流填 story.md 模板。 -""" - -import argparse -import json -import sys -from pathlib import Path - - -def main() -> None: - parser = argparse.ArgumentParser(description="Stage 2 story-develop") - parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") - args = parser.parse_args() - - project = Path(args.project_dir).resolve() - intent_path = project / "script" / "intent.json" - if not intent_path.is_file(): - die(f"前置缺失: intent.json 不存在,先跑 intent-router(Stage 0)") - - story_path = project / "script" / "story.md" - story_path.parent.mkdir(parents=True, exist_ok=True) - - # checkpoint - if story_path.is_file(): - print(f"[checkpoint] story.md 已存在,沿用:{story_path}") - return - - intent = json.loads(intent_path.read_text(encoding="utf-8")) - stub = f"""# 故事梗概(Stage 2) - -## 档位 -{intent['genre']}(时长目标 {intent['duration_target']}s,{intent['shot_count']['min']}-{intent['shot_count']['max']} 镜) - -## 受众 -{intent['audience']} - -## 梗概(100–200 词) -> agent 填。包含:开场钩子、主角、冲突、转折、结局。显式复述受众与类型(如"本片面向科技爱好者,类型为叙事短片")。 - -## 人物 -> agent 填。每个主要人物:姓名/称呼、年龄、身份、一句话性格、视觉锚点(外观特征,后续 character-register Stage 6 用)。 - -| 人物 | 年龄 | 身份 | 性格 | 视觉锚点 | -|------|------|------|------|---------| -| (agent 填) | | | | | - -## 分场(同时间同地点分一场) -> agent 填。每场:地点、时间、出场人物、核心动作、叙事目的。 - -### 场 1 -- 地点:(agent 填) -- 时间:(agent 填) -- 出场人物:(agent 填) -- 核心动作:(agent 填) -- 叙事目的:(agent 填,如"建立主角日常") - -### 场 2(如有) -(agent 填) -""" - story_path.write_text(stub, encoding="utf-8") - - print(f"[done] story.md 模板已落:{story_path}") - print(f"[next] agent 填梗概/人物/分场 → 跑 script-write(Stage 3)") - - -if __name__ == "__main__": - main() diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py index 2186f167..78907c35 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py @@ -22,6 +22,11 @@ from pathlib import Path +def die(msg: str) -> None: + print(f"[error] {msg}", file=sys.stderr) + sys.exit(1) + + def main() -> None: parser = argparse.ArgumentParser(description="Stage 4 storyboard-build") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") @@ -45,7 +50,7 @@ def main() -> None: stub = { "stage": 4, "shots": [], - "instruction": "agent 据剧本拆镜,每镜按下 schema 填。镜数应在 intent.json 的 shot_count.min-max 区间。", + "instruction": "agent 据剧本拆镜,每镜按下 schema 填。", "shot_schema": { "id": "shot-01", "scene": 1, diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh index 40f44668..5ceb940f 100755 --- a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh +++ b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh @@ -24,13 +24,12 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 流程: 通用制作流程(expert-video SKILL.md 的 Stage 0→14 + 两闸门)是做**任何**视频都要遵循的基准, 不是"没指定类型时的备选"。Brief 指定 workflow 时,先读包内 workflows/.md, - 按其阶段裁剪调用下列子命令;未指定时只按通用制作流程走,由 intent-router 定档位。 + 按其阶段裁剪调用下列子命令;未指定时只按通用制作流程走。创意不清时先走 story-develop + intake workflow(workflows/story-develop.md)与甲方收敛 Brief,再进 script-write。 子命令(按阶段序): - intent-router Stage 1 意图路由 → 三档脚本模板(故事讲述型/纯画面动效型/蒙太奇剪接型) reference-concepts 可选 吃甲方给的参考拆解报告出 2–3 差异化概念 - story-develop Stage 2 idea → 故事(分场) - script-write Stage 3 故事 → 分场剧本(含 enhancement_cues + delivery_cues) + script-write Stage 3 Brief 创意 → 分场剧本(含 enhancement_cues + delivery_cues) script-self-eval Stage 3 脚本自评 N 维打分 storyboard-build Stage 4 剧本 → 镜头表 shot-decompose Stage 5 每镜拆首尾帧 + 运动描述 + variation_type diff --git a/crews/content-producer/skills/expert-video/workflows/narration-video.md b/crews/content-producer/skills/expert-video/workflows/narration-video.md index ab237260..b6ad46bd 100644 --- a/crews/content-producer/skills/expert-video/workflows/narration-video.md +++ b/crews/content-producer/skills/expert-video/workflows/narration-video.md @@ -26,8 +26,7 @@ Brief 里写 `workflow: narration-video`,或 Brief 交付了口播文案 / 真 | 阶段 | 本 workflow 的做法 | |------|--------------------| | Stage 0 | 除 Brief 外,必须确认口播文案 / 录音**已到位**:绝对路径存在、可解码、时长可读;缺 → 向甲方要,不开工 | -| Stage 1 | 档位一般为故事讲述型;纯画面动效型不适用(口播是主干) | -| Stage 2–3 | 跳过 `story-develop` / `script-write`;口播稿原样落 `script/script.md` | +| Stage 3 | `script-write` 落稿锁定:口播稿原样落 `script/script.md`,不重写策略文案(无独立故事阶段——story-develop 已退役为 intake workflow,口播类创意已定,不触发) | | Stage 3b | `script-self-eval` 只做检查,不改写:总字数 vs 目标时长(按 6–8 字/秒折算)、句长、合规敏感词、是否存在无法配画面的抽象段 | | Stage 4 | 分镜按口播段落切:每段"讲什么 → 看什么"写清;不写不可见 | | Stage 5 | 画面全来自现成素材时跳过;需要 AIGC 补画面时保留 | diff --git a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md index ab092ad0..638f8ff6 100644 --- a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md +++ b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md @@ -108,9 +108,7 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): | 阶段 | 本 workflow 的做法 | |------|--------------------| -| Stage 1 | 档位固定 **故事讲述型(narrative)** | -| Stage 2 | 甲方交付口播时跳过;未交付时故事梗概必须写明三段结构与反转手法 | -| Stage 3 | `script/script.md` 按段标注(解说 / 反转 / 植入)+ 每段时长占比 + 反转点时间码;自评过「反转幅度与接入丝滑度」四问,GATE A 随脚本交答案 | +| Stage 3 | `script/script.md` 按段标注(解说 / 反转 / 植入)+ 每段时长占比 + 反转点时间码;未交付口播时剧本须体现三段结构与反转手法(reversal-ad 本就是叙事型,无需再分类),甲方交付口播时落稿锁定;自评过「反转幅度与接入丝滑度」四问,GATE A 随脚本交答案 | | Stage 4 | 镜头表每镜标所属段;反转点前后各一镜写明衔接意象(意象桥) | | Stage 6 | 现成素材轨且无 AIGC 角色时跳过角色三视图 | | Stage 7–8 | slot 按段规划,解说段 slot 数最多;植入段 slot 必须能承载 Brief 的价值点 | diff --git a/crews/content-producer/skills/expert-video/workflows/story-develop.md b/crews/content-producer/skills/expert-video/workflows/story-develop.md new file mode 100644 index 00000000..983d0a8e --- /dev/null +++ b/crews/content-producer/skills/expert-video/workflows/story-develop.md @@ -0,0 +1,40 @@ +# Workflow:Story Develop(Brief intake · 创意澄清) + +**这是 intake 类 workflow,不是 type 类 workflow。** type workflow(`narration-video` / `collage-broll` / `reversal-ad`)细化"某类视频怎么制作",是 `Brief.workflow` 的取值;本 workflow 解决"甲方还没把创意讲清楚时,怎么和他对话把 Brief 收敛出来",**不是 `Brief.workflow` 的取值**,也不与 type workflow 互斥。它在 **Stage 0(Brief intake)** 阶段触发,与任何 type workflow 正交可组合——收敛出 Brief 后,仍按通用制作流程 + 对应 type workflow 执行。 + +## 何时触发 + +- **模式 B(直接对接用户)**:用户没给 Brief,或只给了模糊想法("做个短片""帮我策划一下")。 +- **模式 A(Subagent 承制)**:main 的 Brief 缺关键字段(创意 / 核心传达不清、规格缺失),需向 Brief owner 澄清。 +- 触发判据:**Brief 的"创意"不足以直接写 `script.md`**。够用就不触发,直接进 Stage 3 `script-write`。 + +> ❗ 本 workflow 是**澄清与收敛**,不是替甲方创作。选题方向、品牌事实、卖点承诺、CTA 口径归甲方;我只把甲方脑子里的创意问清楚、整理成 Brief,不自行脑补,也不反过来指挥甲方。 + +## 怎么做(对话式,不发问卷) + +不要一次甩七个问题。先从目的与受众问起,让对话自然流动,缺什么补什么;甲方已说清的不重复问。模糊想法("做个短片")不算确认,不得据此调渲染类工具。 + +需要收敛清楚的点(对应 Brief 字段,**创意 + 规格为主**): + +1. **目的 / 核心传达**:这条视频要达成什么?观众看完该记住什么、做什么? +2. **受众 / 观看理由**:给谁看?他们为什么会看下去? +3. **平台**:发哪(抖音 / 视频号 / 小红书 / B站…)?决定画幅、时长带与合规边界。 +4. **创意 / 内容**:讲什么故事、呈现什么内容?——这是 Brief 的核心,**甲方出**;我只追问澄清,不代拟方向。 +5. **规格**:大致片长、横竖屏、画面风格倾向、有无口播(谁的声音)。 +6. **业务植入与 CTA**:植入什么、CTA 主目标与句式(口径归甲方)。 +7. **素材 / 参考 / 约束**:有无现成素材(**绝对路径**)、参考片、deadline、必含内容。 + +调性 / 风格倾向(如庄重 / 诙谐 / 紧迫)影响我的制作手法选择,问清后记 `script/decisions.json`(备选 + 置信度 + 理由)。**不再做 narrative / motion / montage 三档分类**——那是已退役的 `intent-router` 的产物,对下游制作无实际作用(它当年只填一个故事模板页眉,没有任何脚本消费它)。 + +## 产出 + +- **模式 B**:代用户整理 `brief.md`(创意 + 规格 + 口播文案如有),**发用户确认定稿后才开工**。 +- **模式 A**:把澄清结果补进 Brief(或向 Brief owner 确认缺口已补齐),**不重开需求讨论**。 +- 收敛完成的标志:**`brief.md` 的创意足以直接写 `script.md`**。然后回通用制作流程 **Stage 3(`script-write`)**。 + +## 边界 + +- **不产 `intent.json`、不产 `story.md`**——这两个中间产物已废除。创意直接落 `brief.md`;故事 / 剧本的可拍化在 Stage 3 `script-write` 由我据 Brief 创意做,不再有一个独立的"写故事"阶段。 +- 甲方已交付口播文案 / 录音时,叙事以口播稿为准,本 workflow 只需确认口播稿到位,不另起创意。 +- 不做视频下载 / 转写 / 抽帧(那是 main 的 `viral-chaser`);甲方给了参考拆解报告才用可选工具 `reference-concepts`。 +- 不替甲方做需求决策;缺信息就问,不脑补品牌事实与授权。 From e0140e2fd9d46f3544635a5e7bbc94c12900112e Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 07:38:10 +0800 Subject: [PATCH 04/25] =?UTF-8?q?docs(main,docs):=20=E6=B8=85=E7=90=86=20i?= =?UTF-8?q?ntent-router=20=E9=80=80=E5=BD=B9=E5=90=8E=E7=9A=84=20stale=20?= =?UTF-8?q?=E6=A1=A3=E4=BD=8D=E9=99=88=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 承接 expert-video 132c45b(intent-router 已删)。main 侧与架构 doc 仍写着 "未指定 workflow → CP 由 Stage 1 intent-router 定档位(narrative/motion/montage)", 该步骤已不存在。统一改为"叙事 / 动效 / 蒙太奇手法由 CP 据创意自定"。 20 处替换 / 14 文件: - 3 个平台 SKILL.md(douyin/xhs/wx-channel)视频分工硬边界 - 3 个 video-dna-framework.md 制作指向表 + 边界说明 - 3 个 build_style_profile.py 的 video-form 维度提示串 - 4 个 content-production.md 的 Brief workflow 字段 + 委托说明 - viral-chaser/SKILL.md 转交表 - docs/expert-pack-dna-architecture.md 的 video-form 说明 + Pipeline 废除记录 验证:grep 零残留"定档位"指令;剩余 intent-router 提及均为"已退役"语境; 3 个 build_style_profile.py py_compile OK;.py 字符串常量完整。 --- crews/main/skills/expert-douyin/SKILL.md | 2 +- .../douyin-style-profiler/references/video-dna-framework.md | 4 ++-- .../douyin-style-profiler/scripts/build_style_profile.py | 2 +- .../main/skills/expert-douyin/workflows/content-production.md | 2 +- crews/main/skills/expert-wx-channel/SKILL.md | 2 +- .../references/video-dna-framework.md | 4 ++-- .../wx-channel-style-profiler/scripts/build_style_profile.py | 2 +- .../skills/expert-wx-channel/workflows/content-production.md | 4 ++-- crews/main/skills/expert-xhs/SKILL.md | 2 +- .../xhs-style-profiler/references/video-dna-framework.md | 4 ++-- .../tools/xhs-style-profiler/scripts/build_style_profile.py | 2 +- crews/main/skills/expert-xhs/workflows/content-production.md | 4 ++-- crews/main/skills/viral-chaser/SKILL.md | 2 +- docs/expert-pack-dna-architecture.md | 4 ++-- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/crews/main/skills/expert-douyin/SKILL.md b/crews/main/skills/expert-douyin/SKILL.md index 97d30f37..c806ef4f 100644 --- a/crews/main/skills/expert-douyin/SKILL.md +++ b/crews/main/skills/expert-douyin/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),档位由 Stage 1 定。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 风格与 DNA diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md index 9d496675..1a94854b 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md @@ -103,12 +103,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,Stage 1 定档位(narrative / motion / montage) | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,档位由 Stage 1 `intent-router` 定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定(intent-router 档位分类已退役)。 ## Focus ID 表 diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py index c9af3557..4a598110 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py @@ -104,7 +104,7 @@ "topic-angle": "- 单篇观测:选题类型、选题入口(现象 / 问题 / 冲突 / 数据 / 热点 / 挑战 / 个人经历)、目标人群为什么要看完(理解 / 判断 / 行动 / 避坑 / 身份认同 / 情绪共鸣)。\n- 边界:只记本篇,不判断跨篇稳定性;不评价选题好坏。", "title-cover": "- 单篇观测:标题类型(痛点 / 数字 / 反差 / 悬念 / 身份点名 / 搜索长尾)、标题与描述原文、话题标签策略。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉与图文关系、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」,不得凭正文或标题想象补齐。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程 + Stage 1 定档位),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "narration-script": "- 子模块(仅口播类作品启用):起(从什么起步)、承(靠什么推进)、转(转折触发)、合(收束方式;CTA 的目标、位置与句式记在 `interaction-cta`),以及人称与语气、句长与语速、签名式表达。\n- 边界:非口播类或证据不足时写「未启用 / 未观测」;不得把单篇句式直接上升为规则。", "body-voice": "- 单篇观测:开头钩子(原文摘录)、正文组织方式(清单体 / 教程步骤 / 故事线 / 对比 / 观点输出)、分行与段落节奏、口语化程度与人称、emoji 与标点用法、签名式表达。\n- 证据边界:脚本统计只给句长 / 行数 / emoji / 标签等线索;口头禅与签名表达必须回读原文确认。", diff --git a/crews/main/skills/expert-douyin/workflows/content-production.md b/crews/main/skills/expert-douyin/workflows/content-production.md index fc084a80..608ea708 100644 --- a/crews/main/skills/expert-douyin/workflows/content-production.md +++ b/crews/main/skills/expert-douyin/workflows/content-production.md @@ -194,7 +194,7 @@ DNA 约束的是选题与观看理由、标题与封面写法、内容创意原 - 视频名 / slug: - platform:douyin -- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,Stage 1 自定档位) +- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点(+ 反转设计,如为反转植入类) diff --git a/crews/main/skills/expert-wx-channel/SKILL.md b/crews/main/skills/expert-wx-channel/SKILL.md index 959f33d6..0ddbcd67 100644 --- a/crews/main/skills/expert-wx-channel/SKILL.md +++ b/crews/main/skills/expert-wx-channel/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),档位由 Stage 1 定。Brief **不含 DNA 信息**、**不写实现路径**(参考脚本、引擎参数之类实现手段归 CP,规则详见 Content Production Workflow 的 Brief 硬性规则),main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。Brief **不含 DNA 信息**、**不写实现路径**(参考脚本、引擎参数之类实现手段归 CP,规则详见 Content Production Workflow 的 Brief 硬性规则),main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 平台速查 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md index 69162ac8..14530cde 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md @@ -103,12 +103,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,Stage 1 定档位(narrative / motion / montage) | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,档位由 Stage 1 `intent-router` 定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定(intent-router 档位分类已退役)。 ## Focus ID 表 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py index bab59fca..b19a1ed7 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py @@ -88,7 +88,7 @@ "topic-angle": "- 单篇观测:选题类型、选题入口(现象 / 问题 / 冲突 / 数据 / 热点 / 挑战 / 个人经历)、目标人群为什么要看完(理解 / 判断 / 行动 / 避坑 / 身份认同 / 情绪共鸣)。\n- 边界:只记本篇,不判断跨篇稳定性;不评价选题好坏。", "title-cover": "- 单篇观测:**短标题**(发布页可填,官方称填了能获得更多流量;作品管理页不展示,取数时拿不到)与**视频描述**(含话题标签)两者的原文与写法模式。\n- 取数边界:抓取到的作品只有视频描述;短标题拿不到时写「未观测(管理页不展示)」,不得把描述当短标题。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程 + Stage 1 定档位),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "biz-implant": "- 单篇观测:是否有业务植入(纯内容 / 软植入 / 硬广直给)、植入位置与时机、植入载体(剧情道具 / 口播一句话 / 字幕卡片 / 场景背景 / 案例与数据 / 清单第 N 项 / 教程步骤内嵌 / 产品截图 / 购物车或留资组件 / 主页与私信引导)、植入方式原型(反转植入 / 痛点→方案 / 场景带入 / 实测对比 / 身份认同 / 口碑故事 / 教程内嵌 / 硬广直给)、内容与业务的衔接句(原文摘录)、植入密度与占比、品牌词与产品名出现方式与频次。\n- 证据要求:位置 + 载体 + 原文摘录三样齐全;本篇无植入时写「无植入(纯内容)」,不得留空。\n- 边界:只记植入套路,不写逐句广告文案;形态层面的「影视解说 + 反转植入」由 `video-form` 记形态与制作指向。", "interaction-cta": "- 单篇观测:行动目标(关注 / 评论 / 收藏 / 转发 / 私信 / 主页点击 / 进店 / 咨询 / 搜索品牌词 / 购物车 / 直播预约)与本篇主目标、CTA 出现位置与时机(口播收尾句 / 字幕卡 / 片尾贴片 / 描述区 / 正文结尾 / 图组末图 / 评论区)、CTA 句式与原文摘录(命令式 / 提问式 / 利益式 / 身份式 / 悬念式)、一篇放几个行动、诱因设计(利益点 / 情绪 / 身份认同 / 稀缺)、与业务转化目标的对应。\n- 合规边界:不隐藏站外联系方式、不绕平台检测、不以利益换互动,记为必须避免项。", diff --git a/crews/main/skills/expert-wx-channel/workflows/content-production.md b/crews/main/skills/expert-wx-channel/workflows/content-production.md index f0501ef6..cfce83ec 100644 --- a/crews/main/skills/expert-wx-channel/workflows/content-production.md +++ b/crews/main/skills/expert-wx-channel/workflows/content-production.md @@ -200,7 +200,7 @@ DNA 约束的是选题与观看理由、短标题与视频描述写法、内容 - 视频名 / slug: - platform:wx_channel -- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,Stage 1 自定档位) +- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点(+ 反转设计,如为反转植入类) @@ -225,7 +225,7 @@ Brief 硬性规则: 2. 口播类视频:按 DNA 的 `narration-script` 子模块写口播终稿 `wx_channel/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。口播子模块未启用时 Brief 写「口播文案:不适用」或只给要点,由 CP 按其 workflow 组织旁白,main 不再规定逐句台词。用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 3. 参考模式下,把选题与创意结论写进 Brief 的「内容创意」段即可;`viral-chaser` 拆解报告是 main 的采样材料,**不作为 Brief 附件交给 CP**。 -4. spawn `content-producer` 委托制作:只交 Brief + 素材绝对路径 + 口播文案 / 录音;不指定 CP 的工作区与制作方案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),档位由 Stage 1 定。 +4. spawn `content-producer` 委托制作:只交 Brief + 素材绝对路径 + 口播文案 / 录音;不指定 CP 的工作区与制作方案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 5. Brief 变更时更新版本并推送变更要点;已开工中间产物按新版取舍,弃用部分记入交付说明。制作中发现 Brief 无法执行(素材缺失、时长超标)时,由 CP 回报、main 与用户确认后改 Brief,CP 不擅自改策略。 6. CP 交付后,按其回报的绝对路径把成片与封面取回 `wx_channel/outputs//`(`video.mp4` / `cover.jpg`),并把交付说明要点记入作品目录。用户直接提供成片时校验格式(`.mp4` / `.mov` / `.avi` / `.webm`)与时长后复制进作品目录。 diff --git a/crews/main/skills/expert-xhs/SKILL.md b/crews/main/skills/expert-xhs/SKILL.md index b3092feb..0ac87d2b 100644 --- a/crews/main/skills/expert-xhs/SKILL.md +++ b/crews/main/skills/expert-xhs/SKILL.md @@ -50,7 +50,7 @@ DNA 存储目录是 `xhs/dna/`。未指定 DNA 时默认使用并更新 `dna-0` DNA 是**从一批作品样本提取并聚合出的内容生产规则集**:图文 10 维——选题与观看理由、标题与封面图组、内容创意、**匹配的用户问题**、正文表达与语气、图组视觉、**业务植入套路**、**互动引导与 CTA 套路**,加账号运营子模块(简介写法、内容形式比例、发布习惯);视频 11 维——前四项加**业务植入套路**、**互动引导与 CTA 套路**、视频内容形态与制作指向、制作规格与视听倾向、口播文案子模块与账号运营子模块。搜索维度是小红书必备:最大流量池来自搜索,关键词必须落到用户可能的提问原句。DNA 指导 main agent 写图文或出视频 Brief(+ 口播文案),不规定创作细节与成片制作。维度框架 v2 位于 `xhs-style-profiler` 的 `references/note-dna-framework.md` 与 `references/video-dna-framework.md`。 -**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),档位由 Stage 1 定。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 数据与记录 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md index 696c1029..ab1635da 100644 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md @@ -106,12 +106,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,Stage 1 定档位(narrative / motion / montage) | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,档位由 Stage 1 `intent-router` 定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定(intent-router 档位分类已退役)。 ## Focus ID 表 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py index dc475752..2717d4f5 100755 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py @@ -105,7 +105,7 @@ "title-cover": "- 单篇观测:标题类型(痛点 / 数字 / 反差 / 悬念 / 身份点名 / 搜索长尾)、标题与描述原文、话题标签策略。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉与图文关系、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」,不得凭正文或标题想象补齐。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", "search-intent": "- 单篇观测:本篇命中的关键词(核心词 / 痛点词 / 场景词 / 人群词)、用户可能的提问原句、搜索意图层级、标签承载的搜索意图。\n- 边界:单篇只记候选;聚合后才形成「关键词 → 用户问题 → 内容形式」的搜索意图地图。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程 + Stage 1 定档位),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "narration-script": "- 子模块(仅口播类作品启用):起(从什么起步)、承(靠什么推进)、转(转折触发)、合(收束方式;CTA 的目标、位置与句式记在 `interaction-cta`),以及人称与语气、句长与语速、签名式表达。\n- 边界:非口播类或证据不足时写「未启用 / 未观测」;不得把单篇句式直接上升为规则。", "body-voice": "- 单篇观测:开头钩子(原文摘录)、正文组织方式(清单体 / 教程步骤 / 故事线 / 对比 / 观点输出)、分行与段落节奏、口语化程度与人称、emoji 与标点用法、签名式表达。\n- 证据边界:脚本统计只给句长 / 行数 / emoji / 标签等线索;口头禅与签名表达必须回读原文确认。", diff --git a/crews/main/skills/expert-xhs/workflows/content-production.md b/crews/main/skills/expert-xhs/workflows/content-production.md index 0924e76e..57d909b5 100644 --- a/crews/main/skills/expert-xhs/workflows/content-production.md +++ b/crews/main/skills/expert-xhs/workflows/content-production.md @@ -162,7 +162,7 @@ DNA template 是 main agent 的生产输入模板:**图文 DNA 的 template = - 视频名 / slug: - platform:xhs -- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,Stage 1 自定档位) +- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点 @@ -216,7 +216,7 @@ Brief 硬性规则: | --- | --- | | 用户直接提供成片 | 校验格式与时长,复制到作品目录 | | 已有素材需简单加工 | main 用 `video-edit` / `talking-head-cut` 处理 | -| 全案制作 | 委托 `content-producer`:只交 Brief + 素材绝对路径 + 口播文案 / 录音,不指定 CP 工作区;指定 `workflow` 必须采用;未指定时 CP 按其通用制作流程做,档位由 Stage 1 定。成片与封面按 CP 回报的绝对路径取回作品目录 | +| 全案制作 | 委托 `content-producer`:只交 Brief + 素材绝对路径 + 口播文案 / 录音,不指定 CP 工作区;指定 `workflow` 必须采用;未指定时 CP 按其通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。成片与封面按 CP 回报的绝对路径取回作品目录 | 视频封面优先从成片选帧;需要更强视觉冲击时用 `siliconflow-img-gen`。 diff --git a/crews/main/skills/viral-chaser/SKILL.md b/crews/main/skills/viral-chaser/SKILL.md index 8f87ed9f..069647ef 100644 --- a/crews/main/skills/viral-chaser/SKILL.md +++ b/crews/main/skills/viral-chaser/SKILL.md @@ -234,7 +234,7 @@ Read: /ref//references/frames/frame_01_3s.jpg | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → Reversal Ad workflow | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → Narration Video workflow | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → Collage B-roll workflow | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**(CP 按其通用制作流程做,Stage 1 定档位:narrative / motion / montage) | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**(CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法) | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit` | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut` | | 产品操作录屏 | main `ui-demo` | diff --git a/docs/expert-pack-dna-architecture.md b/docs/expert-pack-dna-architecture.md index 94a8047e..1c346387 100644 --- a/docs/expert-pack-dna-architecture.md +++ b/docs/expert-pack-dna-architecture.md @@ -413,7 +413,7 @@ crews//skills/expert-/tools/-style-profiler/ 3. **子模块不是独立 DNA**:口播文案子模块(`narration-script`,参考微信的起承转合)只在口播类作品启用,用于指导 main 写同类型视频的口播文案;账号运营子模块(`account-bio`、`content-mix-cadence`)只在样本来自对标账号批量提取时填写,**只写进 DNA 文档、不进 template**。 4. **视频 DNA 不含创作细节**:不写脚本结构、逐句台词、镜头表、转场与编码参数。视频类 template = **Brief.md 正文模板 + 口播文案模板(可选)**;图文类 template = 图文写作模板。 -`video-form`(视频内容形态:口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场 / 录屏 / 图文卡片)必须聚合成明确的**制作指向**,且只能写真实存在的资源名:Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程 + Stage 1 定档位),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`)。Brief 的 `workflow` 字段据此填写。 +`video-form`(视频内容形态:口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场 / 录屏 / 图文卡片)必须聚合成明确的**制作指向**,且只能写真实存在的资源名:Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`)。Brief 的 `workflow` 字段据此填写。 采样侧配套:`viral-chaser` 的输出必须够喂这套框架——视频 meta(时长、宽高与横竖屏、发布时间、作者与简介、话题标签、互动数据)、覆盖全片的关键帧(含 25%/50%/63%/75%/90% 比例点,反转点通常在 55%-76%)、按时间占比的结构拆解与反转点位置、内容形态判定与制作指向,以及可直接喂 profiler 的 DNA 样本文字稿格式。 @@ -479,7 +479,7 @@ crews/content-producer/ - **workflow 的语义随 crew 变**:平台运营包里 workflow = 业务场景(起号 / 生产 / 复盘);content-producer 里 workflow = **某一类型视频 / 某一类设计任务怎么做**。制作流程本身高度程式化、跨类型一致,写在包根 `SKILL.md`(阶段链、两闸门、护栏、Stage 12 工具箱);类型差异(叙事套路、素材来源、声画组织方式)才写进 workflow。 - **原技能整体降级为 tool**:`video-producer` / `collage-broll` / `design-full` 从路由面消失,成为包内工具;wrapper 名与子命令不变(靠 `skill-wrappers.sh` 的 `*/tools/*/` 扫描层暴露),调用方零改动。 - **`manim-explainer` 删除**:能力已被 `expert-video` 的通用制作流程与 AIGC 动画路径覆盖。 -- **原「Pipeline 机制」废除**:Brief 字段从 `pipeline` 改为 `workflow`;不存在「viral-chaser 报告输入」阶段(追爆拆解是 main 的采样动作,CP 只吃 Brief),Stage 1 由 `intent-router` 承担定档位,`reference-concepts` 降为可选工具(仅直接对接用户模式下用户给了参考拆解报告时用)。 +- **原「Pipeline 机制」废除**:Brief 字段从 `pipeline` 改为 `workflow`;不存在「viral-chaser 报告输入」阶段(追爆拆解是 main 的采样动作,CP 只吃 Brief),intent-router 档位分类已退役,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定,`reference-concepts` 降为可选工具(仅直接对接用户模式下用户给了参考拆解报告时用)。 - **两种工作模式**:A 作为 main 的 subagent(甲方 = main,Brief 已确认,不重开需求讨论);B 直接对接用户(甲方 = 用户,可能不专业,先引导确认 Brief、落实素材位置与存在性,口播文案代拟需用户确认)。两种模式都坚持乙方角色、都自建工作区。 - **运行期数据只写 CP 自己的 Workspace**:`output_videos//`(视频)与 `design_assets/YYYY-MM-DD-<任务名>/`(设计);不写进 main 的平台目录,也不让 main 代建。 From b58cdcd06e94e09dfabfeb1a8bdc02a39cc2d451 Mon Sep 17 00:00:00 2001 From: bigbrother666sh Date: Wed, 16 Sep 2026 12:17:40 +0800 Subject: [PATCH 05/25] =?UTF-8?q?refactor(expert-video):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=80=9A=E7=94=A8=E5=88=B6=E4=BD=9C=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E4=B8=8E=E5=90=84=E7=B1=BB=20workflow=20=E7=BB=86=E8=8A=82?= =?UTF-8?q?=EF=BC=8C=E7=A7=BB=E9=99=A4=E8=BF=87=E6=97=B6=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skills/expert-video/SKILL.md | 47 ++++++++----------- .../expert-video/workflows/narration-video.md | 18 ------- .../expert-video/workflows/reversal-ad.md | 24 +++------- .../expert-video/workflows/story-develop.md | 7 ++- crews/main/skills/expert-douyin/SKILL.md | 2 +- crews/main/skills/expert-wx-channel/SKILL.md | 2 +- .../workflows/content-production.md | 2 +- crews/main/skills/expert-xhs/SKILL.md | 2 +- 8 files changed, 33 insertions(+), 71 deletions(-) diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index 9aca6579..6ad7ef03 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -55,7 +55,7 @@ metadata: | 层 | 是什么 | 怎么用 | |----|--------|--------| -| **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→14 阶段链(Stage 1-2 已退役,基线从 Stage 3 script-write 起)、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | +| **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→14 阶段链、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | | **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:创意不足以直接写剧本时触发 | | **工具说明**(`tools/<工具>/SKILL.md`) | 每个子命令的入参、产物路径、退出码与旁路条件 | 调用前查;本文不重复参数细节 | @@ -72,8 +72,8 @@ metadata: | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 一句文稿 → 一个视觉隐喻 → 静帧 → i2v,三道闸门与 Gate 3 批量调度 | - Brief 指定了 `workflow`:**先读对应文档并直接采用**,不得替换成自创流程。 -- Brief 未指定 `workflow`:仍走通用制作流程;创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 3 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`,**不再做三档分类**(intent-router 已退役)。 -- 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。**活儿小也不跳过** Stage 0 Brief intake、Stage 13 自检与响度归一化、Stage 14 交付三件套。 +- Brief 未指定 `workflow`:仍走通用制作流程;创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 3 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`。 +- 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。 > `story-develop` 是 **intake 类 workflow**(Stage 0 创意澄清,**不是 `Brief.workflow` 取值**),与上表 type 类 workflow 正交:任何类型的视频,创意不清都先走它收敛 Brief,再按通用制作流程 + 对应 type workflow 执行。详见 `workflows/story-develop.md`。 @@ -110,29 +110,25 @@ output_videos// # workflow 文档在技能包内,不是项目目录内容;项目目录只放 Brief、素材、脚本、渲染与交付产物。 -## 通用制作流程(Stage 0→14,两闸门;Stage 1-2 已退役为 intake workflow) +## 通用制作流程(Stage 0→15,两闸门) **我做任何视频都走这条链**;类型 workflow 只在此基础上裁剪与细化。每段的子命令是 `video-producer` 工具下的一个独立脚本,按流程逐个调。 ``` -Stage 0 Brief intake 模式 A:读甲方 Brief,核对字段,缺口向 Brief owner 澄清 - 模式 B:用户未给 Brief 时引导讨论 → 代拟 brief.md → 发用户确认 - 创意不足以直接写剧本时 → 走 story-develop intake workflow(workflows/story-develop.md) - 与甲方对话式收敛创意 + 规格,落定 brief.md(无子命令;意图澄清在此消化,不再产 intent.json) - 〔原 Stage 1 intent-router / Stage 2 story-develop 已退役:三档分类对下游制作无实际作用, - 故事膨胀并入 Stage 3;意图澄清升格为 Stage 0 的 story-develop intake workflow〕 -Stage 3 script-write Brief 创意 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) - 基线生产从此开始。甲方已交付口播文案时改为落稿锁定:原样落 script/script.md,不重写策略文案 -Stage 3b script-self-eval 脚本自评 N 维打分,任一维 <3 必返工(落稿锁定时只检查不改写) -Stage 4 storyboard-build 剧本 → 镜头表(每镜叙事目的/机位复用/位置朝向/不写不可见) -Stage 5 shot-decompose 每镜拆首帧静照/尾帧静照/运动描述(variation_type 三档) -Stage 6 character-register 角色三视图 front/side/back + static/dynamic features 拆分 +Stage 0 Brief intake 读甲方 Brief,核对字段,缺口向 Brief owner 澄清; + 甲方未给 Brief 或 Brief 不足以直接写剧本时 → 走 story-develop intake workflow(workflows/story-develop.md) +Stage 1 script-write Brief 创意 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) + 基线生产从此开始。 +Stage 2 script-self-eval 脚本自评 N 维打分,任一维 <3 必返工(落稿锁定时只检查不改写) +Stage 3 storyboard-build 剧本 → 镜头表(每镜叙事目的/机位复用/位置朝向/不写不可见) +Stage 4 shot-decompose 每镜拆首帧静照/尾帧静照/运动描述(variation_type 三档) +Stage 5 character-register 角色三视图 front/side/back + static/dynamic features 拆分 ────── GATE A:文本闸门(脚本+分镜+机位+角色全齐,停,发甲方审)────── -Stage 7 slot-plan 素材 slot 规划(template + hero slot + tone→slot 数) -Stage 8 asset-resolve 按 slot 取素材(Fast path:多源并发搜 + 缩略图人核 + rejected_picks 落盘) +Stage 6 slot-plan 素材 slot 规划(template + hero slot + tone→slot 数) +Stage 7 asset-resolve 按 slot 取素材(Fast path:多源并发搜 + 缩略图人核 + rejected_picks 落盘) 甲方已给素材时:先入库校验(可解码、分辨率/帧率/时长/音轨、授权记录),缺口才补搜 -Stage 9a slideshow-risk 六维幻灯风险打分(pre-compose 闸门,≥4.0 fail 不许进 compose) -Stage 9b delivery-promise-lock 交付承诺八类锁定 + motion_ratio 预估 +Stage 8 slideshow-risk 六维幻灯风险打分(pre-compose 闸门,≥4.0 fail 不许进 compose) +Stage 9 delivery-promise-lock 交付承诺八类锁定 + motion_ratio 预估 ────── GATE B:素材闸门(素材齐+计划过审,停,发甲方看 contact sheet)────── Stage 10 render-shot 按 slot 渲染(AIGC 走 aigc-video-gen i2v 首尾帧插值;静图走 siliconflow-img-gen) motion-graphics Stage 10 第二条渲染路径:程序化逐帧动态图形(声明式 spec,产品段动效/标题动画/ @@ -140,29 +136,28 @@ Stage 10 render-shot 按 slot 渲染(AIGC 走 aigc-video-gen i2v 首尾 Stage 11 mix-audio 配音配乐四场景分流(A 人物对话声画同出 / B 旁白一次性 TTS 带字级时间戳 + 对齐 / C BGM 成片后统一生成(优先 bgm-library 免版税曲库,pexels/pixabay 并列;定制风格用 aigc-video-gen music)/ D 甲方口播录音 → ASR 时间戳 → 按时间戳补素材) - narration-layout 逐句 TTS 模式(每句独立 mp3):对齐镜头起点 + 防重叠守卫 + 越界断言 + SRT + 可选混音; + narration-layout 逐句 TTS 模式(每句独立 mp3):素材前后各留1s气口 + 防重叠守卫 + 越界断言 + 可选混音; 整段模式的时间戳对齐仍走 narration-align,两者互补 Stage 12 assemble 按序拼接成片(原子工具箱,见下节,我按场景组合,不写死流程) Stage 13a video-review 公共 video-review 技术自检(强制闸门,verdict=pass 才继续) Stage 13b motion-audit motion_led 抽查(兑付 delivery-promise) Stage 13c normalize 响度归一化到 -14 LUFS(**必跑**:`video-producer normalize`) Stage 14a make-cover 封面(siliconflow-img-gen,必含封面主文案) -Stage 14b 交付 回报成片 + 封面 + final-deliver.md 的绝对路径与关键参数 +Stage 15 交付 回报成片 + 封面 + final-deliver.md 的绝对路径与关键参数 ``` - **产物文件存在性即 checkpoint**:子命令先查产物文件是否存在,存在则 load 不重生成(允许手改 JSON 后续跑);要改哪段就重跑对应子命令,未改的不会重生成。 -- Stage 0–6 全是**文本产物**,付费生成前必停——GATE A 落在这条边界上;GATE B 落在素材就绪、pre-compose 闸门通过后,确认渲染前最终计划。 - 类型 workflow 指定时,阶段裁剪以该 workflow 文档为准;**闸门位置与"停下发甲方"的纪律不变**。甲方已在 Brief 中代理批准某道闸门时,把批准范围落 `gates/` 后继续。 - 可选工具 `reference-concepts`:甲方给了参考视频拆解报告时,据报告出 2–3 个差异化概念落 `reference/concepts.md`。 ## 闸门与护栏 -### GATE A(Stage 6 后):文本闸门 +### GATE A(Stage 5 后):文本闸门 文本产物全齐(脚本 + 分镜 + 机位 + 角色),**停下发甲方审**: - 呈交摘要:workflow 或创意定位、场次数、镜数、角色数、关键决策(路径 / 模型 / 风格选择的备选 + 置信度 + 理由) -- **结束本轮回复**,不许在同条回复里进 Stage 7 +- **结束本轮回复**,不许在同条回复里进 Stage 6 - 批准人是 Brief owner(模式 A = main agent,模式 B = 用户);甲方已在 Brief 中代理批准时,把批准范围落 `gates/gate-a.md` 后继续 - 批准是**逐闸门的**——早先的一句"你继续"不覆盖本闸门 @@ -214,9 +209,7 @@ env 依赖:`AWK_API_KEY`(静帧 / 视频生成)、`VOLC_ASR_*`(`narratio - **禁止声称没做过的事**:没有 tool result 或产物文件证明,不许声称已渲染 / 已生成 / 已改动。 - **禁止替甲方做需求决策**:选题方向、品牌事实、卖点承诺、业务植入与 CTA 口径、发布文案不由我定;Brief 没写就问。 - **禁止让甲方建工作区**:工作区自建;也不要把中间产物写进甲方(main / 用户)的目录。 -- **禁止把模糊想法擅自扩成多场多镜**:默认 1 场 3–5 镜,甲方要扩才扩。 - **禁止直接写 ffmpeg 命令**:所有 ffmpeg 调用走 `video-producer` / `collage-broll` 子命令或公共技能子命令;唯一例外是 workflow 文档里给出的既定 ffmpeg 模板(如 Collage B-roll 的首尾帧处理与 contact sheet 拼图),照抄执行不自创。 - **禁止自己做视频下载 / 转写 / 抽帧**:那是 main 的 `viral-chaser` 的活。 - **禁止引入 CLIP / torch 系本地模型**:素材匹配走 Fast path 人核缩略图。 -- **禁止扩充图库源**:保 Pexels + Pixabay 两源。 - **禁止批量生成撞运气**:逐条精做。 diff --git a/crews/content-producer/skills/expert-video/workflows/narration-video.md b/crews/content-producer/skills/expert-video/workflows/narration-video.md index b6ad46bd..b51ace0c 100644 --- a/crews/content-producer/skills/expert-video/workflows/narration-video.md +++ b/crews/content-producer/skills/expert-video/workflows/narration-video.md @@ -21,24 +21,6 @@ Brief 里写 `workflow: narration-video`,或 Brief 交付了口播文案 / 真 - 模式 B(用户直接对话)下用户只给大意时,可代拟文案,但必须发用户确认,定稿后才算 `voiceover`。 - 语义级剪辑(去口气词、智能剪重点)归 main 的 `talking-head-cut`;我只做几何级修整(切段/拼接/混音/烧字幕/补轨)。 -## 阶段裁剪(对通用制作流程的细化) - -| 阶段 | 本 workflow 的做法 | -|------|--------------------| -| Stage 0 | 除 Brief 外,必须确认口播文案 / 录音**已到位**:绝对路径存在、可解码、时长可读;缺 → 向甲方要,不开工 | -| Stage 3 | `script-write` 落稿锁定:口播稿原样落 `script/script.md`,不重写策略文案(无独立故事阶段——story-develop 已退役为 intake workflow,口播类创意已定,不触发) | -| Stage 3b | `script-self-eval` 只做检查,不改写:总字数 vs 目标时长(按 6–8 字/秒折算)、句长、合规敏感词、是否存在无法配画面的抽象段 | -| Stage 4 | 分镜按口播段落切:每段"讲什么 → 看什么"写清;不写不可见 | -| Stage 5 | 画面全来自现成素材时跳过;需要 AIGC 补画面时保留 | -| Stage 6 | 无 AIGC 角色时跳过 | -| Stage 7–8 | slot 按口播语义规划;甲方给了素材先入库校验(可解码/规格/授权),缺口才补搜 | -| Stage 9a | 幻灯风险重点查"一句口播一张静图"的幻灯片感 | -| Stage 9b | 交付承诺必须含音画同步与字幕样式 | -| Stage 11 | **核心阶段**:场景 B(TTS 一次性生成 + 字级时间戳)或场景 D(甲方录音 → ASR 时间戳);字幕必须来自对齐后的时间轴 | -| Stage 12 | `timeline-compose` 按字级时间戳对齐:每句口播不得越过对应镜头边界,连续口播保留呼吸间隔;切旁白段用 `clip-trim --pre-buffer 0.5` 防吞首字。甲方要求逐句 TTS(每句独立 mp3)时改走 `narration-layout`(对齐镜头起点 + 防重叠守卫 + 越界断言 + SRT + 混音一步),拼接用 `assemble --manifest --verify-fps` | -| Stage 13 | `video-review` + `motion-audit` + **响度归一化必跑** | -| Stage 14 | 封面主文案来自 Brief;口播金句作候选时需 Brief 允许 | - ## 声音规范 - **音色**:按 Brief 指定;未指定时选与内容气质匹配的音色,把备选 + 置信度 + 理由记 `script/decisions.json`。 diff --git a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md index 638f8ff6..537b9a53 100644 --- a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md +++ b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md @@ -16,13 +16,14 @@ Brief 里写 `workflow: reversal-ad` 时使用。本文是**通用制作流程 反转点落在总时长 **55%–76%** 之间(集中植入偏前,两段式偏后)。占比是创作约束,不是硬编码参数;Brief 指定时以 Brief 为准。产品植入段末尾可设**二次反转**承载 CTA(见「反转幅度与接入丝滑度」),其时长计入产品植入段,不另立第四段。 -## 反转手法(四选一,或叠加) +## 反转手法(五选一,或叠加) | 手法 | 机制 | 实现要点 | |------|------|----------| | 任务指引式 | 剧情停在求助节点 → 指路者给出答案,答案就是产品 | 「去往 X 就能化解 Y」句式;产品信息用标注贴图/攻略图呈现(箭头 + 关键词),不做广告贴片感 | -| 双关置换式 | 剧情核心意象与产品同名同形 | 意象必须在解说正文里先建立,反转时复用同一意象(沙盘 / 天池 / 秘境之类) | -| 身份彩蛋式 | 主角身份在反转处变成与产品相关的角色 | 身份转换要由剧情自然推出(求职、比赛、任务),不靠旁白硬说 | +| 双关置换式 | 剧情核心意象与产品同名同形 | 意象必须在解说正文里先建立,反转时复用同一意象 | +| 身份彩蛋式 | 主角身份在反转处变成与产品相关的角色 | 身份转换要由剧情自然推出,不靠旁白硬说 | +| 画面转场式 | 素材的某一帧平滑转到产品介绍的首帧(比如说,素材中女主角的眼睛出现倒影,然后放大到产品段首帧) | 转场过渡可以使用 AIGC,同时指定首帧和尾帧。| | 戏中戏式 | 剧情内的媒介(录像机屏幕、照片、窗外、手机屏)先出现产品画面 | 口播仍讲剧情句,画面先行;随后全屏切产品段 | ## 反转幅度与接入丝滑度(第一质量轴) @@ -30,7 +31,7 @@ Brief 里写 `workflow: reversal-ad` 时使用。本文是**通用制作流程 实践中最常见的失败不是没有反转,而是**反转幅度不够大、或接入生硬**。两轴缺一不可: - **幅度大**:反转前后两个世界的距离拉到最远——题材跳(现实求助 / 悬念事件 ↔ 商业产品)、身份跳(普通人 ↔ 产品相关角色)、场景跳、情绪跳。铺垫段演得越认真、越沉浸,反转越强;铺垫段禁止自嘲、禁止对镜头挤眼、禁止流露任何「广告感」。 -- **丝滑接入**:接入点必须由剧情自身逻辑自然推出——自问自答、因果链、意象复用。观众事后的反应应是「原来如此,说得通」,而不是「怎么突然打广告」。接入句的因果必须一句话可复述。 +- **丝滑接入**:接入点必须由剧情自身逻辑自然推出——自问自答、因果链、意象复用。观众事后的反应应是「原来如此,说得通」,而不是「怎么突然打广告」。或者就利用画面转场,使用AIGC生成转场片段连接素材和产品段。 - **二次反转(可选,强烈推荐)**:片尾互动号召(转发 / 点赞 / 评论)不做广告口号,种进剧情动机里——观众完成互动时感觉自己在推进剧情,而不是在点广告。 ✅ 正例(真人口播爆款,借结构不照搬措辞):一个人拍视频「我丢了 2000 块钱,大家帮我找找,找到必有重谢」——一转:「我答谢你什么呢?答谢你带你去呼伦贝尔旅游」,身份揭晓:他其实是呼伦贝尔地接导游,宣传的是旅游线路——二转:「这 2000 块对我真的很重要,看到的哥哥姐姐帮我转发一下」,转发号召成了「找钱」剧情的自然延伸。三个可复制点:铺垫严肃可信(现实求助、零广告感)→ 落差巨大;「答谢你什么呢」自问自答桥接两个世界 → 接入丝滑;CTA 由剧情动机给出 → 转发不是打广告。 @@ -94,7 +95,7 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): | core_message | 本条必须传达的核心信息 | | product_points | 产品/服务事实、允许讲的能力、禁用承诺(只以 Brief 为准,不内置品牌事实) | | implant_cta | Brief「业务植入与 CTA」字段:植入位置与方式、内容与业务的衔接句要求、CTA 主目标与句式;未给时按本 workflow 默认(反转点 55%–76%、植入段单点深打、片尾一个主行动且优先二次反转剧情化承载),并在 GATE A 说明 | -| twist_variant | 任务指引式 / 双关置换式 / 身份彩蛋式 / 戏中戏式;未指定时由我据素材与故事选定,并在 GATE A 说明理由 | +| twist_variant | 任务指引式 / 双关置换式 / 身份彩蛋式 / 画面转场式 / 戏中戏式;未指定时由我据素材与故事选定,并在 GATE A 说明理由 | | story_source | 解说正文的故事来源(开源片名 / 用户素材 / AIGC 生成;开源片名优先 Blender 开源电影片库,见「素材 sourcing」) | | voiceover | 甲方交付口播终稿路径;未交付时写明由我起草 | | source_mode | open_license_footage / user_provided / aigc / mixed | @@ -104,19 +105,6 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): | gates | GATE A/B 批准人;甲方代理批准时写明批准范围 | | acceptance | 交付物、验收标准、遗留问题记录要求 | -## 阶段裁剪(对通用制作流程的细化) - -| 阶段 | 本 workflow 的做法 | -|------|--------------------| -| Stage 3 | `script/script.md` 按段标注(解说 / 反转 / 植入)+ 每段时长占比 + 反转点时间码;未交付口播时剧本须体现三段结构与反转手法(reversal-ad 本就是叙事型,无需再分类),甲方交付口播时落稿锁定;自评过「反转幅度与接入丝滑度」四问,GATE A 随脚本交答案 | -| Stage 4 | 镜头表每镜标所属段;反转点前后各一镜写明衔接意象(意象桥) | -| Stage 6 | 现成素材轨且无 AIGC 角色时跳过角色三视图 | -| Stage 7–8 | slot 按段规划,解说段 slot 数最多;植入段 slot 必须能承载 Brief 的价值点 | -| Stage 9b | delivery_promise 中记录反转点位置与植入段占比承诺 | -| Stage 11 | 场景 B(旁白一次性 TTS + 对齐)或 D(甲方录音);BGM 优先沿用素材原生配乐轨并 ducking | -| Stage 12 | 解说段与植入段允许色调对比(冷暗 → 明亮实景/界面),但必须用钩连句 + 意象桥缝合 | -| Stage 14a | 封面优先用素材高情绪帧直用;封面主文案来自 Brief,无标题平台用核心传达 | - ## 制作护栏 1. **规格一次锁定**:横竖屏、目标时长、分辨率与帧率由 Brief 决定;不为"看起来高级"擅自放大素材。需要统一规格时先确认素材源质量与环境约束,再写入 timeline 计划与决策日志。 diff --git a/crews/content-producer/skills/expert-video/workflows/story-develop.md b/crews/content-producer/skills/expert-video/workflows/story-develop.md index 983d0a8e..94eb1708 100644 --- a/crews/content-producer/skills/expert-video/workflows/story-develop.md +++ b/crews/content-producer/skills/expert-video/workflows/story-develop.md @@ -6,7 +6,7 @@ - **模式 B(直接对接用户)**:用户没给 Brief,或只给了模糊想法("做个短片""帮我策划一下")。 - **模式 A(Subagent 承制)**:main 的 Brief 缺关键字段(创意 / 核心传达不清、规格缺失),需向 Brief owner 澄清。 -- 触发判据:**Brief 的"创意"不足以直接写 `script.md`**。够用就不触发,直接进 Stage 3 `script-write`。 +- 触发判据:**Brief 的"创意"不足以直接写 `script.md`**。够用就不触发`。 > ❗ 本 workflow 是**澄清与收敛**,不是替甲方创作。选题方向、品牌事实、卖点承诺、CTA 口径归甲方;我只把甲方脑子里的创意问清楚、整理成 Brief,不自行脑补,也不反过来指挥甲方。 @@ -24,17 +24,16 @@ 6. **业务植入与 CTA**:植入什么、CTA 主目标与句式(口径归甲方)。 7. **素材 / 参考 / 约束**:有无现成素材(**绝对路径**)、参考片、deadline、必含内容。 -调性 / 风格倾向(如庄重 / 诙谐 / 紧迫)影响我的制作手法选择,问清后记 `script/decisions.json`(备选 + 置信度 + 理由)。**不再做 narrative / motion / montage 三档分类**——那是已退役的 `intent-router` 的产物,对下游制作无实际作用(它当年只填一个故事模板页眉,没有任何脚本消费它)。 +调性 / 风格倾向(如庄重 / 诙谐 / 紧迫)影响我的制作手法选择,问清后记 `script/decisions.json`(备选 + 置信度 + 理由)。 ## 产出 - **模式 B**:代用户整理 `brief.md`(创意 + 规格 + 口播文案如有),**发用户确认定稿后才开工**。 - **模式 A**:把澄清结果补进 Brief(或向 Brief owner 确认缺口已补齐),**不重开需求讨论**。 -- 收敛完成的标志:**`brief.md` 的创意足以直接写 `script.md`**。然后回通用制作流程 **Stage 3(`script-write`)**。 +- 收敛完成的标志:**`brief.md` 的创意足以直接写 `script.md`**。然后回通用制作流程**。 ## 边界 -- **不产 `intent.json`、不产 `story.md`**——这两个中间产物已废除。创意直接落 `brief.md`;故事 / 剧本的可拍化在 Stage 3 `script-write` 由我据 Brief 创意做,不再有一个独立的"写故事"阶段。 - 甲方已交付口播文案 / 录音时,叙事以口播稿为准,本 workflow 只需确认口播稿到位,不另起创意。 - 不做视频下载 / 转写 / 抽帧(那是 main 的 `viral-chaser`);甲方给了参考拆解报告才用可选工具 `reference-concepts`。 - 不替甲方做需求决策;缺信息就问,不脑补品牌事实与授权。 diff --git a/crews/main/skills/expert-douyin/SKILL.md b/crews/main/skills/expert-douyin/SKILL.md index c806ef4f..d9679f8e 100644 --- a/crews/main/skills/expert-douyin/SKILL.md +++ b/crews/main/skills/expert-douyin/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 风格与 DNA diff --git a/crews/main/skills/expert-wx-channel/SKILL.md b/crews/main/skills/expert-wx-channel/SKILL.md index 0ddbcd67..d9d56bc8 100644 --- a/crews/main/skills/expert-wx-channel/SKILL.md +++ b/crews/main/skills/expert-wx-channel/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。Brief **不含 DNA 信息**、**不写实现路径**(参考脚本、引擎参数之类实现手段归 CP,规则详见 Content Production Workflow 的 Brief 硬性规则),main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 平台速查 diff --git a/crews/main/skills/expert-wx-channel/workflows/content-production.md b/crews/main/skills/expert-wx-channel/workflows/content-production.md index cfce83ec..910f1c56 100644 --- a/crews/main/skills/expert-wx-channel/workflows/content-production.md +++ b/crews/main/skills/expert-wx-channel/workflows/content-production.md @@ -248,7 +248,7 @@ main 负责监督并推动 CP 的进度,卡住时追问而不是替它做。 必须等待发布流程完整返回后再判定结果,禁止提前自行判断发布成功。用户要求人工检查后再发布时,按该工具「手动模式」执行。 -**作品链接不抓取、由用户后补**:管理后台分享面板复制链接的路线实践中不可靠(审核 / 转码中常不可用),发布后不去后台取链接。与公众号 `wx-mp-publisher` 同机制:发布后告知用户作品已提交,请其后续在手机端或后台「分享」中获取作品链接(`https://weixin.qq.com/sph/xxxx`)提供给我们,再按 Step 7 补录升级。 +**作品链接不抓取、由用户后补**:管理后台分享面板复制链接的路线实践中不可靠(审核 / 转码中常不可用),发布后不去后台取链接。发布后告知用户作品已提交,请其后续在手机端或后台「分享」中获取作品链接(`https://weixin.qq.com/sph/xxxx`)提供给我们,再按 Step 7 补录升级。 ## Step 7 - 记录 diff --git a/crews/main/skills/expert-xhs/SKILL.md b/crews/main/skills/expert-xhs/SKILL.md index 0ac87d2b..3756f039 100644 --- a/crews/main/skills/expert-xhs/SKILL.md +++ b/crews/main/skills/expert-xhs/SKILL.md @@ -50,7 +50,7 @@ DNA 存储目录是 `xhs/dna/`。未指定 DNA 时默认使用并更新 `dna-0` DNA 是**从一批作品样本提取并聚合出的内容生产规则集**:图文 10 维——选题与观看理由、标题与封面图组、内容创意、**匹配的用户问题**、正文表达与语气、图组视觉、**业务植入套路**、**互动引导与 CTA 套路**,加账号运营子模块(简介写法、内容形式比例、发布习惯);视频 11 维——前四项加**业务植入套路**、**互动引导与 CTA 套路**、视频内容形态与制作指向、制作规格与视听倾向、口播文案子模块与账号运营子模块。搜索维度是小红书必备:最大流量池来自搜索,关键词必须落到用户可能的提问原句。DNA 指导 main agent 写图文或出视频 Brief(+ 口播文案),不规定创作细节与成片制作。维度框架 v2 位于 `xhs-style-profiler` 的 `references/note-dna-framework.md` 与 `references/video-dna-framework.md`。 -**视频全案分工硬边界**:main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营,也直接做图文内容与已有素材轻加工;视频全案的成片制作委托 `content-producer`。口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 数据与记录 From 4fce7ff056707f9141f90514296ccba355d903cb Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 12:33:36 +0800 Subject: [PATCH 06/25] =?UTF-8?q?fix(expert-video):=20=E5=85=A8=E4=BB=93?= =?UTF-8?q?=E5=AF=B9=E9=BD=90=20Stage=200=E2=86=9215=20=E6=96=B0=E7=BC=96?= =?UTF-8?q?=E5=8F=B7=EF=BC=8C=E6=B8=85=E9=99=A4=E6=97=A7=E9=98=B6=E6=AE=B5?= =?UTF-8?q?=E7=BC=96=E5=8F=B7=E4=B8=8E=20intent-router=20=E7=95=99?= =?UTF-8?q?=E7=97=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AGENTS.md / SKILL.md: Stage 0→14→0→15;script-write 由 Stage 3 改 Stage 1; 工作区目录树内联编号(3/3b/4/5/6/7/8/9a/9b/14b)全部按新编号重排; 跨领域技能引用 aigc-video-gen 8/10→7/10、siliconflow-img-gen 6/10/14a→5/10/14a - video-producer.sh: help 全表重编号;GATE A 由 Stage 6 后改 Stage 5 后; motion-audit/make-cover 补 13b/14a 子标 - 12 个子脚本: docstring/argparse/报错/[next] 提示/JSON stub stage 字段全部对齐新编号; 9a/9b/3b/11a/11b 旧子字母清除(11B/11C 保留为 mix-audio 场景字母) - mix-audio.py: 三场景→四场景(正文补齐场景 D 甲方口播录音路径),与 SKILL.md 对齐 - reversal-ad.md: Stage 3 不重写策略文案 → Stage 1 不重写 voiceover.md 口播终稿 - main 三平台 video-dna-framework.md: 删「intent-router 档位分类已退役」历史注记 - character-register.py: 顺手修 docstring 错字(拱分→拆分) --- crews/content-producer/AGENTS.md | 4 +-- .../skills/expert-video/SKILL.md | 16 ++++++------ .../video-producer/scripts/asset-resolve.py | 10 ++++---- .../scripts/character-register.py | 8 +++--- .../scripts/delivery-promise-lock.py | 6 ++--- .../tools/video-producer/scripts/mix-audio.py | 25 +++++++++++-------- .../video-producer/scripts/narration-align.py | 6 ++--- .../scripts/script-self-eval.py | 12 ++++----- .../video-producer/scripts/script-write.py | 8 +++--- .../video-producer/scripts/shot-decompose.py | 12 ++++----- .../video-producer/scripts/slideshow-risk.py | 10 ++++---- .../tools/video-producer/scripts/slot-plan.py | 10 ++++---- .../scripts/storyboard-build.py | 10 ++++---- .../tools/video-producer/video-producer.sh | 24 +++++++++--------- .../expert-video/workflows/reversal-ad.md | 2 +- .../references/video-dna-framework.md | 2 +- .../references/video-dna-framework.md | 2 +- .../references/video-dna-framework.md | 2 +- 18 files changed, 87 insertions(+), 82 deletions(-) diff --git a/crews/content-producer/AGENTS.md b/crews/content-producer/AGENTS.md index becd3a66..09670071 100644 --- a/crews/content-producer/AGENTS.md +++ b/crews/content-producer/AGENTS.md @@ -11,7 +11,7 @@ ## 能力方向路由 -**视频类铁律**:只要接的是视频制作活儿,`expert-video` 的**通用制作流程**(Stage 0→14 阶段链 + GATE A/B 两闸门 + 护栏 + 工作区与交付约定)**一律适用**——它是基准准则,不是"没匹配到类型时的备选",也不与类型 workflow 并列。下表匹配到的类型 workflow 只是叠加在基准上的进一步细化。 +**视频类铁律**:只要接的是视频制作活儿,`expert-video` 的**通用制作流程**(Stage 0→15 阶段链 + GATE A/B 两闸门 + 护栏 + 工作区与交付约定)**一律适用**——它是基准准则,不是"没匹配到类型时的备选",也不与类型 workflow 并列。下表匹配到的类型 workflow 只是叠加在基准上的进一步细化。 | 入口信号 | 专家包 | 怎么做 | |---------|--------|--------| @@ -19,7 +19,7 @@ | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | `expert-video` | 通用制作流程 + Reversal Ad 细化 | | 甲方交付口播文案或真人口播录音,要合成声画 | `expert-video` | 通用制作流程 + Narration Video 细化 | | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | `expert-video` | 通用制作流程 + Collage B-roll 细化 | -| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走;创意不清时先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 3 `script-write`(叙事 / 动效 / 蒙太奇手法由我据创意自定,不再做三档分类) | +| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走;创意不清时先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write`(叙事 / 动效 / 蒙太奇手法由我据创意自定) | | 已有素材要剪辑、修整、拼接、配音、烧字幕 | `expert-video` | 通用制作流程的 Stage 12 工具箱(只做几何级修整) | | "做网页/落地页/APP 界面/品牌视觉体系" | `expert-design` | Web Page / App UI / Brand Visual | diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index 6ad7ef03..da586e57 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -55,7 +55,7 @@ metadata: | 层 | 是什么 | 怎么用 | |----|--------|--------| -| **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→14 阶段链、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | +| **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→15 阶段链、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | | **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:创意不足以直接写剧本时触发 | | **工具说明**(`tools/<工具>/SKILL.md`) | 每个子命令的入参、产物路径、退出码与旁路条件 | 调用前查;本文不重复参数细节 | @@ -72,7 +72,7 @@ metadata: | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 一句文稿 → 一个视觉隐喻 → 静帧 → i2v,三道闸门与 Gate 3 批量调度 | - Brief 指定了 `workflow`:**先读对应文档并直接采用**,不得替换成自创流程。 -- Brief 未指定 `workflow`:仍走通用制作流程;创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 3 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`。 +- Brief 未指定 `workflow`:仍走通用制作流程;创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`。 - 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。 > `story-develop` 是 **intake 类 workflow**(Stage 0 创意澄清,**不是 `Brief.workflow` 取值**),与上表 type 类 workflow 正交:任何类型的视频,创意不清都先走它收敛 Brief,再按通用制作流程 + 对应 type workflow 执行。详见 `workflows/story-develop.md`。 @@ -93,19 +93,19 @@ output_videos// # ├── brief.md # 甲方交付(拷贝入档)或 Stage 0 与用户定稿 ├── voiceover.md # 甲方交付的口播文案(如有) ├── reference/ # 可选:甲方给的参考拆解报告与差异化概念 -├── script/ # script.md(3) / self-eval.json(3b) / decisions.json(审计链) -├── storyboard/ # storyboard.json(4) / shot_decompose.json(5) -├── characters/ # registry.json(6) + /{front,side,back}.png +├── script/ # script.md(1) / self-eval.json(2) / decisions.json(审计链) +├── storyboard/ # storyboard.json(3) / shot_decompose.json(4) +├── characters/ # registry.json(5) + /{front,side,back}.png ├── gates/ # gate-a.md / gate-b.md(含批准人与批准范围) ├── raw_materials/ # 甲方素材入库副本 + 授权记录 -├── slots/ # slot-plan.json(7) / asset-resolve.json(8) / slideshow-risk.json(9a) / delivery-promise.json(9b) +├── slots/ # slot-plan.json(6) / asset-resolve.json(7) / slideshow-risk.json(8) / delivery-promise.json(9) ├── render/shot-NN/ # (10) first-frame.png / last-frame.png / shot.mp4 ├── audio/ # narration.mp3 / narration-segments.json / bgm.mp3 / subtitles.srt ├── artifacts/ # (12) 按镜顺序的最终段 01_*.mp4 … NN_*.mp4 ├── video.mp4 # (12) 成片 ├── review/ # verdict.json(13a) / frames/ / motion-audit.json(13b) ├── cover.jpg # (14a) -└── final-deliver.md # (14b) +└── final-deliver.md # (15) ``` workflow 文档在技能包内,不是项目目录内容;项目目录只放 Brief、素材、脚本、渲染与交付产物。 @@ -198,7 +198,7 @@ Stage 15 交付 回报成片 + 封面 + final-deliver.md 的绝对 | `video-producer` | 阶段链全部原子能力(剧本 / 分镜、素材 slot 与解析、渲染、混音对齐、拼接合成、动效审计、封面)+ 后期处理(`normalize` **必跑**、`burn-srt` / `duck` / `denoise` / `interp` 可选,全部干湿分离不覆盖输入) | `video-producer <子命令>`;`video-producer help` 列全量 | | `collage-broll` | 纸拼贴 B-roll 的环境自检与 Gate 3 批量 i2v 调度(0 全通 / 1 参数错 / 2 部分失败,只重跑失败条目) | `collage-broll check-setup` / `collage-broll gate3 --batch [--dry-run]` | -跨领域公共技能:`aigc-video-gen`(视频片段生成 / i2v 首尾帧插值,Stage 8/10;输出路径须落在 `output_videos/` 下,调用时 workdir 是 Content Producer workspace 根)、`siliconflow-img-gen`(静帧、角色三视图、封面,Stage 6/10/14a)、`awk-tts`(旁白 TTS,带字级时间戳,Stage 11B;`--enable-subtitle` 让火山流式 HTTP 原生返回时间戳)、`bgm-library`(ccMixter 免版税 + 自动 TASL 署名,商用安全,Stage 11C 优先)、`pexels-footage` / `pixabay-footage`(免版税素材与 BGM 搜索)、`video-review`(成片技术自检闸门,Stage 13a)、`video-edit subtitles`(main crew 暴露的烧字幕原子;不可用时向 Brief owner 报工具缺口,不手写 ffmpeg)。 +跨领域公共技能:`aigc-video-gen`(视频片段生成 / i2v 首尾帧插值,Stage 7/10;输出路径须落在 `output_videos/` 下,调用时 workdir 是 Content Producer workspace 根)、`siliconflow-img-gen`(静帧、角色三视图、封面,Stage 5/10/14a)、`awk-tts`(旁白 TTS,带字级时间戳,Stage 11B;`--enable-subtitle` 让火山流式 HTTP 原生返回时间戳)、`bgm-library`(ccMixter 免版税 + 自动 TASL 署名,商用安全,Stage 11C 优先)、`pexels-footage` / `pixabay-footage`(免版税素材与 BGM 搜索)、`video-review`(成片技术自检闸门,Stage 13a)、`video-edit subtitles`(main crew 暴露的烧字幕原子;不可用时向 Brief owner 报工具缺口,不手写 ffmpeg)。 env 依赖:`AWK_API_KEY`(静帧 / 视频生成)、`VOLC_ASR_*`(`narration-align` 回退路径与甲方口播录音转写;旧控制台双头 `VOLC_ASR_APP_ID` + `VOLC_ASR_ACCESS_KEY`,或新控制台单头 `VOLC_ASR_APP_KEY`)。缺 env 时子命令 exit 2,补齐属 IT engineer 职责,不要静默降级。Python 依赖 `requests`、`Pillow`(`motion-graphics` 逐帧绘制)在仓根 `requirements.txt`。系统依赖:`motion-graphics` 需要 Noto Sans SC/CJK 字体(探测 `/usr/share/fonts/opentype/noto-sc` 等候选目录,缺失 exit 2;可用 spec `font_dir` 或 env `MG_FONT_DIR` 覆盖)。机器资源约束(线程数、分辨率上限、低载编码)读本 workspace `MEMORY.md` 或 Brief 的环境约束,不写死在技能包里(`motion-graphics` 默认即低载:nice19/veryfast/crf18/threads2)。 diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py index b5882bba..605af479 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 8 — asset-resolve:按 slot 拉素材(Fast path)。 +"""Stage 7 — asset-resolve:按 slot 拉素材(Fast path)。 Usage: python3 scripts/asset-resolve.py [--source pexels|pixabay|both] [--no-confirm] -入:project_dir/slots/slot-plan.json(Stage 7) +入:project_dir/slots/slot-plan.json(Stage 6) 出:project_dir/slots/asset-resolve.json(每 slot 选定素材 + rejected_picks 落盘) + 素材落 project_dir/raw_materials/ @@ -36,7 +36,7 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 8 asset-resolve") + parser = argparse.ArgumentParser(description="Stage 7 asset-resolve") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") parser.add_argument("--source", default="both", choices=["pexels", "pixabay", "both"]) parser.add_argument("--no-confirm", action="store_true", help="agent 已人核完毕,不再呈交") @@ -59,7 +59,7 @@ def main() -> None: return stub = { - "stage": 8, + "stage": 7, "source": args.source, "raw_materials_dir": str(raw_dir), "instruction": ( @@ -83,7 +83,7 @@ def main() -> None: } resolve_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[done] asset-resolve.json 模板已落:{resolve_path}") - print(f"[next] agent 跑 Fast path 填 picks → 跑 slideshow-risk(Stage 9a)") + print(f"[next] agent 跑 Fast path 填 picks → 跑 slideshow-risk(Stage 8)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py index 59841771..eacb6e5e 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 6 — character-register:角色三视图 + static/dynamic features 拱分。 +"""Stage 5 — character-register:角色三视图 + static/dynamic features 拆分。 Usage: python3 scripts/character-register.py -入:project_dir/storyboard/shot_decompose.json(Stage 5)+ script.md(Stage 3 出场人物) +入:project_dir/storyboard/shot_decompose.json(Stage 4)+ script.md(Stage 1 出场人物) 出:project_dir/characters/registry.json(每个角色 static/dynamic features) + project_dir/characters//front.png + side.png + back.png(调 siliconflow-img-gen) @@ -28,7 +28,7 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 6 character-register") + parser = argparse.ArgumentParser(description="Stage 5 character-register") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() @@ -49,7 +49,7 @@ def main() -> None: return stub = { - "stage": 6, + "stage": 5, "characters": [], "instruction": ( "agent 据 script.md 出场人物 + shot_decompose.json 列出所有出场角色,每个角色填 schema 并调 " diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py index c9e3b049..b92f1803 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Stage 9b — delivery-promise-lock:交付承诺八类锁定 + motion_ratio 预估。 +"""Stage 9 — delivery-promise-lock:交付承诺八类锁定 + motion_ratio 预估。 Usage: python3 scripts/delivery-promise-lock.py @@ -32,7 +32,7 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 9b delivery-promise-lock") + parser = argparse.ArgumentParser(description="Stage 9 delivery-promise-lock") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() @@ -52,7 +52,7 @@ def main() -> None: return stub = { - "stage": "9b", + "stage": "9", "promises": { "has_dialogue": None, "has_narration": None, diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py index df734fb7..d7958b7f 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -"""Stage 11 — mix-audio:配音配乐三场景分流。 +"""Stage 11 — mix-audio:配音配乐四场景分流。 Usage: python3 scripts/mix-audio.py 入:project_dir/script/script.md(含 delivery_cues / 对白 / 旁白标记) 出:project_dir/audio/ 目录 + subtitles.srt 模板 - (实际音频产物由 agent 按下方三场景路径生成) + (实际音频产物由 agent 按下方四场景路径生成) -三场景分流(agent 按 script.md 实际内容判断走哪条): +四场景分流(agent 按 script.md 实际内容判断走哪条): A. 人物对话 → 声画同出 aigc-video-gen i2v 渲染时人物对白自带语音,不单独做 TTS。 @@ -17,7 +17,7 @@ B. 旁白 → 一次性 TTS + ASR 对齐 1. agent 把整片旁白词写好,一次性 TTS 生成 audio/narration.mp3 (保证语音一致性,不要分段生成) - 2. 跑 narration-align(Stage 11b): + 2. 跑 narration-align(Stage 11): python3 scripts/narration-align.py → 调火山 ASR 极速版对 narration.mp3 转写,输出 audio/narration-segments.json(utterance 级真实时间戳,秒) @@ -30,7 +30,12 @@ - pexels-footage / pixabay-footage 搜 background music - 静音占位(无 BGM 需求时) -混合场景:A+B+C、B+C、A+C 均可能,agent 按 script.md 判断。 + D. 甲方口播录音 → ASR 时间戳 → 按时间戳补素材 + 1. 甲方口播录音落 audio/voiceover.(Brief 给绝对路径;文案落稿锁定,不重写) + 2. 跑 narration-align 拿 utterance 级真实时间戳 + 3. agent 拿时间戳按句排素材,assemble 时混入;不重配旁白 + +混合场景:A/B/C/D 任意组合均可能,agent 按 script.md 与 Brief 判断。 """ import argparse @@ -61,7 +66,7 @@ def main() -> None: print(f"[done] audio/ 目录已建 + subtitles.srt 模板已落") print() - print("=== 配音配乐三场景分流(agent 按 script.md 判断走哪条)===") + print("=== 配音配乐四场景分流(agent 按 script.md 判断走哪条)===") print() print("A. 人物对话 → 声画同出") print(" aigc-video-gen i2v 渲染时人物对白自带语音,不单独做 TTS。") @@ -73,7 +78,7 @@ def main() -> None: print(" 调 awk-tts 时加 --enable-subtitle,火山单向流式 HTTP 原生返回") print(" 字级时间戳(sentence.words 带 startTime/endTime,秒),") print(" awk-tts 自动落盘 audio/narration.subtitle.json") - print(" 2. 跑 narration-align(Stage 11b):") + print(" 2. 跑 narration-align(Stage 11):") print(" python3 scripts/narration-align.py ") print(" → 优先复用 narration.subtitle.json(TTS 原生字级时间戳,零额外调用)") print(" → 缺失时回退火山 ASR 极速版转写 narration.mp3") @@ -87,10 +92,10 @@ def main() -> None: print(" - pexels-footage / pixabay-footage 搜 background music") print(" - 静音占位(无 BGM 需求时)") print() - print("混合场景:A+B+C、B+C、A+C 均可能,agent 按 script.md 判断。") + print("混合场景:A/B/C/D 任意组合均可能,agent 按 script.md 与 Brief 判断。") print() - print("D. 用户口播录音 → ASR 时间戳 → 按时间戳补素材") - print(" 1. agent 把用户给的口播录音落 audio/voiceover.") + print("D. 甲方口播录音 → ASR 时间戳 → 按时间戳补素材") + print(" 1. agent 把甲方给的口播录音落 audio/voiceover.(Brief 给绝对路径;文案落稿锁定,不重写)") print(" 2. 调火山 ASR 极速版转写口播录音,拿 utterance 级真实时间戳") print(" (凭据复用 viral-chaser 同池 VOLC_ASR_*,与 narration-align 回退路径同一接口)") print(" 3. agent 按时间戳把口播内容切成段,每段对应一个 shot 时长区间") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py index bbaa6437..115c2fb5 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 11b — narration-align:旁白时间戳对齐。 +"""Stage 11 — narration-align:旁白时间戳对齐。 Usage: python3 scripts/narration-align.py -入:project_dir/audio/narration.mp3(Stage 11a 一次性 TTS 生成的整段旁白) +入:project_dir/audio/narration.mp3(Stage 11 场景 B 一次性 TTS 生成的整段旁白) + project_dir/audio/narration.subtitle.json(awk-tts --enable-subtitle 落盘的 TTS 原生字级时间戳,优先复用) 出:project_dir/audio/narration-segments.json { @@ -153,7 +153,7 @@ def fallback_asr(narration: Path, out_path: Path) -> None: def main() -> None: load_env_file() - parser = argparse.ArgumentParser(description="Stage 11b narration-align") + parser = argparse.ArgumentParser(description="Stage 11 narration-align") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") parser.add_argument( "--audio", diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py index 8a958910..e108932e 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 3b — script-self-eval:剧本自评 N 维打分,任一维 <3 必返工。 +"""Stage 2 — script-self-eval:剧本自评 N 维打分,任一维 <3 必返工。 Usage: python3 scripts/script-self-eval.py -入:project_dir/script/script.md(Stage 3) +入:project_dir/script/script.md(Stage 1) 出:project_dir/script/self-eval.json(N 维分 1–5 + 总评 + 是否必返工) N 维(硬约束五条): @@ -38,14 +38,14 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 3b script-self-eval") + parser = argparse.ArgumentParser(description="Stage 2 script-self-eval") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() project = Path(args.project_dir).resolve() script_path = project / "script" / "script.md" if not script_path.is_file(): - die(f"前置缺失: script.md 不存在,先跑 script-write(Stage 3)") + die(f"前置缺失: script.md 不存在,先跑 script-write(Stage 1)") eval_path = project / "script" / "self-eval.json" eval_path.parent.mkdir(parents=True, exist_ok=True) @@ -58,7 +58,7 @@ def main() -> None: return stub = { - "stage": "3b", + "stage": "2", "dims": [ {"key": k, "name": n, "criteria": c, "score": None, "note": ""} for k, n, c in EVAL_DIMS @@ -69,7 +69,7 @@ def main() -> None: } eval_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[done] self-eval.json 模板已落:{eval_path}") - print(f"[next] agent 逐维打分 → 任一维 <3 必返工 → 全维 ≥3 跑 storyboard-build(Stage 4)") + print(f"[next] agent 逐维打分 → 任一维 <3 必返工 → 全维 ≥3 跑 storyboard-build(Stage 3)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py index d4752b93..7c0fa3a9 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Stage 3 — script-write:Brief 创意 → 分场剧本。 +"""Stage 1 — script-write:Brief 创意 → 分场剧本。 Usage: python3 scripts/script-write.py @@ -27,7 +27,7 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 3 script-write") + parser = argparse.ArgumentParser(description="Stage 1 script-write") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() @@ -44,7 +44,7 @@ def main() -> None: print(f"[checkpoint] script.md 已存在,沿用:{script_path}") return - stub = f"""# 分场剧本(Stage 3) + stub = f"""# 分场剧本(Stage 1) > 据 Brief 创意({brief_path.name})拆成可拍化分场剧本。每场含:场景描述、出场人物、对白、动作、enhancement_cues、delivery_cues。 @@ -81,7 +81,7 @@ def main() -> None: """ script_path.write_text(stub, encoding="utf-8") print(f"[done] script.md 模板已落:{script_path}") - print(f"[next] agent 填剧本 → 跑 script-self-eval(Stage 3b)") + print(f"[next] agent 填剧本 → 跑 script-self-eval(Stage 2)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py index c39631eb..5aea173e 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 5 — shot-decompose:每镜拆首帧静照 / 尾帧静照 / 运动描述。 +"""Stage 4 — shot-decompose:每镜拆首帧静照 / 尾帧静照 / 运动描述。 Usage: python3 scripts/shot-decompose.py -入:project_dir/storyboard/storyboard.json(Stage 4) +入:project_dir/storyboard/storyboard.json(Stage 3) 出:project_dir/storyboard/shot_decompose.json(每镜 first_frame/last_frame 文字描述 + motion + variation_type) variation_type 三档(定传给 aigc-video-gen 的参考图数): @@ -30,14 +30,14 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 5 shot-decompose") + parser = argparse.ArgumentParser(description="Stage 4 shot-decompose") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() project = Path(args.project_dir).resolve() board_path = project / "storyboard" / "storyboard.json" if not board_path.is_file(): - die(f"前置缺失: storyboard.json 不存在,先跑 storyboard-build(Stage 4)") + die(f"前置缺失: storyboard.json 不存在,先跑 storyboard-build(Stage 3)") decompose_path = project / "storyboard" / "shot_decompose.json" decompose_path.parent.mkdir(parents=True, exist_ok=True) @@ -53,7 +53,7 @@ def main() -> None: shots = board.get("shots", []) stub = { - "stage": 5, + "stage": 4, "decompose": [], "instruction": "agent 为 storyboard.json 每镜拆首尾帧文字描述 + 运动 + variation_type。运动描述禁角色名用外观特征。", "decompose_schema": { @@ -68,7 +68,7 @@ def main() -> None: } decompose_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[done] shot_decompose.json 模板已落:{decompose_path}") - print(f"[next] agent 填每镜首尾帧+运动+variation_type → 跑 character-register(Stage 6)") + print(f"[next] agent 填每镜首尾帧+运动+variation_type → 跑 character-register(Stage 5)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py index 2a8ba61b..61780ba1 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 9a — slideshow-risk:六维幻灯风险打分(pre-compose 闸门)。 +"""Stage 8 — slideshow-risk:六维幻灯风险打分(pre-compose 闸门)。 Usage: python3 scripts/slideshow-risk.py -入:project_dir/slots/asset-resolve.json(Stage 8,素材齐) +入:project_dir/slots/asset-resolve.json(Stage 7,素材齐) 出:project_dir/slots/slideshow-risk.json(六维分 + verdict) 六维(每维 0–10,加权总分 ≥4.0 才许进 compose,<4.0 fail 必换素材): @@ -41,7 +41,7 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 9a slideshow-risk") + parser = argparse.ArgumentParser(description="Stage 8 slideshow-risk") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() @@ -61,7 +61,7 @@ def main() -> None: return stub = { - "stage": "9a", + "stage": "8", "dims": [ {"key": k, "name": n, "criteria": c, "weight": w, "score": None, "note": ""} for k, n, c, w in RISK_DIMS @@ -77,7 +77,7 @@ def main() -> None: } risk_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[done] slideshow-risk.json 模板已落:{risk_path}") - print(f"[next] agent 填六维分 → fail 必返工 → pass 跑 delivery-promise-lock(Stage 9b)") + print(f"[next] agent 填六维分 → fail 必返工 → pass 跑 delivery-promise-lock(Stage 9)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py index 8462106a..a7832205 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 7 — slot-plan:素材 slot 规划。 +"""Stage 6 — slot-plan:素材 slot 规划。 Usage: python3 scripts/slot-plan.py -入:project_dir/storyboard/shot_decompose.json(Stage 5) +入:project_dir/storyboard/shot_decompose.json(Stage 4) 出:project_dir/slots/slot-plan.json(每镜对应 slot:template + hero slot + tone→slot 数) template:slot 模板(如"主角家中—晨光—白T青年") @@ -40,7 +40,7 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 7 slot-plan") + parser = argparse.ArgumentParser(description="Stage 6 slot-plan") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") parser.add_argument("--tone", default=None, choices=sorted(TONE_SLOT_TABLE), help="调性,不传走 narrative 默认") args = parser.parse_args() @@ -64,7 +64,7 @@ def main() -> None: tone_cfg = TONE_SLOT_TABLE[tone] stub = { - "stage": 7, + "stage": 6, "tone": tone, "tone_config": tone_cfg, "slots": [], @@ -88,7 +88,7 @@ def main() -> None: } plan_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[done] slot-plan.json 模板已落:{plan_path}") - print(f"[next] agent 填 slot schema → 跑 asset-resolve(Stage 8)") + print(f"[next] agent 填 slot schema → 跑 asset-resolve(Stage 7)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py index 78907c35..66fa854e 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""Stage 4 — storyboard-build:剧本 → 镜头表。 +"""Stage 3 — storyboard-build:剧本 → 镜头表。 Usage: python3 scripts/storyboard-build.py -入:project_dir/script/script.md(Stage 3,self-eval 全维 ≥3) +入:project_dir/script/script.md(Stage 1,self-eval 全维 ≥3) 出:project_dir/storyboard/storyboard.json(镜头表,每镜叙事目的/机位复用/位置朝向/不写不可见) 硬规则六条: @@ -28,7 +28,7 @@ def die(msg: str) -> None: def main() -> None: - parser = argparse.ArgumentParser(description="Stage 4 storyboard-build") + parser = argparse.ArgumentParser(description="Stage 3 storyboard-build") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() @@ -48,7 +48,7 @@ def main() -> None: return stub = { - "stage": 4, + "stage": 3, "shots": [], "instruction": "agent 据剧本拆镜,每镜按下 schema 填。", "shot_schema": { @@ -67,7 +67,7 @@ def main() -> None: } board_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[done] storyboard.json 模板已落:{board_path}") - print(f"[next] agent 填镜头表 → 跑 shot-decompose(Stage 5)") + print(f"[next] agent 填镜头表 → 跑 shot-decompose(Stage 4)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh index 5ceb940f..eaca2558 100755 --- a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh +++ b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh @@ -22,21 +22,21 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 video-producer help 列可用子命令 流程: - 通用制作流程(expert-video SKILL.md 的 Stage 0→14 + 两闸门)是做**任何**视频都要遵循的基准, + 通用制作流程(expert-video SKILL.md 的 Stage 0→15 + 两闸门)是做**任何**视频都要遵循的基准, 不是"没指定类型时的备选"。Brief 指定 workflow 时,先读包内 workflows/.md, 按其阶段裁剪调用下列子命令;未指定时只按通用制作流程走。创意不清时先走 story-develop intake workflow(workflows/story-develop.md)与甲方收敛 Brief,再进 script-write。 子命令(按阶段序): reference-concepts 可选 吃甲方给的参考拆解报告出 2–3 差异化概念 - script-write Stage 3 Brief 创意 → 分场剧本(含 enhancement_cues + delivery_cues) - script-self-eval Stage 3 脚本自评 N 维打分 - storyboard-build Stage 4 剧本 → 镜头表 - shot-decompose Stage 5 每镜拆首尾帧 + 运动描述 + variation_type - character-register Stage 6 角色三视图 + static/dynamic features 拆分 - slot-plan Stage 7 素材 slot 规划 - asset-resolve Stage 8 按 slot 拉素材(Fast path 人核缩略图) - slideshow-risk Stage 9 六维幻灯风险打分(pre-compose 闸门) + script-write Stage 1 Brief 创意 → 分场剧本(含 enhancement_cues + delivery_cues) + script-self-eval Stage 2 脚本自评 N 维打分 + storyboard-build Stage 3 剧本 → 镜头表 + shot-decompose Stage 4 每镜拆首尾帧 + 运动描述 + variation_type + character-register Stage 5 角色三视图 + static/dynamic features 拆分 + slot-plan Stage 6 素材 slot 规划 + asset-resolve Stage 7 按 slot 拉素材(Fast path 人核缩略图) + slideshow-risk Stage 8 六维幻灯风险打分(pre-compose 闸门) delivery-promise-lock Stage 9 交付承诺八类锁定 render-shot Stage 10 按 slot 渲染(AIGC i2v / 静图) motion-graphics Stage 10 程序化逐帧动态图形(声明式 spec;产品段动效/标题动画,与 render-shot 并列的第二条渲染路径) @@ -50,9 +50,9 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 assemble Stage 12 按镜顺序拼接成片 + 转场 + 规格归一化 + 守卫断言(--manifest/--verify-fps/--expect-durations) add-silent-audio Stage 12 给无音频的视频片段补静音音轨(concat 前置) make-outro Stage 12 片尾制作(形象图+黑边+烧字幕+静音轨 → 标准比例片尾) - motion-audit Stage 13 motion_led 抽查(补公共 video-review) + motion-audit Stage 13b motion_led 抽查(补公共 video-review) normalize Stage 13c 响度归一化到 -14 LUFS(**必跑**) - make-cover Stage 14 封面(siliconflow-img-gen,必含封面主文案) + make-cover Stage 14a 封面(siliconflow-img-gen,必含封面主文案) 后期处理(可选,全部干湿分离:输出落 _<处理名>.mp4,不覆盖输入): burn-srt libass 把 SRT 硬烧进画面(甲方要字幕时) @@ -60,7 +60,7 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 denoise afftdn / arnndn 去环境噪声(仅甲方素材音质差时) interp minterpolate 补帧到 30/60fps(仅低 fps 源材) -闸门不是子命令——GATE A(Stage 6 后文本闸门)与 GATE B(Stage 9 后素材闸门)由 agent +闸门不是子命令——GATE A(Stage 5 后文本闸门)与 GATE B(Stage 9 后素材闸门)由 agent 按 expert-video SKILL.md 执行:呈交摘要 → 结束本轮回复 → 等甲方(main agent 或用户)逐闸门批准。 产物文件存在性即 checkpoint:每个子命令先查产物文件是否存在,存在则 load 不重生成。 diff --git a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md index 537b9a53..2d2463ce 100644 --- a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md +++ b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md @@ -65,7 +65,7 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): ## 口播(旁白) -- 甲方交付 `voiceover.md` 时**原样落稿锁定**(Stage 3 不重写策略文案,仍跑 `script-self-eval` 做检查)。 +- 甲方交付 `voiceover.md` 时**原样落稿锁定**(Stage 1 `script-write` 不重写口播终稿,仍跑 `script-self-eval` 做检查)。 - 未交付时由我写,GATE A 交审。默认规范:第三人称解说体;避免问句、感叹号、第二人称(你/您/家人们)与促销信号词(「接下来介绍」「这是我们的」);句长 10–15 字,语速 7–8 字/秒。 - 真人口播录音(甲方提供)走 Stage 11 场景 D:ASR 拿时间戳后按时间戳排素材,不重配旁白。 diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md index 1a94854b..ad6f332e 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md @@ -108,7 +108,7 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定(intent-router 档位分类已退役)。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 ## Focus ID 表 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md index 14530cde..d28e9b04 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md @@ -108,7 +108,7 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定(intent-router 档位分类已退役)。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 ## Focus ID 表 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md index ab1635da..b4f85eb9 100644 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md @@ -111,7 +111,7 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定(intent-router 档位分类已退役)。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 ## Focus ID 表 From 287c8384ae39fa9609fd26ae42b2665b4e38354e Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 13:00:22 +0800 Subject: [PATCH 07/25] =?UTF-8?q?refactor(expert-video):=20collage-broll?= =?UTF-8?q?=20=E6=8C=89=E6=96=B0=E6=A1=86=E6=9E=B6=E4=BB=8E=E5=8E=9F?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E9=87=8D=E7=A7=BB=E6=A4=8D=20+=20story-devel?= =?UTF-8?q?op=20=E5=8D=87=E6=A0=BC=E4=B8=BA=E6=9C=AA=E6=8C=87=E5=AE=9A=20w?= =?UTF-8?q?orkflow=20=E7=9A=84=E9=BB=98=E8=AE=A4=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit collage-broll 重移植(蓝本:gbro-collage-broll 原版 SKILL.md,方法论忠实照搬): - 三道闸门收敛为 GATE A / GATE B 两道:GATE A 检隐喻清单(script/script.md, 即本类型的"分场剧本"),GATE B 检静帧 contact sheet(素材闸门) - 新增阶段裁剪表:Stage 3-5 裁剪、6-9 由静帧生成替代、10 重定义为 i2v 组装、 11/12 裁剪(默认无声逐条独立成片)、13a/b 照走、13c 无声片豁免 - 输入契约对齐 Brief:voiceover.md 落稿锁定不重写,不混入"brief.md 含隐喻清单"旧写法 - 工作区对齐标准树:script/(隐喻+visual-spec+prompts)、gates/、render//、 review/(QA 结论与总览图),废弃平铺的 gate2-qa/gate3-qa/gen-jobs 旧结构 - 工具层:gate3 子命令改名 render(run_gate3.py→run_render.py),check_setup 与 wrapper help 同步新口径;退出码语义不变 story-develop 默认入口化(对齐理念:未指定 workflow 走 story-develop): - SKILL.md / AGENTS.md / video-producer.sh / story-develop.md 统一口径: Brief 未指定 workflow 且无类型信号 → 默认从 story-develop 进入, 创意不足先收敛、够用则快速通过直进 Stage 1 --- crews/content-producer/AGENTS.md | 2 +- .../skills/expert-video/SKILL.md | 13 +- .../expert-video/tools/collage-broll/SKILL.md | 14 +- .../tools/collage-broll/collage-broll.sh | 24 +- .../collage-broll/scripts/check_setup.sh | 16 +- .../scripts/{run_gate3.py => run_render.py} | 8 +- .../tools/video-producer/video-producer.sh | 4 +- .../expert-video/workflows/collage-broll.md | 206 ++++++++++-------- .../expert-video/workflows/story-develop.md | 5 +- 9 files changed, 165 insertions(+), 127 deletions(-) rename crews/content-producer/skills/expert-video/tools/collage-broll/scripts/{run_gate3.py => run_render.py} (89%) diff --git a/crews/content-producer/AGENTS.md b/crews/content-producer/AGENTS.md index 09670071..6d3386d0 100644 --- a/crews/content-producer/AGENTS.md +++ b/crews/content-producer/AGENTS.md @@ -19,7 +19,7 @@ | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | `expert-video` | 通用制作流程 + Reversal Ad 细化 | | 甲方交付口播文案或真人口播录音,要合成声画 | `expert-video` | 通用制作流程 + Narration Video 细化 | | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | `expert-video` | 通用制作流程 + Collage B-roll 细化 | -| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走;创意不清时先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write`(叙事 / 动效 / 蒙太奇手法由我据创意自定) | +| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 先走 `story-develop` intake workflow(创意不清时与甲方收敛 Brief,够用则快速通过),再进 Stage 1 `script-write`,按通用制作流程出片(叙事 / 动效 / 蒙太奇手法由我据创意自定) | | 已有素材要剪辑、修整、拼接、配音、烧字幕 | `expert-video` | 通用制作流程的 Stage 12 工具箱(只做几何级修整) | | "做网页/落地页/APP 界面/品牌视觉体系" | `expert-design` | Web Page / App UI / Brand Visual | diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index da586e57..807354b2 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -56,7 +56,7 @@ metadata: | 层 | 是什么 | 怎么用 | |----|--------|--------| | **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→15 阶段链、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | -| **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:创意不足以直接写剧本时触发 | +| **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:Brief 未指定 workflow 时进入(创意不足先收敛,够用快速通过) | | **工具说明**(`tools/<工具>/SKILL.md`) | 每个子命令的入参、产物路径、退出码与旁路条件 | 调用前查;本文不重复参数细节 | > 通用制作流程**不是**与类型 workflow 并列的第四条路,也**不是**"Brief 没指定类型时的 fallback"。它是底座;类型 workflow 只在底座上细化,产出特定类型的视频。 @@ -69,13 +69,13 @@ metadata: |--------------------|----------|---------------------|--------------| | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | Reversal Ad | `reversal-ad` | 三段结构占比、反转点落在 55%–76%、四种反转手法、反转幅度与接入丝滑度两轴(含二次反转 CTA)、素材三模式 sourcing(Blender 片库 / 用户直供三查 / AIGC)、意象桥与钩连句、植入段约束 | | 口播类(甲方交付口播文案或真人录音,要合成声画) | Narration Video | `narration-video` | 口播稿落稿锁定不重写、按字级时间戳配画面、声音规范与验收清单 | -| "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 一句文稿 → 一个视觉隐喻 → 静帧 → i2v,三道闸门与 Gate 3 批量调度 | +| "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 隐喻清单即 script(GATE A 检)→ 静帧即素材(GATE B 检 contact sheet)→ Stage 10 `collage-broll render` 批量 i2v 组装;阶段裁剪表见 workflow | - Brief 指定了 `workflow`:**先读对应文档并直接采用**,不得替换成自创流程。 -- Brief 未指定 `workflow`:仍走通用制作流程;创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`。 +- Brief 未指定 `workflow` 且无明确类型信号:走 `story-develop` intake workflow——创意不足以直接写剧本时与甲方收敛,够用则快速通过——再进 Stage 1 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`。 - 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。 -> `story-develop` 是 **intake 类 workflow**(Stage 0 创意澄清,**不是 `Brief.workflow` 取值**),与上表 type 类 workflow 正交:任何类型的视频,创意不清都先走它收敛 Brief,再按通用制作流程 + 对应 type workflow 执行。详见 `workflows/story-develop.md`。 +> `story-develop` 是 **intake 类 workflow**(Stage 0 创意澄清,**不是 `Brief.workflow` 取值**),与上表 type 类 workflow 正交:Brief 未指定 workflow 时默认从它进入——创意不足以直接写剧本先收敛,够用则快速通过——再按通用制作流程(+ 信号识别到的 type workflow)执行。详见 `workflows/story-develop.md`。 不属于我的活(交回甲方或转其他专家包): @@ -116,7 +116,8 @@ workflow 文档在技能包内,不是项目目录内容;项目目录只放 B ``` Stage 0 Brief intake 读甲方 Brief,核对字段,缺口向 Brief owner 澄清; - 甲方未给 Brief 或 Brief 不足以直接写剧本时 → 走 story-develop intake workflow(workflows/story-develop.md) + 甲方未给 Brief,或 Brief 未指定 workflow → 走 story-develop intake workflow + (workflows/story-develop.md;创意不足先收敛,够用快速通过) Stage 1 script-write Brief 创意 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) 基线生产从此开始。 Stage 2 script-self-eval 脚本自评 N 维打分,任一维 <3 必返工(落稿锁定时只检查不改写) @@ -196,7 +197,7 @@ Stage 15 交付 回报成片 + 封面 + final-deliver.md 的绝对 | 工具 | 用途 | 命令 | |------|------|------| | `video-producer` | 阶段链全部原子能力(剧本 / 分镜、素材 slot 与解析、渲染、混音对齐、拼接合成、动效审计、封面)+ 后期处理(`normalize` **必跑**、`burn-srt` / `duck` / `denoise` / `interp` 可选,全部干湿分离不覆盖输入) | `video-producer <子命令>`;`video-producer help` 列全量 | -| `collage-broll` | 纸拼贴 B-roll 的环境自检与 Gate 3 批量 i2v 调度(0 全通 / 1 参数错 / 2 部分失败,只重跑失败条目) | `collage-broll check-setup` / `collage-broll gate3 --batch [--dry-run]` | +| `collage-broll` | 纸拼贴 B-roll 的环境自检与 Stage 10 批量 i2v 调度(0 全通 / 1 参数错 / 2 部分失败,只重跑失败条目) | `collage-broll check-setup` / `collage-broll render --batch [--dry-run]` | 跨领域公共技能:`aigc-video-gen`(视频片段生成 / i2v 首尾帧插值,Stage 7/10;输出路径须落在 `output_videos/` 下,调用时 workdir 是 Content Producer workspace 根)、`siliconflow-img-gen`(静帧、角色三视图、封面,Stage 5/10/14a)、`awk-tts`(旁白 TTS,带字级时间戳,Stage 11B;`--enable-subtitle` 让火山流式 HTTP 原生返回时间戳)、`bgm-library`(ccMixter 免版税 + 自动 TASL 署名,商用安全,Stage 11C 优先)、`pexels-footage` / `pixabay-footage`(免版税素材与 BGM 搜索)、`video-review`(成片技术自检闸门,Stage 13a)、`video-edit subtitles`(main crew 暴露的烧字幕原子;不可用时向 Brief owner 报工具缺口,不手写 ffmpeg)。 diff --git a/crews/content-producer/skills/expert-video/tools/collage-broll/SKILL.md b/crews/content-producer/skills/expert-video/tools/collage-broll/SKILL.md index a4383138..34e48b49 100644 --- a/crews/content-producer/skills/expert-video/tools/collage-broll/SKILL.md +++ b/crews/content-producer/skills/expert-video/tools/collage-broll/SKILL.md @@ -1,11 +1,11 @@ --- name: collage-broll -description: 纸拼贴 B-roll 的原子工具——环境自检与 Gate 3 批量 i2v 视频生成调度。 +description: 纸拼贴 B-roll 的原子工具——环境自检与 Stage 10 批量 i2v 视频生成调度。 --- # collage-broll — 工具说明 -> 本文是 `expert-video` 专家包内的工具说明书,不独立出现在技能列表中。制作流程(视觉隐喻 → 静帧 → 视频三道闸门)由包内 Collage B-roll Workflow 编排。 +> 本文是 `expert-video` 专家包内的工具说明书,不独立出现在技能列表中。制作流程(GATE A 隐喻清单 → GATE B 静帧 → Stage 10 i2v 组装)由包内 Collage B-roll Workflow 编排。 **用途**:代理纸拼贴 B-roll 的两个脚本,让 agent 走 PATH 调用、零路径拼接。 @@ -14,19 +14,19 @@ description: 纸拼贴 B-roll 的原子工具——环境自检与 Gate 3 批量 | 子命令 | 入 | 出 | 退出码 | |--------|----|----|--------| | `collage-broll check-setup` | 无(读环境变量与 PATH) | stdout 逐项 PASS/FAIL | 0 全通 / 1 有缺项 | -| `collage-broll gate3` | `--batch /gen-jobs.json`(每条含 prompt / first_frame / last_frame / output / ratio / resolution / duration),可选 `--dry-run` | 逐条调公共 `aigc-video-gen` i2v 落 MP4 + decisions.log | 0 全通 / 1 参数错、jobs 文件不存在或格式错、`aigc-video-gen` 不在 PATH / 2 部分 job 失败(stderr 报失败清单,已跑通的保留) | +| `collage-broll render` | `--batch /render/gen-jobs.json`(每条含 prompt / first_frame / last_frame / output / ratio / resolution / duration),可选 `--dry-run` | 逐条调公共 `aigc-video-gen` i2v 落 MP4 + decisions.log | 0 全通 / 1 参数错、jobs 文件不存在或格式错、`aigc-video-gen` 不在 PATH / 2 部分 job 失败(stderr 报失败清单,已跑通的保留) | **调用方式**: ```bash collage-broll check-setup -collage-broll gate3 --batch output_videos//gen-jobs.json --dry-run -collage-broll gate3 --batch output_videos//gen-jobs.json +collage-broll render --batch output_videos//render/gen-jobs.json --dry-run +collage-broll render --batch output_videos//render/gen-jobs.json ``` **注意事项**: -- `gate3` 串行调度:视频生成是异步轮询任务,并行会撞平台并发限。 +- `render` 串行调度:视频生成是异步轮询任务,并行会撞平台并发限。 - `gen-jobs.json` 的 `output` 必须是相对 workspace 根、且落在 `output_videos/` 下的路径(`aigc-video-gen` 的 ensure_safe_output 要求);调用时 workdir 必须是 Content Producer workspace 根。 -- 依赖:`ffmpeg` / `ffprobe` / Python ≥ 3.10;`AWK_API_KEY`(Gate 2 静帧);`MODELSTUDIO_API_KEY` 或 `DASHSCOPE_API_KEY`(百炼)或 `AWK_GEN_KEY`(火山)。缺项由 `check-setup` 报出,补齐属 IT engineer 职责。 +- 依赖:`ffmpeg` / `ffprobe` / Python ≥ 3.10;`AWK_API_KEY`(Phase 2 静帧);`MODELSTUDIO_API_KEY` 或 `DASHSCOPE_API_KEY`(百炼)或 `AWK_GEN_KEY`(火山)。缺项由 `check-setup` 报出,补齐属 IT engineer 职责。 - 模型候选链 fallback 与 decisions.log 由 `aigc-video-gen` 自带,本工具不重复实现。 diff --git a/crews/content-producer/skills/expert-video/tools/collage-broll/collage-broll.sh b/crews/content-producer/skills/expert-video/tools/collage-broll/collage-broll.sh index 60d36095..5df04f9b 100755 --- a/crews/content-producer/skills/expert-video/tools/collage-broll/collage-broll.sh +++ b/crews/content-producer/skills/expert-video/tools/collage-broll/collage-broll.sh @@ -2,8 +2,8 @@ # collage-broll.sh — collage-broll 工具 wrapper(薄转发,子命令范式) # 让 agent 用 `collage-broll <子命令> [参数...]` 走 PATH,零路径拼接。 # 子命令: -# check-setup 环境自检(ffmpeg/ffprobe/AWK_API_KEY/视频平台 key/python 版本) -# gate3 --batch [--dry-run] Gate 3 批量调度 i2v 生成 +# check-setup 环境自检(ffmpeg/ffprobe/AWK_API_KEY/视频平台 key/python 版本) +# render --batch [--dry-run] Stage 10 批量调度 i2v 生成 # 纸拼贴 B-roll 的完整制作流程见 expert-video 包内 Collage B-roll Workflow。 set -euo pipefail SELF="${BASH_SOURCE[0]}" @@ -11,31 +11,31 @@ SELF="${BASH_SOURCE[0]}" while [ -L "$SELF" ]; do SELF="$(readlink -f "$SELF")"; done SCRIPT_DIR="$(cd "$(dirname "$SELF")" && pwd)" -SUBCMD="${1:?用法: collage-broll [参数...]}" +SUBCMD="${1:?用法: collage-broll [参数...]}" shift case "$SUBCMD" in check-setup) exec bash "$SCRIPT_DIR/scripts/check_setup.sh" "$@" ;; - gate3) - exec python3 "$SCRIPT_DIR/scripts/run_gate3.py" "$@" + render) + exec python3 "$SCRIPT_DIR/scripts/run_render.py" "$@" ;; -h|--help|help) cat <<'HELP' collage-broll — 纸拼贴 B-roll 原子工具(wrapper) 用法: - collage-broll check-setup 环境自检(依赖与 key) - collage-broll gate3 --batch Gate 3 批量 i2v 生成(串行调 aigc-video-gen) - collage-broll gate3 --batch <...> --dry-run 只打印调度计划不真调 - collage-broll help 本帮助 + collage-broll check-setup 环境自检(依赖与 key) + collage-broll render --batch Stage 10 批量 i2v 生成(串行调 aigc-video-gen) + collage-broll render --batch <...> --dry-run 只打印调度计划不真调 + collage-broll help 本帮助 退出码: check-setup 0 全通 / 1 有缺项 - gate3 0 全部 job 跑通 / 1 参数错或 wrapper 缺失 / 2 部分 job 失败(已跑通的保留) + render 0 全部 job 跑通 / 1 参数错或 wrapper 缺失 / 2 部分 job 失败(已跑通的保留) -制作流程(隐喻 → 静帧 → 视频三道闸门)见 expert-video 包内 Collage B-roll Workflow; -本 wrapper 只代理脚本,不含流程语义。 +制作流程(GATE A 隐喻清单 → GATE B 静帧 → Stage 10 i2v 组装)见 expert-video 包内 +Collage B-roll Workflow;本 wrapper 只代理脚本,不含流程语义。 HELP ;; *) diff --git a/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh b/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh index aed734ee..db08aa1f 100644 --- a/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh +++ b/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh @@ -2,7 +2,7 @@ # collage-broll environment self-check. # Exit 0 = all good; exit 1 = at least one item missing (details on stdout). # -# 探依赖:ffmpeg / ffprobe / AWK_API_KEY(Gate 2 静帧)/ 视频平台 key(Gate 3 视频) +# 探依赖:ffmpeg / ffprobe / AWK_API_KEY(Phase 2 静帧)/ 视频平台 key(Stage 10 i2v 视频) # 不探 venv——仓根 requirements.txt 统一装,不留独立 venv(xiaobei 语境) set -u @@ -12,20 +12,20 @@ FAIL=0 ok() { printf 'PASS %s\n' "$1"; } bad() { printf 'FAIL %s\n' "$1"; FAIL=1; } -# 1. AWK_API_KEY(Gate 2 静帧生成要——siliconflow-img-gen / Seedream) +# 1. AWK_API_KEY(Phase 2 静帧生成要——siliconflow-img-gen / Seedream) if [ -n "${AWK_API_KEY:-}" ]; then - ok "AWK_API_KEY 已设置(Gate 2 静帧可用)" + ok "AWK_API_KEY 已设置(Phase 2 静帧可用)" else - bad "AWK_API_KEY 未设置(Gate 2 静帧生成要——到 https://console.volcengine.com/ark 创建后 export 到 shell 配置)" + bad "AWK_API_KEY 未设置(Phase 2 静帧生成要——到 https://console.volcengine.com/ark 创建后 export 到 shell 配置)" fi -# 2. 视频平台 key(Gate 3 视频生成要——aigc-video-gen / 百炼或火山) +# 2. 视频平台 key(Stage 10 i2v 视频生成要——aigc-video-gen / 百炼或火山) if [ -n "${MODELSTUDIO_API_KEY:-}" ] || [ -n "${DASHSCOPE_API_KEY:-}" ]; then - ok "MODELSTUDIO_API_KEY / DASHSCOPE_API_KEY 已设置(Gate 3 走百炼 happyhorse-1.1-i2v)" + ok "MODELSTUDIO_API_KEY / DASHSCOPE_API_KEY 已设置(Stage 10 i2v 走百炼 happyhorse-1.1-i2v)" elif [ -n "${AWK_GEN_KEY:-}" ]; then - ok "AWK_GEN_KEY 已设置(Gate 3 走火山 Seedance,百炼未配)" + ok "AWK_GEN_KEY 已设置(Stage 10 i2v 走火山 Seedance,百炼未配)" else - bad "视频平台 key 都未设置(Gate 3 要 MODELSTUDIO_API_KEY 百炼 或 AWK_GEN_KEY 火山)" + bad "视频平台 key 都未设置(Stage 10 i2v 要 MODELSTUDIO_API_KEY 百炼 或 AWK_GEN_KEY 火山)" fi # 3. ffmpeg / ffprobe diff --git a/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/run_gate3.py b/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/run_render.py similarity index 89% rename from crews/content-producer/skills/expert-video/tools/collage-broll/scripts/run_gate3.py rename to crews/content-producer/skills/expert-video/tools/collage-broll/scripts/run_render.py index 746529e6..a8fd2115 100644 --- a/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/run_gate3.py +++ b/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/run_render.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Gate 3 批量调度——读 gen-jobs.json 逐条调公共 aigc-video-gen wrapper 走 i2v 模式(首尾帧插值)。 +"""Stage 10 render 批量调度——读 gen-jobs.json 逐条调公共 aigc-video-gen wrapper 走 i2v 模式(首尾帧插值)。 每个 job 字段: prompt aigc-video-gen --prompt(中文声画同出描述) @@ -14,8 +14,8 @@ 串行调(视频生成是异步轮询任务,并行调会撞平台并发限)。 Usage: - python3 /scripts/run_gate3.py --batch /gen-jobs.json - python3 /scripts/run_gate3.py --batch /gen-jobs.json --dry-run + python3 /scripts/run_render.py --batch /render/gen-jobs.json + python3 /scripts/run_render.py --batch /render/gen-jobs.json --dry-run Exit codes: 0 全部 job 跑通 @@ -74,7 +74,7 @@ def run_one(job: dict, job_id: int, dry_run: bool) -> tuple[bool, str]: def main() -> None: parser = argparse.ArgumentParser( - description="Gate 3 批量调度——读 gen-jobs.json 逐条调公共 aigc-video-gen wrapper 走 i2v(首尾帧插值)." + description="Stage 10 render 批量调度——读 gen-jobs.json 逐条调公共 aigc-video-gen wrapper 走 i2v(首尾帧插值)." ) parser.add_argument("--batch", required=True, help="gen-jobs.json 路径") parser.add_argument("--dry-run", action="store_true", help="只打印不真调") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh index eaca2558..277a66ee 100755 --- a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh +++ b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh @@ -24,8 +24,8 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 流程: 通用制作流程(expert-video SKILL.md 的 Stage 0→15 + 两闸门)是做**任何**视频都要遵循的基准, 不是"没指定类型时的备选"。Brief 指定 workflow 时,先读包内 workflows/.md, - 按其阶段裁剪调用下列子命令;未指定时只按通用制作流程走。创意不清时先走 story-develop - intake workflow(workflows/story-develop.md)与甲方收敛 Brief,再进 script-write。 + 按其阶段裁剪调用下列子命令;未指定时先走 story-develop intake workflow + (workflows/story-develop.md:创意不清先收敛,够用快速通过),再进 script-write。 子命令(按阶段序): reference-concepts 可选 吃甲方给的参考拆解报告出 2–3 差异化概念 diff --git a/crews/content-producer/skills/expert-video/workflows/collage-broll.md b/crews/content-producer/skills/expert-video/workflows/collage-broll.md index ca65c90d..fec81cb8 100644 --- a/crews/content-producer/skills/expert-video/workflows/collage-broll.md +++ b/crews/content-producer/skills/expert-video/workflows/collage-broll.md @@ -1,83 +1,58 @@ # Workflow:Collage B-roll(纸拼贴组装动画) -Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴"时使用。把一句约 5 秒的口播压成一个 sharp visual idea,再做成高级编辑风纸拼贴组装动画。 +Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴"时使用。本文是**通用制作流程在纸拼贴 B-roll 上的细化**:方法论移植自 gbro-collage-broll(半调纸拼贴 + assemble-from-empty),阶段链按下表裁剪,闸门收敛为 GATE A / GATE B 两道——**GATE A 检隐喻清单(即本类型的 script),GATE B 检静帧 contact sheet(即素材)**。不替代通用流程;原子能力、护栏、工作区与交付约定照 `expert-video` 的 SKILL.md 执行;本文与通用流程冲突处以本文为准,但闸门与护栏不让步。 -本文是**通用制作流程在纸拼贴 B-roll 上的细化**(三道闸门、隐喻与静帧规范、Gate 3 批量调度),不替代通用流程;原子能力、护栏、工作区与交付约定照 `expert-video` 的 SKILL.md 执行。 +## 类型定义 -## 三道闸门 +把一句约 5 秒的口播文稿压成一个 sharp visual idea,做成高级编辑风**半调纸拼贴(halftone paper-collage)组装动画** B-roll: -| 闸门 | 停在哪 | 交付给甲方看什么 | -|------|--------|-----------------| -| Gate 1 隐喻确认 | 只设计视觉隐喻,**不生成图片、不生成视频、不调任何视频模型** | 每条的核心意思、情绪、一句话视觉命题、3–6 个关键物件、建议底色与点色、预期组装顺序 | -| Gate 2 静帧确认 | 隐喻确认后才写 visual spec 与 imagegen prompt,用 `siliconflow-img-gen` 生成静帧 | 带编号的静帧 contact sheet + `gate2-qa.md` | -| Gate 3 视频生成 | 静帧确认后不再问用哪个模型,直接调 `collage-broll gate3` 批量跑 i2v | 逐条 contact sheet + 末帧对照 + `gate3-qa.md` + `video-review` 结论 | +- 强烈、平坦、均匀的纯色纸面色场 + 黑白 halftone 照片剪贴为骨架 + 彩色卡纸点缀服务信息层级 +- 动作是 assemble-from-empty:元素从空场逐件滑入、卡位、组装(stop-motion 质感),不是轻微漂移、晃动或慢 zoom +- 默认交付 9:16、5 秒、720×1280、无声 MP4,可直接垫在口播下面 -- 每道闸门都要**结束本轮回复**等甲方批;甲方只确认部分编号时,只让通过的条目进下一道。 -- Gate 1 / Gate 2 分别对应通用制作流程的 GATE A(文本)/ GATE B(素材)语义:付费生成前必停。 -- 甲方已在 Brief 中代理批准某道闸门时,把批准范围记进对应 QA 文件后继续。 +逐条文稿 → 逐条隐喻 → 逐条独立成片。批量隐喻优先形成前后叙事(例如先表现手工消耗与经验流失,再表现规范沉淀与人机分工)。 -## 成功标准 +## 输入契约(Brief 侧) -- 一句话只表达一个清晰隐喻;不要把文稿逐字放进画面。 -- 一条文稿控制在 3–6 个关键物件;元素过多语意变弱,i2v 组装也不稳定。 -- 同一批画面有统一设计语言,但**不强制全部蓝底**。 -- 背景是强烈、平坦、均匀的色场,可按语意变化。 -- 主体以黑白 halftone photographic cut-outs 为骨架;关键卡片、按钮、胶片、规则册等允许红、黄、青、橙、紫、奶油白等彩色纸张。 -- 所有纸片有清晰裁切边、奶油白 keyline、低透明度柔和阴影和纸张颗粒。 -- 动作是 assemble-from-empty,不是轻微漂移、晃动或慢 zoom。 -- 无字幕、无口播全文、无 logo、无水印、无 UI。 -- 默认交付 9:16、5 秒、720×1280、无声 MP4。 +| Brief 字段 | 要求 | +|-----------|------| +| 文稿 | `voiceover.md`(绝对路径)或 Brief 内文稿字段,逐条可独立成句。**落稿锁定不重写**——隐喻是文稿的视觉转译,不是改写;发现文稿无法转译(一句话塞多个隐喻、超时长带)报 Brief owner | +| form | 画幅 / 时长 / 分辨率 / 声音;未指定按默认(9:16、5s、720P、无声) | +| gates | GATE A / GATE B 批准人;代理批准时写明批准范围 | +| acceptance | 验收标准(未指定按「交付」节默认) | -批量隐喻优先形成前后叙事(例如先表现手工消耗与经验流失,再表现规范沉淀与人机分工)。 +## 阶段裁剪表(相对通用制作流程) -## 不适用 - -- 需要精确控制图层、遮挡、镜头穿越或可编辑时间线 → 改用分层动画方案,并向甲方说明本 workflow 做不到。 -- 只要视频提示词、不要成片 → 直接写 prompt 交付,不走本流程。 -- 需要真实人物产品广告或口播演员 → 走 Narration Video,或只按通用制作流程做(不套类型 workflow)。 -- 甲方明确要可逐层修改的透明素材 → 本 workflow 默认不拆透明图层。 - -## 项目目录 - -自建工作区 `output_videos//`(甲方不指定、不代建): +| Stage | 处置 | 本文对应 | +|-------|------|---------| +| 0 Brief intake | 照走 | 核对文稿逐条可拆、规格齐;缺字段向 Brief owner 澄清 | +| 1 script-write | **重定义** | Phase 1 隐喻设计——产物落 `script/script.md`(隐喻清单),就是本类型的"分场剧本" | +| 2 script-self-eval | **重定义** | 隐喻自检(见 Phase 1),任一条不过必返工 | +| 3–5 storyboard / shot-decompose / character-register | **裁剪** | 单镜固定机位、无人物角色,不建 storyboard / shot_decompose / characters | +| GATE A | 照走 | 呈交隐喻清单 | +| 6–9 slot-plan → delivery-promise-lock | **由静帧生成替代** | Phase 2:visual-spec + Seedream 静帧 + 静帧 QA;AIGC 素材的来源记录 = prompt + 模型 + 生成时间 | +| GATE B | 照走 | 呈交静帧 contact sheet | +| 10 render-shot | **重定义** | Phase 3:i2v 首尾帧组装(`collage-broll render` 批量调度) | +| 11 mix-audio | **裁剪** | 默认无声交付;甲方要 aigc 原声时交付带声版 | +| 12 assemble | **裁剪** | 逐条独立成片,不拼接 | +| 13a video-review | 照走(强制) | 无声版 `audio_absent` warning 是预期,放行;带声版出 `audio_absent` 是 critical | +| 13b motion-audit | 照走 | 抽查组装过程逐件进入而非整体淡入 | +| 13c normalize | 无声片豁免 | 带声版必跑 | +| 14a make-cover | 按需 | B-roll 垫片通常无封面,Brief 要求才做 | +| 15 交付 | 照走 | 回报产物绝对路径 | -```text -/ -├── brief.md # 文稿 + Gate 1 隐喻清单 -├── visual-spec.json # Gate 2 视觉规格 -├── imagegen-prompts.md # Gate 2 Seedream prompt 留档 -├── gen-jobs.json # Gate 3 批量调用清单 -├── gate2-qa.md # 静帧 QA 结论 -├── gate3-qa.md # 视频 QA 结论 -├── still-contact-sheet.jpg # Gate 2 静帧总图 -├── video-contact-sheet-all.jpg # Gate 3 全部成片逐秒抽帧 -├── video-first-frame-all.jpg # 全部成片实际首帧(验证真的从空色场开始) -├── end-frame-comparison-all.jpg # 确认静帧 vs 视频末帧并排 -├── 01-/ -│ ├── gen-prompt.txt # aigc-video-gen --prompt 内容 -│ ├── frames/ -│ │ ├── still.png # Gate 2 确认的完成帧(原图) -│ │ ├── last-frame.png # 统一裁到 720x1280 的尾帧 -│ │ └── first-frame.png # 纯色空首帧(同底色 hex) -│ └── gen-runs/run-v01/ -│ ├── final-5s.mp4 # aigc-video-gen 产物(声画同出) -│ ├── final-5s-noaudio.mp4 # 默认交付版(无声) -│ ├── contact-sheet.jpg -│ ├── video-last-frame.jpg -│ └── end-frame-comparison.jpg -└── 02-/... -``` +## Phase 1 隐喻设计(Stage 1 重定义) -## Phase 1:设计视觉隐喻(Gate 1) - -先把文稿压成一个视觉命题,提取: +先把每条文稿压成一个视觉命题,提取: - **核心意思**:观众最终要看懂什么 - **情绪**:冷静、惊讶、紧迫、豁然开朗、荒诞、反讽 - **动作动词**:打开、连接、漏掉、装订、归档、点亮、压缩、分叉、组装 - **可视化隐喻**:机器、时钟、胶片、档案柜、控制台、规则册、漏斗、轨道、棋子 -输出格式: +**不要把文稿逐字放进画面。** 默认一条文稿只做一个隐喻,控制在 3–6 个关键物件;元素过多语意变弱,i2v 组装也不稳定。 + +`script/script.md` 每条格式: ```text 1. 核心意思:经验每次都在重复消耗 @@ -87,9 +62,25 @@ Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼 组装顺序:时钟 → 人物与剪刀 → 胶片 → 最终短输出 ``` -输出后停下等确认。 +### 隐喻自检(Stage 2 重定义,任一条不过必返工) + +1. 一句话只表达一个清晰隐喻? +2. 关键物件 3–6 个,不是满屏碎片? +3. 文稿没有逐字进画面(无字幕、无口播全文)? +4. 底色与点色按语义色场表选,有理由(见 Phase 2 色彩规则)? +5. 批量时前后叙事成立,或每条独立成立? + +## GATE A:呈交隐喻清单 + +文本闸门——**停,结束本轮回复**,发 Brief owner 审: + +- 呈交:条数、每条一句话视觉命题、色彩方案、组装顺序概览 +- 甲方只确认部分编号时,只让通过的条目进 Phase 2;未通过条目改隐喻重审 +- 甲方已在 Brief 代理批准时,把批准范围落 `gates/gate-a.md` 后继续 -## Phase 2:生成彩色拼贴静帧(Gate 2) +## Phase 2 生成静帧(GATE A 批准后) + +先写自包含的 `script/visual-spec.json`,再写 imagegen prompt。 ### visual-spec.json @@ -125,13 +116,13 @@ Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼 主体以黑白半调为主,局部彩色纸张必须服务信息层级,不为彩色而彩色。 -### imagegen prompt(siliconflow-img-gen / Seedream) +### imagegen prompt(Seedream / siliconflow-img-gen) ```bash -siliconflow-img-gen --prompt "<下面整段>" --image-size 1600x2848 --out-dir /01-/frames/ +siliconflow-img-gen --prompt "<下面整段>" --image-size 1600x2848 --out-dir /render//frames/ ``` -Prompt 用英文(Seedream 对英文响应更好): +Prompt 用英文(Seedream 对英文响应更好),整段落 `script/imagegen-prompts.md` 留档: ```text Use case: ads-marketing @@ -151,27 +142,36 @@ Seedream 不支持参考图锁风格,"同设计语言"靠同一批复用同一 检查:隐喻是否一眼看懂 / 主体是否集中 / 是否有假字、logo、水印、UI / 是否保留足够纯色场便于从空场组装 / 是否 3–6 个清晰大组而非满屏碎片 / 同批是否统一质感但有色彩变化。 -通过的原图复制到 `/frames/still.png`,拼 contact sheet 后交甲方确认,结论写 `gate2-qa.md`。要求重生部分静帧时,新版 contact sheet 递增命名(`still-contact-sheet-v2.jpg`),保留旧版便于对比。 +通过的原图复制到 `render//frames/still.png`,拼带编号的 contact sheet。要求重生部分静帧时,新版 contact sheet 递增命名(`still-contact-sheet-v2.jpg`),保留旧版便于对比。QA 结论写 `review/still-qa.md`。 ```bash -ffmpeg -y -pattern_type glob -i "/*/frames/still.png" \ - -vf "scale=270:480,tile=5x1" -frames:v 1 /still-contact-sheet.jpg +ffmpeg -y -pattern_type glob -i "/render/*/frames/still.png" \ + -vf "scale=270:480,tile=5x1" -frames:v 1 /review/still-contact-sheet.jpg ``` 段数 > 5 时分多行(`tile=5x2`、`5x3`…)。 -## Phase 3:i2v 生成视频(Gate 3) +## GATE B:呈交静帧 contact sheet + +素材闸门——**停,结束本轮回复**,发 Brief owner 看: + +- 呈交:静帧 contact sheet(带编号)、静帧 QA 结论、每条色彩与隐喻的对应关系 +- 部分通过:只让通过的条目进 Phase 3;要改的静帧重生并重新确认 +- 授权与来源记录一并呈交(AIGC 素材 = prompt + 模型 + 生成时间,留档见工作区) +- 甲方已在 Brief 代理批准时,把批准范围落 `gates/gate-b.md` 后继续 + +## Phase 3 i2v 组装视频(Stage 10 重定义) ### 1. 准备首尾帧 ```bash # 尾帧:确认静帧统一裁到 720x1280 -ffmpeg -y -i /frames/still.png \ +ffmpeg -y -i render//frames/still.png \ -vf "scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280" \ - /frames/last-frame.png + render//frames/last-frame.png # 首帧:与尾帧同底色的纯色空纸面(assemble-from-empty 的核心) -ffmpeg -y -f lavfi -i color=c=0x:s=720x1280 -frames:v 1 /frames/first-frame.png +ffmpeg -y -f lavfi -i color=c=0x:s=720x1280 -frames:v 1 render//frames/first-frame.png ``` 甲方明确要求不从完全空白开始时,首帧才保留一个基础物件。 @@ -188,11 +188,11 @@ ffmpeg -y -f lavfi -i color=c=0x:s=720x1280 -frames:v 1 /frames/first ### 3. 批量生成 -写 `gen-jobs.json`(每条含 `prompt` / `first_frame` / `last_frame` / `output` / `ratio` / `resolution` / `duration`),然后: +写 `render/gen-jobs.json`(每条含 `prompt` / `first_frame` / `last_frame` / `output` / `ratio` / `resolution` / `duration`),然后: ```bash -collage-broll gate3 --batch /gen-jobs.json -collage-broll gate3 --batch /gen-jobs.json --dry-run # 先看调度计划 +collage-broll render --batch /render/gen-jobs.json +collage-broll render --batch /render/gen-jobs.json --dry-run # 先看调度计划 ``` 内部串行逐条调公共 `aigc-video-gen` i2v(视频生成是异步轮询任务,并行会撞平台并发限),候选链 fallback 与 decisions.log 由 `aigc-video-gen` 自带。退出码 2 = 部分 job 失败:只重跑失败条目,已通过的不重跑。 @@ -204,17 +204,19 @@ collage-broll gate3 --batch /gen-jobs.json --dry-run # 先看调 ### 4. 强制无声交付 ```bash -ffmpeg -y -i /final-5s.mp4 -map 0:v:0 -c:v copy -an /final-5s-noaudio.mp4 +ffmpeg -y -i render//gen-runs/run-v01/final-5s.mp4 -map 0:v:0 -c:v copy -an \ + render//gen-runs/run-v01/final-5s-noaudio.mp4 ``` -默认交付 `final-5s-noaudio.mp4`,保留 `final-5s.mp4` 作中间产物。甲方明确要带声时直接交付 `final-5s.mp4`。 +默认交付 `final-5s-noaudio.mp4`,保留 `final-5s.mp4` 作中间产物(aigc 声画同出的原声版;甲方明确要带声时直接交付它)。 -## 视频 QA +## 视频 QA(Stage 13) 不要只看尾帧,必须检查组装过程与最终落位。 ```bash -ffmpeg -y -i /final-5s-noaudio.mp4 -vf "fps=1,scale=270:480,tile=5x1" -frames:v 1 /contact-sheet.jpg +ffmpeg -y -i render//gen-runs/run-v01/final-5s-noaudio.mp4 \ + -vf "fps=1,scale=270:480,tile=5x1" -frames:v 1 render//gen-runs/run-v01/contact-sheet.jpg ``` 通过标准: @@ -226,14 +228,14 @@ ffmpeg -y -i /final-5s-noaudio.mp4 -vf "fps=1,scale=270:480,tile=5x1" -fram - 最终帧与确认静帧一致;轻微姿态或细节漂移只要不影响隐喻语义即判通过,不为此重跑 - 成片为 720×1280、5 秒 -另抽视频末帧与确认静帧并排生成 `end-frame-comparison.jpg`;批量项目再合三张总览图(`video-contact-sheet-all.jpg` / `video-first-frame-all.jpg` / `end-frame-comparison-all.jpg`)。逐条 QA 结论(含带瑕疵通过的理由)写 `gate3-qa.md`。 +另抽视频末帧与确认静帧并排生成 `end-frame-comparison.jpg`;批量项目再合三张总览图落 `review/`(`video-contact-sheet-all.jpg` / `video-first-frame-all.jpg` / `end-frame-comparison-all.jpg`)。逐条 QA 结论(含带瑕疵通过的判定理由)写 `review/collage-video-qa.md`。 ### 技术自检(强制) 视觉 QA 后必须再跑公共 `video-review`,verdict=pass 才交付: ```bash -video-review /final-5s-noaudio.mp4 +video-review render//gen-runs/run-v01/final-5s-noaudio.mp4 ``` 视觉 QA 评美与语义,`video-review` 评技术合规(ffprobe 全字段 / 抽帧黑帧扫 / 音频电平 / 时长分辨率一致性),互补不重叠。fail 按 critical 项修或重生对应 job;warn 向甲方复述由其决定。 @@ -251,12 +253,46 @@ video-review /final-5s-noaudio.mp4 | 个别视频失败 | 只重跑对应 job | | i2v 报错 | 查首尾帧是否 720x1280、是否真存在、workdir 是否 workspace 根 | +## 工作区 + +自建工作区 `output_videos//`(甲方不指定、不代建),在通用流程标准树上裁剪: + +```text +/ +├── brief.md / voiceover.md # 甲方交付(Brief + 落稿锁定文稿) +├── script/ +│ ├── script.md # Phase 1 隐喻清单(本类型的"分场剧本") +│ ├── visual-spec.json # Phase 2 视觉规格 +│ ├── imagegen-prompts.md # Seedream prompt 留档 +│ └── decisions.json # 决策审计链 +├── gates/ +│ ├── gate-a.md / gate-b.md # 闸门批准记录(含批准人与批准范围) +├── render/ +│ ├── gen-jobs.json # Phase 3 批量 i2v 调用清单 +│ └── / +│ ├── frames/ # still.png(确认原图)/ last-frame.png / first-frame.png +│ └── gen-runs/run-v01/ # final-5s.mp4 / final-5s-noaudio.mp4 / contact-sheet.jpg / +│ # video-last-frame.jpg / end-frame-comparison.jpg +└── review/ + ├── still-qa.md # GATE B 静帧 QA 结论 + ├── still-contact-sheet.jpg # GATE B 呈交物(重生递增 v2/v3…) + ├── collage-video-qa.md # Phase 3 逐条视频 QA 结论 + └── video-contact-sheet-all.jpg / video-first-frame-all.jpg / end-frame-comparison-all.jpg +``` + ## 交付 -- 每条 `/gen-runs/run-v01/final-5s-noaudio.mp4`(甲方要带声则 `final-5s.mp4`) +- 每条 `render//gen-runs/run-v01/final-5s-noaudio.mp4`(甲方要带声则 `final-5s.mp4`) - 每条 contact sheet、批量总 contact sheet、末帧对照图 -- `gate2-qa.md` / `gate3-qa.md` / `video-review` 结论 +- `review/still-qa.md` + `review/collage-video-qa.md` + `video-review` 结论 - 一句说明每条文稿如何转成视觉隐喻 - 回报产物**绝对路径** 成片问题来自 i2v 生成限制(组装感弱 / 尾帧漂移)时直接说明;只有需要精确图层控制时才建议换方案,并向甲方报清代价。 + +## 不适用 + +- 需要精确控制图层、遮挡、镜头穿越或可编辑时间线 → 改用分层动画方案,并向甲方说明本 workflow 做不到 +- 只要视频提示词、不要成片 → 直接写 prompt 交付,不走本流程 +- 需要真实人物产品广告或口播演员 → 走 Narration Video,或按通用制作流程做 +- 甲方明确要可逐层修改的透明素材 → 本 workflow 默认不拆透明图层 diff --git a/crews/content-producer/skills/expert-video/workflows/story-develop.md b/crews/content-producer/skills/expert-video/workflows/story-develop.md index 94eb1708..253778cd 100644 --- a/crews/content-producer/skills/expert-video/workflows/story-develop.md +++ b/crews/content-producer/skills/expert-video/workflows/story-develop.md @@ -1,12 +1,13 @@ # Workflow:Story Develop(Brief intake · 创意澄清) -**这是 intake 类 workflow,不是 type 类 workflow。** type workflow(`narration-video` / `collage-broll` / `reversal-ad`)细化"某类视频怎么制作",是 `Brief.workflow` 的取值;本 workflow 解决"甲方还没把创意讲清楚时,怎么和他对话把 Brief 收敛出来",**不是 `Brief.workflow` 的取值**,也不与 type workflow 互斥。它在 **Stage 0(Brief intake)** 阶段触发,与任何 type workflow 正交可组合——收敛出 Brief 后,仍按通用制作流程 + 对应 type workflow 执行。 +**这是 intake 类 workflow,不是 type 类 workflow。** type workflow(`narration-video` / `collage-broll` / `reversal-ad`)细化"某类视频怎么制作",是 `Brief.workflow` 的取值;本 workflow 解决"甲方还没把创意讲清楚时,怎么和他对话把 Brief 收敛出来",**不是 `Brief.workflow` 的取值**,也不与 type workflow 互斥。**Brief 未指定 workflow 时,Stage 0(Brief intake)默认从这里进入**,与任何 type workflow 正交可组合——收敛出 Brief 后,仍按通用制作流程 + 对应 type workflow 执行。 ## 何时触发 +- **Brief 未指定 `workflow` 且无明确类型信号**("从零做视频""出一支完整视频"):默认入口。 - **模式 B(直接对接用户)**:用户没给 Brief,或只给了模糊想法("做个短片""帮我策划一下")。 - **模式 A(Subagent 承制)**:main 的 Brief 缺关键字段(创意 / 核心传达不清、规格缺失),需向 Brief owner 澄清。 -- 触发判据:**Brief 的"创意"不足以直接写 `script.md`**。够用就不触发`。 +- 创意足以直接写 `script.md` 时**快速通过**:确认 Brief 字段完整即直进 Stage 1,不硬走对话。 > ❗ 本 workflow 是**澄清与收敛**,不是替甲方创作。选题方向、品牌事实、卖点承诺、CTA 口径归甲方;我只把甲方脑子里的创意问清楚、整理成 Brief,不自行脑补,也不反过来指挥甲方。 From f01b84928d618590ec22d63f69b2b03047365710 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 13:06:27 +0800 Subject: [PATCH 08/25] =?UTF-8?q?fix(main,=E4=B8=89=E5=B9=B3=E5=8F=B0):=20?= =?UTF-8?q?=E6=8C=89=E8=A7=86=E9=A2=91=E4=B8=A4=E6=9D=A1=E7=90=86=E5=BF=B5?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1=E4=BF=AE=E6=AD=A3=E2=80=94=E2=80=94=E5=A0=B5?= =?UTF-8?q?=E5=8F=A3=E6=92=AD=E4=BB=A3=E5=86=99=E5=8F=A3=E5=AD=90=E3=80=81?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E8=B7=AF=E7=BA=BF=E5=BD=92=E4=BD=8D=20Brief?= =?UTF-8?q?=20=E5=A5=91=E7=BA=A6=E3=80=81story-develop=20=E5=8F=A3?= =?UTF-8?q?=E5=BE=84=E8=A1=A5=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 违背理念的实质修复: - 删「口播子模块未启用时只给要点、由 CP 组织旁白」代写通道(xhs:192 / wx:226)—— 口播终稿一律由 main 写(子模块启用按其结构写,未启用按用户要求与 Brief 核心传达写), 「不适用」仅限非口播类;三平台 SKILL.md 分工硬边界、style-dna 口播行、 content-production spawn 段同步去条件化 - 脚本制作路线(douyin:24 / wx:24):「根据 DNA 改脚本交 CP」改为用户脚本按素材处理 (绝对路径进 Brief 素材清单),main 只调策略层不动分镜,需动分镜即改走 Brief 委托由 CP 重出 - 校验清单(build_style_profile.py ×3):删「未启用时避免规定逐句口播」,改要求无论 子模块是否启用口播类必附 voiceover.md 绝对路径或录音路径 story-develop 口径补齐(对齐 CP 侧新默认入口): - 三平台 SKILL.md / content-production Brief 模板与 spawn 段 / video-dna-framework 路由表与尾段 / build_style_profile video-form scaffold:「未指定 workflow → CP 走 story-develop intake 收敛创意(够用快速通过)后按通用制作流程做」 一致性清理: - 「只交 Brief + 素材 + 口播」→「只交 Brief 一份(素材、口播均以绝对路径写在 Brief 内)」 - 「制作简报」统一为 Brief;Brief 模板 workflow 行「未指定」枚举值改「省略本字段」; douyin 采集表补 workflow 行;video-review 限定为 main 自做轻加工自检; wx editing 成片后流程措辞、style-dna 图文残句、account-setup 脚本简报→话术简报 --- crews/main/skills/expert-douyin/SKILL.md | 4 ++-- .../references/video-dna-framework.md | 4 ++-- .../scripts/build_style_profile.py | 4 ++-- .../skills/expert-douyin/workflows/account-setup.md | 2 +- .../expert-douyin/workflows/content-production.md | 13 +++++++------ .../skills/expert-douyin/workflows/style-dna.md | 2 +- crews/main/skills/expert-wx-channel/SKILL.md | 4 ++-- .../references/video-dna-framework.md | 4 ++-- .../scripts/build_style_profile.py | 4 ++-- .../workflows/content-production.md | 12 ++++++------ .../skills/expert-wx-channel/workflows/editing.md | 2 +- .../skills/expert-wx-channel/workflows/style-dna.md | 4 ++-- crews/main/skills/expert-xhs/SKILL.md | 2 +- .../references/video-dna-framework.md | 4 ++-- .../scripts/build_style_profile.py | 4 ++-- .../expert-xhs/workflows/content-production.md | 10 +++++----- crews/main/skills/expert-xhs/workflows/style-dna.md | 2 +- 17 files changed, 41 insertions(+), 40 deletions(-) diff --git a/crews/main/skills/expert-douyin/SKILL.md b/crews/main/skills/expert-douyin/SKILL.md index d9679f8e..edd66752 100644 --- a/crews/main/skills/expert-douyin/SKILL.md +++ b/crews/main/skills/expert-douyin/SKILL.md @@ -43,9 +43,9 @@ metadata: 跨领域通用技能:`viral-chaser`(抖音 / B站 / 小红书视频下载拆解,DNA 采样与仿写参考的取数主力)、`smart-search`(跨平台搜索,选题调研优先走社交平台,不用通用搜索引擎)、`content-calibrator`(DNA 表现评估)、`published-track`(发布记录与指标库)、`login-manager`(抖音登录态维护)。 -素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 +素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 风格与 DNA diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md index ad6f332e..d0f9daf5 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md @@ -103,12 +103,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 走其 **story-develop** intake workflow 收敛创意(够用快速通过)后按**通用制作流程**做——通用制作流程是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 ## Focus ID 表 diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py index 4a598110..9dfa1968 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py @@ -104,7 +104,7 @@ "topic-angle": "- 单篇观测:选题类型、选题入口(现象 / 问题 / 冲突 / 数据 / 热点 / 挑战 / 个人经历)、目标人群为什么要看完(理解 / 判断 / 行动 / 避坑 / 身份认同 / 情绪共鸣)。\n- 边界:只记本篇,不判断跨篇稳定性;不评价选题好坏。", "title-cover": "- 单篇观测:标题类型(痛点 / 数字 / 反差 / 悬念 / 身份点名 / 搜索长尾)、标题与描述原文、话题标签策略。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉与图文关系、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」,不得凭正文或标题想象补齐。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 走其 story-develop intake 收敛创意后按通用制作流程自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "narration-script": "- 子模块(仅口播类作品启用):起(从什么起步)、承(靠什么推进)、转(转折触发)、合(收束方式;CTA 的目标、位置与句式记在 `interaction-cta`),以及人称与语气、句长与语速、签名式表达。\n- 边界:非口播类或证据不足时写「未启用 / 未观测」;不得把单篇句式直接上升为规则。", "body-voice": "- 单篇观测:开头钩子(原文摘录)、正文组织方式(清单体 / 教程步骤 / 故事线 / 对比 / 观点输出)、分行与段落节奏、口语化程度与人称、emoji 与标点用法、签名式表达。\n- 证据边界:脚本统计只给句长 / 行数 / emoji / 标签等线索;口头禅与签名表达必须回读原文确认。", @@ -144,7 +144,7 @@ } TEMPLATE_CHECKLISTS = { - "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播文案(或真人口播录音路径);口播子模块未启用时是否避免规定逐句口播。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", + "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播终稿(`voiceover.md` 绝对路径)或真人口播录音路径——无论口播子模块是否启用;「不适用」仅限非口播类视频。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", "note": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题与封面图组是否来自 DNA 文档。\n- 正文表达的每条规则是否可从 DNA 文档推导,未使用空泛形容词。\n- 图组数量、构图与视觉风格是否与 DNA 一致;视觉结论是否有图片证据。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「软性推荐」),CTA 是否每篇只放一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", } diff --git a/crews/main/skills/expert-douyin/workflows/account-setup.md b/crews/main/skills/expert-douyin/workflows/account-setup.md index 532fbbef..e8af6dc9 100644 --- a/crews/main/skills/expert-douyin/workflows/account-setup.md +++ b/crews/main/skills/expert-douyin/workflows/account-setup.md @@ -2,7 +2,7 @@ 新号起号、账号定位梳理、内容支柱搭建、默认 `dna-0` 初始化、老号接手与诊断走这个 Workflow。独立账号对标走 `account-benchmark.md`。 -起号拆成六件事:定位清楚、观看理由成立、标签稳定、内容有用、互动真实、复盘持续;平台功能入口、算法权重、处罚规则按待确认信息处理。产出要能直接执行:表格、清单、脚本简报、选题池或复盘动作,少写空泛建议,多给"下一条视频该怎么做"。 +起号拆成六件事:定位清楚、观看理由成立、标签稳定、内容有用、互动真实、复盘持续;平台功能入口、算法权重、处罚规则按待确认信息处理。产出要能直接执行:表格、清单、话术简报、选题池或复盘动作,少写空泛建议,多给"下一条视频该怎么做"。 ## 入口判断 diff --git a/crews/main/skills/expert-douyin/workflows/content-production.md b/crews/main/skills/expert-douyin/workflows/content-production.md index 608ea708..da830e16 100644 --- a/crews/main/skills/expert-douyin/workflows/content-production.md +++ b/crews/main/skills/expert-douyin/workflows/content-production.md @@ -20,8 +20,8 @@ | 路线 | 判断 | 执行方 | | --- | --- | --- | | 素材组装 / 轻剪辑 | 用户手里有可用素材 | main 直接做:`video-edit` / `talking-head-cut` / `ui-demo` | -| 从零制作 | 没有素材,需要出脚本、拍摄/生成画面 | main 出制作简报,委托 `content-producer` | -| 脚本制作 | 用户已有脚本 | 根据dna对脚本做必要修改,提交用户确认后,脚本交 `content-producer` 制作 | +| 从零制作 | 没有素材,需要出脚本、拍摄/生成画面 | main 出 Brief,委托 `content-producer` | +| 脚本制作 | 用户已有脚本 | 用户脚本按素材处理(绝对路径写进 Brief 素材清单,必须保留的事实 / 结构要点写进 Brief 要求);main 只调策略层(选题 / 口播口径 / 植入 / CTA),不改写脚本本体、不动分镜与画面执行——需动分镜即改走 Brief 委托,由 CP 重出 | ### 3. 抖音链接的意图判断 @@ -77,6 +77,7 @@ DNA template 是 main agent 的生产输入模板: | 项目 | 规则 | | --- | --- | | 制作路线 | 素材组装 / 从零制作 / 脚本制作,判断依据见 Step 0 | +| workflow | 视频全案已确定形态时写 CP `expert-video` 支持的 workflow(reversal-ad / narration-video / collage-broll);未确定则省略(省略 = CP 走其 story-develop intake 收敛后按通用制作流程做) | | 主题 / 方向 | 用户给了明确主题时不得另起炉灶,仅按 DNA template 细化选题和钩子 | | 素材 | 用户提供的视频片段、图片、录音、文案必须优先使用 | | 目标观众 | 未指定时按 `business_knowledge.md` 和 DNA 受众关系推导 | @@ -187,14 +188,14 @@ DNA 约束的是选题与观看理由、标题与封面写法、内容创意原 ### 路线 B / C:委托 content-producer 制作 -1. 产出**制作简报** `douyin/outputs//brief.md`(Brief 是 main / CP 的唯一交接物): +1. 产出 **Brief** `douyin/outputs//brief.md`(Brief 是 main / CP 的唯一交接物): ```markdown # 抖音视频制作 Brief - 视频名 / slug: - platform:douyin -- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法) +- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 走其 story-develop intake workflow 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点(+ 反转设计,如为反转植入类) @@ -216,9 +217,9 @@ Brief 硬性规则: - **素材给绝对路径**:main 负责素材准备(用户素材预处理、`ui-demo` 录屏、从 `campaign_assets/` 挑选),把绝对路径写进 Brief。 - **甲乙方关系**:需求方向、品牌事实、发布文案归 main;制作方案、分镜、渲染参数归 CP。 -2. 口播类视频:按 DNA 的 `narration-script` 子模块写口播终稿 `douyin/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。 +2. 口播类视频:口播终稿一律由 main 写——`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写——落 `douyin/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。 3. 参考模式下,把选题与创意结论写进 Brief 的「内容创意」段即可;`viral-chaser` 拆解报告是 main 的采样材料,**不作为 Brief 附件交给 CP**。 -4. spawn `content-producer` 委托制作:只交 Brief + 素材绝对路径 + 口播文案 / 录音;不指定 CP 的工作区与制作方案。 +4. spawn `content-producer` 委托制作:只交 Brief 一份——素材、口播文案 / 录音均已以绝对路径写在 Brief 内;不指定 CP 的工作区与制作方案。 5. Brief 变更时更新版本并推送变更要点;已开工中间产物按新版取舍,弃用部分记入交付说明。 6. CP 交付后,按其回报的绝对路径把成片与封面取回 `douyin/outputs//`(`video.mp4` / `cover.jpg`),并把交付说明要点记入作品目录。 diff --git a/crews/main/skills/expert-douyin/workflows/style-dna.md b/crews/main/skills/expert-douyin/workflows/style-dna.md index 99dbf19a..060f2835 100644 --- a/crews/main/skills/expert-douyin/workflows/style-dna.md +++ b/crews/main/skills/expert-douyin/workflows/style-dna.md @@ -156,7 +156,7 @@ douyin-style-profiler update \ - **图文内容**:读取图文 DNA 文档与 template,main agent 直接生产。 - **视频全案**:读取视频 DNA 文档与 template,main agent 产出 **Brief**(+ 口播类的口播文案)。Brief 写明选题与观看理由、标题与简介、内容创意、`workflow`(视频形态的制作指向)、制作规格(横竖屏 / 时长带 / 画面风格 / 配音音色)、素材清单与授权(绝对路径)、验收标准、闸门批准人。 - **Brief 不含 DNA 信息**:Content Producer 看不到 main 的 DNA,只按 Brief 制作;也不要把 DNA 文档路径写进 Brief。 -- **口播类视频**:口播文案子模块启用时,口播终稿由 main agent 写好并随 Brief 交付;真人口播时由 main agent 指导用户录音并向用户取得录音文件。CP 不重写策略文案。 +- **口播类视频**:口播终稿一律由 main agent 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写);真人口播时由 main agent 指导用户录音并向用户取得录音文件。CP 不重写。 - **工作区**:main 不替 CP 建工作区,也不指定项目目录;CP 自建工作区,双方 T3 权限可互访取文件。 ## 对标接口 diff --git a/crews/main/skills/expert-wx-channel/SKILL.md b/crews/main/skills/expert-wx-channel/SKILL.md index d9d56bc8..b85e0ce4 100644 --- a/crews/main/skills/expert-wx-channel/SKILL.md +++ b/crews/main/skills/expert-wx-channel/SKILL.md @@ -43,9 +43,9 @@ metadata: 跨领域通用技能:`published-track`(发布记录与指标库)、`content-calibrator`(DNA 表现评估)、`smart-search`(跨平台搜索,选题调研优先社交平台)、`council`(定位决策辅助)、`siliconflow-img-gen`(封面图生成)。 -素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 +素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 平台速查 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md index d28e9b04..0c7a75b8 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md @@ -103,12 +103,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 走其 **story-develop** intake workflow 收敛创意(够用快速通过)后按**通用制作流程**做——通用制作流程是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 ## Focus ID 表 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py index b19a1ed7..1f5aa98b 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py @@ -88,7 +88,7 @@ "topic-angle": "- 单篇观测:选题类型、选题入口(现象 / 问题 / 冲突 / 数据 / 热点 / 挑战 / 个人经历)、目标人群为什么要看完(理解 / 判断 / 行动 / 避坑 / 身份认同 / 情绪共鸣)。\n- 边界:只记本篇,不判断跨篇稳定性;不评价选题好坏。", "title-cover": "- 单篇观测:**短标题**(发布页可填,官方称填了能获得更多流量;作品管理页不展示,取数时拿不到)与**视频描述**(含话题标签)两者的原文与写法模式。\n- 取数边界:抓取到的作品只有视频描述;短标题拿不到时写「未观测(管理页不展示)」,不得把描述当短标题。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 走其 story-develop intake 收敛创意后按通用制作流程自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "biz-implant": "- 单篇观测:是否有业务植入(纯内容 / 软植入 / 硬广直给)、植入位置与时机、植入载体(剧情道具 / 口播一句话 / 字幕卡片 / 场景背景 / 案例与数据 / 清单第 N 项 / 教程步骤内嵌 / 产品截图 / 购物车或留资组件 / 主页与私信引导)、植入方式原型(反转植入 / 痛点→方案 / 场景带入 / 实测对比 / 身份认同 / 口碑故事 / 教程内嵌 / 硬广直给)、内容与业务的衔接句(原文摘录)、植入密度与占比、品牌词与产品名出现方式与频次。\n- 证据要求:位置 + 载体 + 原文摘录三样齐全;本篇无植入时写「无植入(纯内容)」,不得留空。\n- 边界:只记植入套路,不写逐句广告文案;形态层面的「影视解说 + 反转植入」由 `video-form` 记形态与制作指向。", "interaction-cta": "- 单篇观测:行动目标(关注 / 评论 / 收藏 / 转发 / 私信 / 主页点击 / 进店 / 咨询 / 搜索品牌词 / 购物车 / 直播预约)与本篇主目标、CTA 出现位置与时机(口播收尾句 / 字幕卡 / 片尾贴片 / 描述区 / 正文结尾 / 图组末图 / 评论区)、CTA 句式与原文摘录(命令式 / 提问式 / 利益式 / 身份式 / 悬念式)、一篇放几个行动、诱因设计(利益点 / 情绪 / 身份认同 / 稀缺)、与业务转化目标的对应。\n- 合规边界:不隐藏站外联系方式、不绕平台检测、不以利益换互动,记为必须避免项。", @@ -120,7 +120,7 @@ } TEMPLATE_CHECKLISTS = { - "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 短标题与视频描述是否都已拟定(视频号两者都必须填),且写法来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播文案(或真人口播录音路径);口播子模块未启用时是否避免规定逐句口播。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", + "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 短标题与视频描述是否都已拟定(视频号两者都必须填),且写法来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播终稿(`voiceover.md` 绝对路径)或真人口播录音路径——无论口播子模块是否启用;「不适用」仅限非口播类视频。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", } DNA_SUBMODULE_NOTE = "- **口播文案子模块**(`narration-script`):仅口播类视频启用,用于指导 main agent 写同类型视频的口播文案;它不是独立 DNA,未启用时写「未启用」。\n- **账号运营子模块**(`account-bio`、`content-mix-cadence`):只在样本来自用户提供的对标账号(可从账号发布列表批量提取)时填写;结论只写进本 DNA 文档,不进 template;样本不足写「未观测」。" diff --git a/crews/main/skills/expert-wx-channel/workflows/content-production.md b/crews/main/skills/expert-wx-channel/workflows/content-production.md index 910f1c56..d30ae78b 100644 --- a/crews/main/skills/expert-wx-channel/workflows/content-production.md +++ b/crews/main/skills/expert-wx-channel/workflows/content-production.md @@ -20,8 +20,8 @@ | 路线 | 判断 | 执行方 | | --- | --- | --- | | 素材组装 / 轻剪辑 | 用户手里有可用素材 | main 直接做:`video-edit` / `talking-head-cut` / `ui-demo` | -| 从零制作 | 没有素材,需要出脚本、拍摄 / 生成画面 | main 出制作简报,委托 `content-producer` | -| 脚本制作 | 用户已有脚本 | 按 DNA 对脚本做必要修改,提交用户确认后,脚本交 `content-producer` 制作 | +| 从零制作 | 没有素材,需要出脚本、拍摄 / 生成画面 | main 出 Brief,委托 `content-producer` | +| 脚本制作 | 用户已有脚本 | 用户脚本按素材处理(绝对路径写进 Brief 素材清单,必须保留的事实 / 结构要点写进 Brief 要求);main 只调策略层(选题 / 口播口径 / 植入 / CTA),不改写脚本本体、不动分镜与画面执行——需动分镜即改走 Brief 委托,由 CP 重出 | ### 3. 参考视频的意图判断 @@ -193,14 +193,14 @@ DNA 约束的是选题与观看理由、短标题与视频描述写法、内容 ### 路线 B / C:委托 content-producer 制作 -1. 产出**制作简报** `wx_channel/outputs//brief.md`(Brief 是 main / CP 的唯一交接物): +1. 产出 **Brief** `wx_channel/outputs//brief.md`(Brief 是 main / CP 的唯一交接物): ```markdown # 视频号视频制作 Brief - 视频名 / slug: - platform:wx_channel -- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法) +- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 走其 story-develop intake workflow 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点(+ 反转设计,如为反转植入类) @@ -223,9 +223,9 @@ Brief 硬性规则: - **不写实现路径**:Brief 只写需求与验收标准(「植入段必须动态化、禁静态贴图」「字幕逐句对齐不飘」及分辨率/帧率/时长带等交付规格),不写实现手段——❌「参考/照改某脚本」、❌ ffmpeg/引擎参数表(CRF、threads、nice、MarginV 等)、❌ 工具链内部细节;用什么工具、什么参数达成需求归 CP。例外:用户点名的工程参数照传并注明「用户指定」。发现需求必须指定脚本/参数才说得清时,改写成可观察的验收结果——那是需求没写清,不是越界的许可。 - **甲乙方关系**:需求方向、品牌事实、发布文案归 main;制作方案、分镜、渲染参数归 CP。 -2. 口播类视频:按 DNA 的 `narration-script` 子模块写口播终稿 `wx_channel/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。口播子模块未启用时 Brief 写「口播文案:不适用」或只给要点,由 CP 按其 workflow 组织旁白,main 不再规定逐句台词。用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 +2. 口播类视频:口播终稿一律由 main 写——`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写——落 `wx_channel/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。「不适用」仅限非口播类视频。用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 3. 参考模式下,把选题与创意结论写进 Brief 的「内容创意」段即可;`viral-chaser` 拆解报告是 main 的采样材料,**不作为 Brief 附件交给 CP**。 -4. spawn `content-producer` 委托制作:只交 Brief + 素材绝对路径 + 口播文案 / 录音;不指定 CP 的工作区与制作方案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +4. spawn `content-producer` 委托制作:只交 Brief 一份——素材、口播文案 / 录音均已以绝对路径写在 Brief 内;不指定 CP 的工作区与制作方案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 5. Brief 变更时更新版本并推送变更要点;已开工中间产物按新版取舍,弃用部分记入交付说明。制作中发现 Brief 无法执行(素材缺失、时长超标)时,由 CP 回报、main 与用户确认后改 Brief,CP 不擅自改策略。 6. CP 交付后,按其回报的绝对路径把成片与封面取回 `wx_channel/outputs//`(`video.mp4` / `cover.jpg`),并把交付说明要点记入作品目录。用户直接提供成片时校验格式(`.mp4` / `.mov` / `.avi` / `.webm`)与时长后复制进作品目录。 diff --git a/crews/main/skills/expert-wx-channel/workflows/editing.md b/crews/main/skills/expert-wx-channel/workflows/editing.md index d0305f39..86ae59ea 100644 --- a/crews/main/skills/expert-wx-channel/workflows/editing.md +++ b/crews/main/skills/expert-wx-channel/workflows/editing.md @@ -67,7 +67,7 @@ ## 改后发布 -用户要求改完后发布 / 重新发布时,走 `content-production.md` 的成片后流程(Step 5-7): +用户要求改完后发布 / 重新发布时,走 `content-production.md` 的改后制作与发布流程(Step 5-7): 1. **成片同步**:脚本改动涉及画面或口播的,按 `content-production.md` Step 5 重新走视频制作(已有成片只需轻剪辑时走 `talking-head-cut` / `video-edit`)。 2. **存文件**:稿件与成片归位 `wx_channel/outputs//`,同步更新 `brief.md`、`voiceover.md`(如有)、`cover.jpg` 与 `dna-meta.json`。 diff --git a/crews/main/skills/expert-wx-channel/workflows/style-dna.md b/crews/main/skills/expert-wx-channel/workflows/style-dna.md index 952d40a4..0c935adc 100644 --- a/crews/main/skills/expert-wx-channel/workflows/style-dna.md +++ b/crews/main/skills/expert-wx-channel/workflows/style-dna.md @@ -145,7 +145,7 @@ wx-channel-style-profiler update \ - **视频全案**:读取 DNA 文档与 template,main agent 产出 **Brief**(+ 口播类的口播文案)。Brief 写明选题与观看理由、内容创意、`workflow`(视频形态的制作指向)、制作规格、素材清单与授权(绝对路径)、验收标准、闸门批准人。 - **短标题与视频描述由 main agent 拟定**:视频号发布页两项都可填,官方称填短标题能获得更多流量,因此两项都必须有、发布时都必须填;但入库(`published-track`)与 `wx-channel-engagement` 匹配只用**视频描述**,短标题不写库。这两项属发布文案,不进 Brief 的制作要求。 - **Brief 不含 DNA 信息**:Content Producer 看不到 main 的 DNA,只按 Brief 制作;也不要把 DNA 文档路径写进 Brief。 -- **口播类视频**:口播文案子模块启用时,口播终稿由 main agent 写好并随 Brief 交付;真人口播时由 main agent 指导用户录音并向用户取得录音文件。CP 不重写策略文案。 +- **口播类视频**:口播终稿一律由 main agent 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写);真人口播时由 main agent 指导用户录音并向用户取得录音文件。CP 不重写。 - **工作区**:main 不替 CP 建工作区,也不指定项目目录;CP 自建工作区,双方 T3 权限可互访取文件。 ## 对标接口 @@ -157,4 +157,4 @@ wx-channel-style-profiler update \ - 一个生产任务只使用一个 DNA,且作品类型与任务一致;需要融合时先更新 DNA。 - 样本、用户输入、数据反馈必须可追溯。 - 账号运营子模块(简介写法、内容形式比例、发布习惯)只在对标账号批量样本下填写,且只进 DNA 文档不进 template;覆盖度不足就写未观测。 -- Template 只写 main agent 可执行的规则:视频类 = Brief 正文模板 + 口播文案模板;图文类 = 图文写作模板。都不写成片制作细节。 +- Template 只写 main agent 可执行的规则:视频类 = Brief 正文模板 + 口播文案模板。都不写成片制作细节。 diff --git a/crews/main/skills/expert-xhs/SKILL.md b/crews/main/skills/expert-xhs/SKILL.md index 3756f039..f7fbf967 100644 --- a/crews/main/skills/expert-xhs/SKILL.md +++ b/crews/main/skills/expert-xhs/SKILL.md @@ -50,7 +50,7 @@ DNA 存储目录是 `xhs/dna/`。未指定 DNA 时默认使用并更新 `dna-0` DNA 是**从一批作品样本提取并聚合出的内容生产规则集**:图文 10 维——选题与观看理由、标题与封面图组、内容创意、**匹配的用户问题**、正文表达与语气、图组视觉、**业务植入套路**、**互动引导与 CTA 套路**,加账号运营子模块(简介写法、内容形式比例、发布习惯);视频 11 维——前四项加**业务植入套路**、**互动引导与 CTA 套路**、视频内容形态与制作指向、制作规格与视听倾向、口播文案子模块与账号运营子模块。搜索维度是小红书必备:最大流量池来自搜索,关键词必须落到用户可能的提问原句。DNA 指导 main agent 写图文或出视频 Brief(+ 口播文案),不规定创作细节与成片制作。维度框架 v2 位于 `xhs-style-profiler` 的 `references/note-dna-framework.md` 与 `references/video-dna-framework.md`。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播文案由 main 按 `narration-script` 子模块写好并随 Brief 交付(真人口播时,指导用户录音并取得录音文件),CP 不重写策略文案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 数据与记录 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md index b4f85eb9..f2803b75 100644 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md @@ -106,12 +106,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 走其 **story-develop** intake workflow 收敛创意(够用快速通过)后按**通用制作流程**做——通用制作流程是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 ## Focus ID 表 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py index 2717d4f5..2835f284 100755 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py @@ -105,7 +105,7 @@ "title-cover": "- 单篇观测:标题类型(痛点 / 数字 / 反差 / 悬念 / 身份点名 / 搜索长尾)、标题与描述原文、话题标签策略。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉与图文关系、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」,不得凭正文或标题想象补齐。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", "search-intent": "- 单篇观测:本篇命中的关键词(核心词 / 痛点词 / 场景词 / 人群词)、用户可能的提问原句、搜索意图层级、标签承载的搜索意图。\n- 边界:单篇只记候选;聚合后才形成「关键词 → 用户问题 → 内容形式」的搜索意图地图。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 走其 story-develop intake 收敛创意后按通用制作流程自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "narration-script": "- 子模块(仅口播类作品启用):起(从什么起步)、承(靠什么推进)、转(转折触发)、合(收束方式;CTA 的目标、位置与句式记在 `interaction-cta`),以及人称与语气、句长与语速、签名式表达。\n- 边界:非口播类或证据不足时写「未启用 / 未观测」;不得把单篇句式直接上升为规则。", "body-voice": "- 单篇观测:开头钩子(原文摘录)、正文组织方式(清单体 / 教程步骤 / 故事线 / 对比 / 观点输出)、分行与段落节奏、口语化程度与人称、emoji 与标点用法、签名式表达。\n- 证据边界:脚本统计只给句长 / 行数 / emoji / 标签等线索;口头禅与签名表达必须回读原文确认。", @@ -146,7 +146,7 @@ } TEMPLATE_CHECKLISTS = { - "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播文案(或真人口播录音路径);口播子模块未启用时是否避免规定逐句口播。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 关键词是否落到用户可能的提问原句(不只平台标签),并与内容形式匹配。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", + "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播终稿(`voiceover.md` 绝对路径)或真人口播录音路径——无论口播子模块是否启用;「不适用」仅限非口播类视频。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 关键词是否落到用户可能的提问原句(不只平台标签),并与内容形式匹配。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", "note": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题与封面图组是否来自 DNA 文档。\n- 正文表达的每条规则是否可从 DNA 文档推导,未使用空泛形容词。\n- 图组数量、构图与视觉风格是否与 DNA 一致;视觉结论是否有图片证据。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「软性推荐」),CTA 是否每篇只放一个主行动、句式可执行且未越合规红线。\n- 关键词是否落到用户可能的提问原句(不只平台标签),并与内容形式匹配。\n- 用户输入是否已转译为具体执行规则。", } diff --git a/crews/main/skills/expert-xhs/workflows/content-production.md b/crews/main/skills/expert-xhs/workflows/content-production.md index 57d909b5..981859cd 100644 --- a/crews/main/skills/expert-xhs/workflows/content-production.md +++ b/crews/main/skills/expert-xhs/workflows/content-production.md @@ -81,7 +81,7 @@ DNA template 是 main agent 的生产输入模板:**图文 DNA 的 template = | 标题 | 硬限制 ≤ 20 字;覆盖主关键词或用户问题 | | 图组 | 硬限制 ≤ 18 张;用户提供优先 | | 行动引导 | 只放一个平台内动作 | -| workflow | 视频全案已确定形态时写 CP `expert-video` 支持的 workflow(reversal-ad / narration-video / collage-broll);未确定则省略 | +| workflow | 视频全案已确定形态时写 CP `expert-video` 支持的 workflow(reversal-ad / narration-video / collage-broll);未确定则省略(省略 = CP 走其 story-develop intake 收敛后按通用制作流程做) | 优先级: @@ -162,7 +162,7 @@ DNA template 是 main agent 的生产输入模板:**图文 DNA 的 template = - 视频名 / slug: - platform:xhs -- workflow:reversal-ad / narration-video / collage-broll / 未指定(未指定 = CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法) +- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 走其 story-develop intake workflow 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点 @@ -187,9 +187,9 @@ Brief 硬性规则: ### 口播文案规则 -- **口播类视频的口播文案由 main agent 出**:按 DNA 文档的 `narration-script`(口播文案子模块)写终稿,保存为作品目录下的 `voiceover.md`,在 Brief 中给**绝对路径**。CP 不重写策略文案,只做声画实现。 +- **口播类视频的口播终稿一律由 main agent 出**:`narration-script`(口播文案子模块)启用时按其结构写,未启用时按用户要求与 Brief 核心传达写,保存为作品目录下的 `voiceover.md`,在 Brief 中给**绝对路径**。CP 不重写,只做声画实现。 - **真人口播**:明确要用用户真人声音时,指导用户按口播稿录音,完成后向用户取得录音文件,落到作品目录并在 Brief 中给绝对路径。 -- **口播子模块未启用**:Brief 写明「口播文案:不适用」或只给要点,由 CP 按其 workflow 组织旁白;main 不再规定逐句台词。 +- **口播子模块未启用**:口播终稿仍由 main 写——按用户要求与 Brief 的核心传达 / CTA 要求直接撰写(不依赖 DNA 子模块),落 `voiceover.md` 并在 Brief 给绝对路径;「不适用」仅限非口播类视频。 - 用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 ## 【确认】正文 / Brief @@ -216,7 +216,7 @@ Brief 硬性规则: | --- | --- | | 用户直接提供成片 | 校验格式与时长,复制到作品目录 | | 已有素材需简单加工 | main 用 `video-edit` / `talking-head-cut` 处理 | -| 全案制作 | 委托 `content-producer`:只交 Brief + 素材绝对路径 + 口播文案 / 录音,不指定 CP 工作区;指定 `workflow` 必须采用;未指定时 CP 按其通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。成片与封面按 CP 回报的绝对路径取回作品目录 | +| 全案制作 | 委托 `content-producer`:只交 Brief 一份(素材、口播文案 / 录音以绝对路径写在 Brief 内),不指定 CP 工作区;指定 `workflow` 必须采用;未指定时 CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。成片与封面按 CP 回报的绝对路径取回作品目录 | 视频封面优先从成片选帧;需要更强视觉冲击时用 `siliconflow-img-gen`。 diff --git a/crews/main/skills/expert-xhs/workflows/style-dna.md b/crews/main/skills/expert-xhs/workflows/style-dna.md index 28ea35d2..e1f073da 100644 --- a/crews/main/skills/expert-xhs/workflows/style-dna.md +++ b/crews/main/skills/expert-xhs/workflows/style-dna.md @@ -149,7 +149,7 @@ xhs-style-profiler update \ - **图文笔记**:读取图文 DNA 文档与 template(含关键词与用户问题段),main agent 直接生产。 - **视频全案**:读取视频 DNA 文档与 template,main agent 产出 **Brief**(+ 口播类的口播文案)。Brief 写明选题与观看理由、标题与简介、内容创意、关键词与用户问题、`workflow`(视频形态的制作指向)、制作规格、素材清单与授权(绝对路径)、验收标准、闸门批准人。 - **Brief 不含 DNA 信息**:Content Producer 看不到 main 的 DNA,只按 Brief 制作;也不要把 DNA 文档路径写进 Brief。 -- **口播类视频**:口播文案子模块启用时,口播终稿由 main agent 写好并随 Brief 交付;真人口播时由 main agent 向用户取得录音文件。CP 不重写策略文案。 +- **口播类视频**:口播终稿一律由 main agent 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写);真人口播时由 main agent 指导用户录音并向用户取得录音文件。CP 不重写。 - **工作区**:main 不替 CP 建工作区,也不指定项目目录;CP 自建工作区,双方 T3 权限可互访取文件。 ## 对标接口 From 421cdb4704c08d5b526bed9e24a151acab550ef7 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 13:09:10 +0800 Subject: [PATCH 09/25] =?UTF-8?q?fix(video,=E5=85=A8=E7=BA=BF):=20?= =?UTF-8?q?=E5=9B=9E=E6=AD=A3=20story-develop=20=E5=8F=A3=E5=BE=84?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=A7=A6=E5=8F=91=E6=9D=A1=E4=BB=B6=E6=98=AF?= =?UTF-8?q?=20Brief=20=E7=BC=BA=E5=A4=B1/=E8=B4=A8=E9=87=8F=E4=B8=8D?= =?UTF-8?q?=E8=B6=B3=EF=BC=8C=E9=9D=9E=E3=80=8C=E6=9C=AA=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=20workflow=20=E9=BB=98=E8=AE=A4=E5=85=A5=E5=8F=A3=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户澄清:story-develop 的产出是 Brief;Stage 0→15 是 CP 一切视频必走流程(无论指定什么 workflow),起点是 Brief;甲方没给 Brief 或 Brief 质量不足以完成 script 生产才调用 story-develop。 - CP 侧 SKILL.md / AGENTS.md / video-producer.sh / story-develop.md:「未指定 workflow 时 默认从 story-develop 进入」的错误口径回正为「未指定时按通用制作流程做;Brief 创意不足 以直接写剧本时先走 story-develop 收敛 Brief」;story-develop 何时触发恢复原始判据 (甲方没给 Brief 或 Brief 创意不足以直接写 script.md,够用就不触发),顺手修掉原文 行尾杂散反引号 - main 三平台 SKILL.md / video-dna-framework / build_style_profile / content-production: 同步回正;main 侧保留一处新增语境「Brief 缺失或创意不足时 CP 会走 story-develop 与 Brief owner 收敛 Brief」(让 main 看到 CP 反向提问时有上下文) - 上一轮的其他修复(口播代写、脚本路线、只交 Brief、制作简报统一等)全部保留 README 上游借鉴列表补 gbro-collage-broll(MIT)引用:Collage B-roll workflow 的 方法论来源与移植口径说明 --- README.md | 1 + crews/content-producer/AGENTS.md | 2 +- crews/content-producer/skills/expert-video/SKILL.md | 9 ++++----- .../expert-video/tools/video-producer/video-producer.sh | 4 ++-- .../skills/expert-video/workflows/story-develop.md | 5 ++--- crews/main/skills/expert-douyin/SKILL.md | 2 +- .../references/video-dna-framework.md | 4 ++-- .../douyin-style-profiler/scripts/build_style_profile.py | 2 +- .../skills/expert-douyin/workflows/content-production.md | 4 ++-- crews/main/skills/expert-wx-channel/SKILL.md | 2 +- .../references/video-dna-framework.md | 4 ++-- .../scripts/build_style_profile.py | 2 +- .../expert-wx-channel/workflows/content-production.md | 4 ++-- crews/main/skills/expert-xhs/SKILL.md | 2 +- .../xhs-style-profiler/references/video-dna-framework.md | 4 ++-- .../xhs-style-profiler/scripts/build_style_profile.py | 2 +- .../skills/expert-xhs/workflows/content-production.md | 6 +++--- 17 files changed, 29 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index da0411fb..4cc30894 100755 --- a/README.md +++ b/README.md @@ -340,6 +340,7 @@ wiseflow/ - html-video(nexu-io 的 HTML 视频渲染方案 — `video-producer` 的 Stage 10 静帧→成片渲染思路与素材组装约定参考自此) https://github.com/nexu-io/html-video - ViMax(HKUDS 的视频生成框架 — `video-producer` 的机位一致性约束与素材 slot 规划借鉴其镜头规划策略) https://github.com/HKUDS/ViMax - OpenMontage(calesthio 的开源蒙太奇剪辑方案 — `video-producer` 的 Stage 12 拼接成片+转场工作流借鉴其片段组装与节奏控制思路) https://github.com/calesthio/OpenMontage +- gbro-collage-broll(MIT — 半调纸拼贴 B-roll 三闸门方法论 — `expert-video` 的 Collage B-roll workflow 移植自此:隐喻设计法、语义色场表、visual-spec schema、静帧/视频 QA 标准照搬,闸门映射为 GATE A/B、渲染栈换成 siliconflow-img-gen + aigc-video-gen i2v) https://github.com/pyang5166/gbro-collage-broll - agent-skills-launch-pack_(起号方法论知识来源) https://github.com/chenjin-cmd/agent-skills-launch-pack_ ## Citation diff --git a/crews/content-producer/AGENTS.md b/crews/content-producer/AGENTS.md index 6d3386d0..b1752d50 100644 --- a/crews/content-producer/AGENTS.md +++ b/crews/content-producer/AGENTS.md @@ -19,7 +19,7 @@ | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | `expert-video` | 通用制作流程 + Reversal Ad 细化 | | 甲方交付口播文案或真人口播录音,要合成声画 | `expert-video` | 通用制作流程 + Narration Video 细化 | | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | `expert-video` | 通用制作流程 + Collage B-roll 细化 | -| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 先走 `story-develop` intake workflow(创意不清时与甲方收敛 Brief,够用则快速通过),再进 Stage 1 `script-write`,按通用制作流程出片(叙事 / 动效 / 蒙太奇手法由我据创意自定) | +| "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走(叙事 / 动效 / 蒙太奇手法由我据创意自定);Brief 缺失或创意不清时先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write` | | 已有素材要剪辑、修整、拼接、配音、烧字幕 | `expert-video` | 通用制作流程的 Stage 12 工具箱(只做几何级修整) | | "做网页/落地页/APP 界面/品牌视觉体系" | `expert-design` | Web Page / App UI / Brand Visual | diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index 807354b2..89e197f3 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -56,7 +56,7 @@ metadata: | 层 | 是什么 | 怎么用 | |----|--------|--------| | **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→15 阶段链、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | -| **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:Brief 未指定 workflow 时进入(创意不足先收敛,够用快速通过) | +| **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:Brief 缺失或创意不足以直接写剧本时触发 | | **工具说明**(`tools/<工具>/SKILL.md`) | 每个子命令的入参、产物路径、退出码与旁路条件 | 调用前查;本文不重复参数细节 | > 通用制作流程**不是**与类型 workflow 并列的第四条路,也**不是**"Brief 没指定类型时的 fallback"。它是底座;类型 workflow 只在底座上细化,产出特定类型的视频。 @@ -72,10 +72,10 @@ metadata: | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 隐喻清单即 script(GATE A 检)→ 静帧即素材(GATE B 检 contact sheet)→ Stage 10 `collage-broll render` 批量 i2v 组装;阶段裁剪表见 workflow | - Brief 指定了 `workflow`:**先读对应文档并直接采用**,不得替换成自创流程。 -- Brief 未指定 `workflow` 且无明确类型信号:走 `story-develop` intake workflow——创意不足以直接写剧本时与甲方收敛,够用则快速通过——再进 Stage 1 `script-write`。叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`。 +- Brief 未指定 `workflow`:仍走通用制作流程,叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`;Brief 创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write`。 - 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。 -> `story-develop` 是 **intake 类 workflow**(Stage 0 创意澄清,**不是 `Brief.workflow` 取值**),与上表 type 类 workflow 正交:Brief 未指定 workflow 时默认从它进入——创意不足以直接写剧本先收敛,够用则快速通过——再按通用制作流程(+ 信号识别到的 type workflow)执行。详见 `workflows/story-develop.md`。 +> `story-develop` 是 **intake 类 workflow**(Stage 0 创意澄清,**不是 `Brief.workflow` 取值**),与上表 type 类 workflow 正交:甲方没给 Brief 或 Brief 创意不足以直接写剧本时走它收敛出 Brief,再按通用制作流程 + 对应 type workflow 执行。详见 `workflows/story-develop.md`。 不属于我的活(交回甲方或转其他专家包): @@ -116,8 +116,7 @@ workflow 文档在技能包内,不是项目目录内容;项目目录只放 B ``` Stage 0 Brief intake 读甲方 Brief,核对字段,缺口向 Brief owner 澄清; - 甲方未给 Brief,或 Brief 未指定 workflow → 走 story-develop intake workflow - (workflows/story-develop.md;创意不足先收敛,够用快速通过) + 甲方未给 Brief 或 Brief 不足以直接写剧本时 → 走 story-develop intake workflow(workflows/story-develop.md) Stage 1 script-write Brief 创意 → 分场剧本(同时间同地点分一场、可拍化描述、enhancer 润色) 基线生产从此开始。 Stage 2 script-self-eval 脚本自评 N 维打分,任一维 <3 必返工(落稿锁定时只检查不改写) diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh index 277a66ee..298a57a9 100755 --- a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh +++ b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh @@ -24,8 +24,8 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 流程: 通用制作流程(expert-video SKILL.md 的 Stage 0→15 + 两闸门)是做**任何**视频都要遵循的基准, 不是"没指定类型时的备选"。Brief 指定 workflow 时,先读包内 workflows/.md, - 按其阶段裁剪调用下列子命令;未指定时先走 story-develop intake workflow - (workflows/story-develop.md:创意不清先收敛,够用快速通过),再进 script-write。 + 按其阶段裁剪调用下列子命令;未指定时只按通用制作流程走。Brief 创意不足以直接写剧本时, + 先走 story-develop intake workflow(workflows/story-develop.md)与甲方收敛 Brief,再进 script-write。 子命令(按阶段序): reference-concepts 可选 吃甲方给的参考拆解报告出 2–3 差异化概念 diff --git a/crews/content-producer/skills/expert-video/workflows/story-develop.md b/crews/content-producer/skills/expert-video/workflows/story-develop.md index 253778cd..3a126eab 100644 --- a/crews/content-producer/skills/expert-video/workflows/story-develop.md +++ b/crews/content-producer/skills/expert-video/workflows/story-develop.md @@ -1,13 +1,12 @@ # Workflow:Story Develop(Brief intake · 创意澄清) -**这是 intake 类 workflow,不是 type 类 workflow。** type workflow(`narration-video` / `collage-broll` / `reversal-ad`)细化"某类视频怎么制作",是 `Brief.workflow` 的取值;本 workflow 解决"甲方还没把创意讲清楚时,怎么和他对话把 Brief 收敛出来",**不是 `Brief.workflow` 的取值**,也不与 type workflow 互斥。**Brief 未指定 workflow 时,Stage 0(Brief intake)默认从这里进入**,与任何 type workflow 正交可组合——收敛出 Brief 后,仍按通用制作流程 + 对应 type workflow 执行。 +**这是 intake 类 workflow,不是 type 类 workflow。** type workflow(`narration-video` / `collage-broll` / `reversal-ad`)细化"某类视频怎么制作",是 `Brief.workflow` 的取值;本 workflow 解决"甲方还没把创意讲清楚时,怎么和他对话把 Brief 收敛出来",**不是 `Brief.workflow` 的取值**,也不与 type workflow 互斥。它在 **Stage 0(Brief intake)** 阶段触发,与任何 type workflow 正交可组合——收敛出 Brief 后,仍按通用制作流程 + 对应 type workflow 执行。 ## 何时触发 -- **Brief 未指定 `workflow` 且无明确类型信号**("从零做视频""出一支完整视频"):默认入口。 - **模式 B(直接对接用户)**:用户没给 Brief,或只给了模糊想法("做个短片""帮我策划一下")。 - **模式 A(Subagent 承制)**:main 的 Brief 缺关键字段(创意 / 核心传达不清、规格缺失),需向 Brief owner 澄清。 -- 创意足以直接写 `script.md` 时**快速通过**:确认 Brief 字段完整即直进 Stage 1,不硬走对话。 +- 触发判据:**甲方没给 Brief,或 Brief 的"创意"不足以直接写 `script.md`**。够用就不触发。 > ❗ 本 workflow 是**澄清与收敛**,不是替甲方创作。选题方向、品牌事实、卖点承诺、CTA 口径归甲方;我只把甲方脑子里的创意问清楚、整理成 Brief,不自行脑补,也不反过来指挥甲方。 diff --git a/crews/main/skills/expert-douyin/SKILL.md b/crews/main/skills/expert-douyin/SKILL.md index edd66752..e9785d51 100644 --- a/crews/main/skills/expert-douyin/SKILL.md +++ b/crews/main/skills/expert-douyin/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 风格与 DNA diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md index d0f9daf5..2f0e8b18 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md @@ -103,12 +103,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定 | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 走其 **story-develop** intake workflow 收敛创意(够用快速通过)后按**通用制作流程**做——通用制作流程是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。 ## Focus ID 表 diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py index 9dfa1968..78e398cb 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py @@ -104,7 +104,7 @@ "topic-angle": "- 单篇观测:选题类型、选题入口(现象 / 问题 / 冲突 / 数据 / 热点 / 挑战 / 个人经历)、目标人群为什么要看完(理解 / 判断 / 行动 / 避坑 / 身份认同 / 情绪共鸣)。\n- 边界:只记本篇,不判断跨篇稳定性;不评价选题好坏。", "title-cover": "- 单篇观测:标题类型(痛点 / 数字 / 反差 / 悬念 / 身份点名 / 搜索长尾)、标题与描述原文、话题标签策略。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉与图文关系、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」,不得凭正文或标题想象补齐。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 走其 story-develop intake 收敛创意后按通用制作流程自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "narration-script": "- 子模块(仅口播类作品启用):起(从什么起步)、承(靠什么推进)、转(转折触发)、合(收束方式;CTA 的目标、位置与句式记在 `interaction-cta`),以及人称与语气、句长与语速、签名式表达。\n- 边界:非口播类或证据不足时写「未启用 / 未观测」;不得把单篇句式直接上升为规则。", "body-voice": "- 单篇观测:开头钩子(原文摘录)、正文组织方式(清单体 / 教程步骤 / 故事线 / 对比 / 观点输出)、分行与段落节奏、口语化程度与人称、emoji 与标点用法、签名式表达。\n- 证据边界:脚本统计只给句长 / 行数 / emoji / 标签等线索;口头禅与签名表达必须回读原文确认。", diff --git a/crews/main/skills/expert-douyin/workflows/content-production.md b/crews/main/skills/expert-douyin/workflows/content-production.md index da830e16..51d9d205 100644 --- a/crews/main/skills/expert-douyin/workflows/content-production.md +++ b/crews/main/skills/expert-douyin/workflows/content-production.md @@ -77,7 +77,7 @@ DNA template 是 main agent 的生产输入模板: | 项目 | 规则 | | --- | --- | | 制作路线 | 素材组装 / 从零制作 / 脚本制作,判断依据见 Step 0 | -| workflow | 视频全案已确定形态时写 CP `expert-video` 支持的 workflow(reversal-ad / narration-video / collage-broll);未确定则省略(省略 = CP 走其 story-develop intake 收敛后按通用制作流程做) | +| workflow | 视频全案已确定形态时写 CP `expert-video` 支持的 workflow(reversal-ad / narration-video / collage-broll);未确定则省略(省略 = CP 按其通用制作流程做) | | 主题 / 方向 | 用户给了明确主题时不得另起炉灶,仅按 DNA template 细化选题和钩子 | | 素材 | 用户提供的视频片段、图片、录音、文案必须优先使用 | | 目标观众 | 未指定时按 `business_knowledge.md` 和 DNA 受众关系推导 | @@ -195,7 +195,7 @@ DNA 约束的是选题与观看理由、标题与封面写法、内容创意原 - 视频名 / slug: - platform:douyin -- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 走其 story-develop intake workflow 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) +- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 按其通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点(+ 反转设计,如为反转植入类) diff --git a/crews/main/skills/expert-wx-channel/SKILL.md b/crews/main/skills/expert-wx-channel/SKILL.md index b85e0ce4..7be776e3 100644 --- a/crews/main/skills/expert-wx-channel/SKILL.md +++ b/crews/main/skills/expert-wx-channel/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 平台速查 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md index 0c7a75b8..1c3a8153 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md @@ -103,12 +103,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定 | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 走其 **story-develop** intake workflow 收敛创意(够用快速通过)后按**通用制作流程**做——通用制作流程是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。 ## Focus ID 表 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py index 1f5aa98b..a8a52e11 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py @@ -88,7 +88,7 @@ "topic-angle": "- 单篇观测:选题类型、选题入口(现象 / 问题 / 冲突 / 数据 / 热点 / 挑战 / 个人经历)、目标人群为什么要看完(理解 / 判断 / 行动 / 避坑 / 身份认同 / 情绪共鸣)。\n- 边界:只记本篇,不判断跨篇稳定性;不评价选题好坏。", "title-cover": "- 单篇观测:**短标题**(发布页可填,官方称填了能获得更多流量;作品管理页不展示,取数时拿不到)与**视频描述**(含话题标签)两者的原文与写法模式。\n- 取数边界:抓取到的作品只有视频描述;短标题拿不到时写「未观测(管理页不展示)」,不得把描述当短标题。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 走其 story-develop intake 收敛创意后按通用制作流程自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "biz-implant": "- 单篇观测:是否有业务植入(纯内容 / 软植入 / 硬广直给)、植入位置与时机、植入载体(剧情道具 / 口播一句话 / 字幕卡片 / 场景背景 / 案例与数据 / 清单第 N 项 / 教程步骤内嵌 / 产品截图 / 购物车或留资组件 / 主页与私信引导)、植入方式原型(反转植入 / 痛点→方案 / 场景带入 / 实测对比 / 身份认同 / 口碑故事 / 教程内嵌 / 硬广直给)、内容与业务的衔接句(原文摘录)、植入密度与占比、品牌词与产品名出现方式与频次。\n- 证据要求:位置 + 载体 + 原文摘录三样齐全;本篇无植入时写「无植入(纯内容)」,不得留空。\n- 边界:只记植入套路,不写逐句广告文案;形态层面的「影视解说 + 反转植入」由 `video-form` 记形态与制作指向。", "interaction-cta": "- 单篇观测:行动目标(关注 / 评论 / 收藏 / 转发 / 私信 / 主页点击 / 进店 / 咨询 / 搜索品牌词 / 购物车 / 直播预约)与本篇主目标、CTA 出现位置与时机(口播收尾句 / 字幕卡 / 片尾贴片 / 描述区 / 正文结尾 / 图组末图 / 评论区)、CTA 句式与原文摘录(命令式 / 提问式 / 利益式 / 身份式 / 悬念式)、一篇放几个行动、诱因设计(利益点 / 情绪 / 身份认同 / 稀缺)、与业务转化目标的对应。\n- 合规边界:不隐藏站外联系方式、不绕平台检测、不以利益换互动,记为必须避免项。", diff --git a/crews/main/skills/expert-wx-channel/workflows/content-production.md b/crews/main/skills/expert-wx-channel/workflows/content-production.md index d30ae78b..81f70b7a 100644 --- a/crews/main/skills/expert-wx-channel/workflows/content-production.md +++ b/crews/main/skills/expert-wx-channel/workflows/content-production.md @@ -200,7 +200,7 @@ DNA 约束的是选题与观看理由、短标题与视频描述写法、内容 - 视频名 / slug: - platform:wx_channel -- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 走其 story-develop intake workflow 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) +- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 按其通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点(+ 反转设计,如为反转植入类) @@ -225,7 +225,7 @@ Brief 硬性规则: 2. 口播类视频:口播终稿一律由 main 写——`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写——落 `wx_channel/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。「不适用」仅限非口播类视频。用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 3. 参考模式下,把选题与创意结论写进 Brief 的「内容创意」段即可;`viral-chaser` 拆解报告是 main 的采样材料,**不作为 Brief 附件交给 CP**。 -4. spawn `content-producer` 委托制作:只交 Brief 一份——素材、口播文案 / 录音均已以绝对路径写在 Brief 内;不指定 CP 的工作区与制作方案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +4. spawn `content-producer` 委托制作:只交 Brief 一份——素材、口播文案 / 录音均已以绝对路径写在 Brief 内;不指定 CP 的工作区与制作方案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。 5. Brief 变更时更新版本并推送变更要点;已开工中间产物按新版取舍,弃用部分记入交付说明。制作中发现 Brief 无法执行(素材缺失、时长超标)时,由 CP 回报、main 与用户确认后改 Brief,CP 不擅自改策略。 6. CP 交付后,按其回报的绝对路径把成片与封面取回 `wx_channel/outputs//`(`video.mp4` / `cover.jpg`),并把交付说明要点记入作品目录。用户直接提供成片时校验格式(`.mp4` / `.mov` / `.avi` / `.webm`)与时长后复制进作品目录。 diff --git a/crews/main/skills/expert-xhs/SKILL.md b/crews/main/skills/expert-xhs/SKILL.md index f7fbf967..c130475d 100644 --- a/crews/main/skills/expert-xhs/SKILL.md +++ b/crews/main/skills/expert-xhs/SKILL.md @@ -50,7 +50,7 @@ DNA 存储目录是 `xhs/dna/`。未指定 DNA 时默认使用并更新 `dna-0` DNA 是**从一批作品样本提取并聚合出的内容生产规则集**:图文 10 维——选题与观看理由、标题与封面图组、内容创意、**匹配的用户问题**、正文表达与语气、图组视觉、**业务植入套路**、**互动引导与 CTA 套路**,加账号运营子模块(简介写法、内容形式比例、发布习惯);视频 11 维——前四项加**业务植入套路**、**互动引导与 CTA 套路**、视频内容形态与制作指向、制作规格与视听倾向、口播文案子模块与账号运营子模块。搜索维度是小红书必备:最大流量池来自搜索,关键词必须落到用户可能的提问原句。DNA 指导 main agent 写图文或出视频 Brief(+ 口播文案),不规定创作细节与成片制作。维度框架 v2 位于 `xhs-style-profiler` 的 `references/note-dna-framework.md` 与 `references/video-dna-framework.md`。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 走其 story-develop intake workflow(创意不足先收敛,够用快速通过)后按通用制作流程做。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 数据与记录 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md index f2803b75..da8f8077 100644 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md @@ -106,12 +106,12 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | | 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | -| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定 | 省略 | +| 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | | 已有真人口播素材去口气词、剪高光 | main `talking-head-cut`(不委托 CP) | — | | 产品操作录屏 | main `ui-demo`(不委托 CP) | — | -Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 走其 **story-develop** intake workflow 收敛创意(够用快速通过)后按**通用制作流程**做——通用制作流程是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。 +Brief 写了 `workflow` 时 Content Producer 必须直接采用,不得替换成自创流程;未写时 CP 按其**通用制作流程**做——那是 CP 所有视频工作的基准准则,不是与其他 workflow 并列的选项,也不是 fallback,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。 ## Focus ID 表 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py index 2835f284..73072216 100755 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py @@ -105,7 +105,7 @@ "title-cover": "- 单篇观测:标题类型(痛点 / 数字 / 反差 / 悬念 / 身份点名 / 搜索长尾)、标题与描述原文、话题标签策略。\n- 视觉证据:封面或首帧必须由视觉模型读取图片,至少提取画面主体与场景、构图与画幅、色彩体系、光线与质感、风格与媒介、文字视觉与图文关系、品牌识别元素、避免项,并反推为可执行的 AIGC 提示词要素;无图片写「未提供」,不得凭正文或标题想象补齐。", "content-idea": "- 单篇观测:一句话创意内核、创意类型、展开逻辑(悬念 / 反转 / 递进 / 对比 / 清单 / 实测)、记忆点。\n- 可复用信号:这个创意套路换成别的主题还能怎么用。\n- 边界:只记创意层,不记创作细节(逐句台词、镜头表、脚本结构、转场与编码参数)。", "search-intent": "- 单篇观测:本篇命中的关键词(核心词 / 痛点词 / 场景词 / 人群词)、用户可能的提问原句、搜索意图层级、标签承载的搜索意图。\n- 边界:单篇只记候选;聚合后才形成「关键词 → 用户问题 → 内容形式」的搜索意图地图。", - "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 走其 story-develop intake 收敛创意后按通用制作流程自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", + "video-form": "- 单篇观测:视频内容形态(口播 / 实拍拼接 / 影视解说+反转植入 / 纯 AIGC 动画 / 创意转场动效 / 录屏演示 / 图文卡片视频 / 混合)与判定依据(画面证据、口播占比、素材来源)。\n- 制作指向:必须落到真实存在的资源名——Content Producer `expert-video` 的某个 workflow(Reversal Ad / Narration Video / Collage B-roll;不属这三类就写「不指定类型 workflow」,由 CP 按通用制作流程据创意自定手法),或 main 的素材加工技能(`video-edit` / `talking-head-cut` / `ui-demo`);不得发明不存在的名字。", "production-spec": "- 单篇观测:横屏或竖屏、时长带、画面风格(色调、质感、字幕样式倾向、信息密度)、配音音色与声音形态(原声口播 / TTS / 旁白 / 纯画面字幕)、BGM 与音效倾向、封面规格。\n- 边界:只记规格与倾向,不规定镜头参数、逐镜设计、转场与编码细节——那些归 Content Producer。", "narration-script": "- 子模块(仅口播类作品启用):起(从什么起步)、承(靠什么推进)、转(转折触发)、合(收束方式;CTA 的目标、位置与句式记在 `interaction-cta`),以及人称与语气、句长与语速、签名式表达。\n- 边界:非口播类或证据不足时写「未启用 / 未观测」;不得把单篇句式直接上升为规则。", "body-voice": "- 单篇观测:开头钩子(原文摘录)、正文组织方式(清单体 / 教程步骤 / 故事线 / 对比 / 观点输出)、分行与段落节奏、口语化程度与人称、emoji 与标点用法、签名式表达。\n- 证据边界:脚本统计只给句长 / 行数 / emoji / 标签等线索;口头禅与签名表达必须回读原文确认。", diff --git a/crews/main/skills/expert-xhs/workflows/content-production.md b/crews/main/skills/expert-xhs/workflows/content-production.md index 981859cd..9a3e69f1 100644 --- a/crews/main/skills/expert-xhs/workflows/content-production.md +++ b/crews/main/skills/expert-xhs/workflows/content-production.md @@ -81,7 +81,7 @@ DNA template 是 main agent 的生产输入模板:**图文 DNA 的 template = | 标题 | 硬限制 ≤ 20 字;覆盖主关键词或用户问题 | | 图组 | 硬限制 ≤ 18 张;用户提供优先 | | 行动引导 | 只放一个平台内动作 | -| workflow | 视频全案已确定形态时写 CP `expert-video` 支持的 workflow(reversal-ad / narration-video / collage-broll);未确定则省略(省略 = CP 走其 story-develop intake 收敛后按通用制作流程做) | +| workflow | 视频全案已确定形态时写 CP `expert-video` 支持的 workflow(reversal-ad / narration-video / collage-broll);未确定则省略(省略 = CP 按其通用制作流程做) | 优先级: @@ -162,7 +162,7 @@ DNA template 是 main agent 的生产输入模板:**图文 DNA 的 template = - 视频名 / slug: - platform:xhs -- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 走其 story-develop intake workflow 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) +- workflow:reversal-ad / narration-video / collage-broll(视频形态未确定时省略本字段;省略 = CP 按其通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定) - 选题与观看理由: - 核心传达: - 内容创意:创意原型 + 展开逻辑 + 记忆点 @@ -216,7 +216,7 @@ Brief 硬性规则: | --- | --- | | 用户直接提供成片 | 校验格式与时长,复制到作品目录 | | 已有素材需简单加工 | main 用 `video-edit` / `talking-head-cut` 处理 | -| 全案制作 | 委托 `content-producer`:只交 Brief 一份(素材、口播文案 / 录音以绝对路径写在 Brief 内),不指定 CP 工作区;指定 `workflow` 必须采用;未指定时 CP 走其 story-develop intake 收敛创意后按通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定。成片与封面按 CP 回报的绝对路径取回作品目录 | +| 全案制作 | 委托 `content-producer`:只交 Brief 一份(素材、口播文案 / 录音以绝对路径写在 Brief 内),不指定 CP 工作区;指定 `workflow` 必须采用;未指定时 CP 按其通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。成片与封面按 CP 回报的绝对路径取回作品目录 | 视频封面优先从成片选帧;需要更强视觉冲击时用 `siliconflow-img-gen`。 From c88dbcff8df58458400e34ac3626bbb096561230 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 16:13:24 +0800 Subject: [PATCH 10/25] =?UTF-8?q?feat(=E5=85=A8=E7=BA=BF):=20=E6=98=8E?= =?UTF-8?q?=E7=A1=AE=E5=8F=A3=E6=92=AD=E4=B8=8E=E6=97=81=E7=99=BD=E7=9A=84?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E2=80=94=E2=80=94=E5=8F=A3=E6=92=AD=E7=A8=BF?= =?UTF-8?q?=E5=BD=92=20main=EF=BC=8C=E6=97=81=E7=99=BD=E5=AE=8C=E5=85=A8?= =?UTF-8?q?=E5=BD=92=20CP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户定稿的术语边界:口播 = 真人出镜 / 数字人 / 真人录音,口播稿由 main 出 (voiceover.md 随 Brief 交付,或 main 向用户取得录音);旁白 = 剪辑配的解说 (画外音),完全由 CP 写、GATE A 交审,main 不出旁白稿。 main 三平台(SKILL.md 硬边界 / Brief 模板口播字段 / content-production 口播规则 / video-dna-framework 用途声明与路由表 / build_style_profile 校验清单): - 口播定义显式化(真人出镜 / 数字人 / 真人录音),Brief 口播字段注明 「旁白归 CP 写,写不适用」 - DNA 用途声明收窄为「口播(真人出镜/数字人)的口播文案;旁白不在 DNA 指导范围」 CP expert-video(SKILL.md 交付表与路由 / AGENTS.md 路由行 / narration-video / reversal-ad / collage-broll): - narration-video:类型定义拆两形态——口播稿甲方出、旁白稿由我写 GATE A 交审; 硬边界与验收分形态表述;模式 B 代拟条款限定为用户本人出镜口播 - reversal-ad:解说旁白归我写(原「未交付时由我写」的模糊分支消除), 口播形态(真人出镜/数字人)才走甲方交付落稿锁定;正文泛指声音轨的 「口播」统一改「解说/旁白」,避免与口播归属混淆 - collage-broll:文稿行注明口播形态给 voiceover.md,其余给 Brief 文稿字段 --- crews/content-producer/AGENTS.md | 2 +- .../skills/expert-video/SKILL.md | 6 +++--- .../expert-video/workflows/collage-broll.md | 2 +- .../expert-video/workflows/narration-video.md | 13 ++++++------ .../expert-video/workflows/reversal-ad.md | 20 +++++++++---------- crews/main/skills/expert-douyin/SKILL.md | 2 +- .../references/video-dna-framework.md | 4 ++-- .../scripts/build_style_profile.py | 2 +- .../workflows/content-production.md | 4 ++-- crews/main/skills/expert-wx-channel/SKILL.md | 2 +- .../references/video-dna-framework.md | 4 ++-- .../scripts/build_style_profile.py | 2 +- .../workflows/content-production.md | 4 ++-- crews/main/skills/expert-xhs/SKILL.md | 2 +- .../references/video-dna-framework.md | 4 ++-- .../scripts/build_style_profile.py | 2 +- .../workflows/content-production.md | 6 +++--- 17 files changed, 41 insertions(+), 40 deletions(-) diff --git a/crews/content-producer/AGENTS.md b/crews/content-producer/AGENTS.md index b1752d50..0af3fdd8 100644 --- a/crews/content-producer/AGENTS.md +++ b/crews/content-producer/AGENTS.md @@ -17,7 +17,7 @@ |---------|--------|--------| | Brief 指定 `workflow`(如 `reversal-ad`) | `expert-video` | 通用制作流程 + 读 Brief 指定的 `workflows/<值>.md`,按其阶段裁剪执行 | | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | `expert-video` | 通用制作流程 + Reversal Ad 细化 | -| 甲方交付口播文案或真人口播录音,要合成声画 | `expert-video` | 通用制作流程 + Narration Video 细化 | +| 口播(真人出镜 / 数字人 / 真人录音,甲方出口播稿)或旁白(TTS 配音解说,稿由 CP 写)要合成声画 | `expert-video` | 通用制作流程 + Narration Video 细化 | | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | `expert-video` | 通用制作流程 + Collage B-roll 细化 | | "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走(叙事 / 动效 / 蒙太奇手法由我据创意自定);Brief 缺失或创意不清时先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write` | | 已有素材要剪辑、修整、拼接、配音、烧字幕 | `expert-video` | 通用制作流程的 Stage 12 工具箱(只做几何级修整) | diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index 89e197f3..fceefbad 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -32,7 +32,7 @@ metadata: 1. **先明确 Brief**:至少问清做什么类型视频、给谁看、要传达什么、时长与横竖屏、有没有现成素材、要不要口播(谁的声音)、什么时候要;整理成 `brief.md` 发用户确认。模糊想法("做个短片""帮我策划一下")**不算确认**,不得据此调渲染类工具。 2. **素材必须落实位置**:让用户给出**绝对路径**(或明确授权从哪个目录取),逐条 `ls` 确认真实存在、可解码;缺什么明说,不许拿"待补"开工。 -3. **口播内容**:口播文案由甲方出(模式 A 是 main agent,模式 B 是用户)。用户只给大意时我可代拟,但必须发用户确认定稿;明确是用户真人口播时,必须拿到录音文件(绝对路径)。 +3. **口播与旁白分开**:**口播**(真人出镜 / 数字人 / 真人录音)的口播稿由甲方出(模式 A 是 main agent,模式 B 是用户)——模式 B 用户只给大意时可代拟,但必须发用户确认定稿;真人口播必须拿到录音文件(绝对路径)。**旁白**(剪辑配的解说)完全由我写,GATE A 交审,不找甲方要旁白稿。 ### 甲方交付什么、我交付什么 @@ -40,7 +40,7 @@ metadata: |----------|------| | `brief.md` | 绝对路径。含视频类型 / workflow、主题与观看理由、核心传达、业务植入与 CTA(植入位置与方式、内容与业务的衔接句要求、CTA 主目标与句式)、时长与横竖屏、素材清单、封面要求、交付与验收、闸门批准人。**不含 DNA 信息**(甲方内部资产,我不读也不用) | | 已有素材 | 绝对路径逐条列出,含来源与授权说明;我只做入库校验与技术处理 | -| 口播文案 / 录音 | 有口播时甲方出具 `voiceover.md`(绝对路径);我不重写策略文案,只做声画实现。真人口播时给录音文件绝对路径 | +| 口播文案 / 录音 | **口播**(真人出镜 / 数字人 / 真人录音)时甲方出具 `voiceover.md`(绝对路径)或录音文件,我不重写、只做声画实现;**旁白**(剪辑配解说)不由甲方出,由我写 | - ✅ 缺字段 → 向 Brief owner 澄清后再开工。 - ❌ 缺字段 → 自己猜品牌卖点、自己编授权、自己改需求方向。 @@ -68,7 +68,7 @@ metadata: | 视频类型 / 入口信号 | workflow | Brief `workflow` 值 | 它细化了什么 | |--------------------|----------|---------------------|--------------| | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | Reversal Ad | `reversal-ad` | 三段结构占比、反转点落在 55%–76%、四种反转手法、反转幅度与接入丝滑度两轴(含二次反转 CTA)、素材三模式 sourcing(Blender 片库 / 用户直供三查 / AIGC)、意象桥与钩连句、植入段约束 | -| 口播类(甲方交付口播文案或真人录音,要合成声画) | Narration Video | `narration-video` | 口播稿落稿锁定不重写、按字级时间戳配画面、声音规范与验收清单 | +| 口播 / 旁白类(口播:真人出镜、数字人、真人录音;旁白:TTS 配音解说) | Narration Video | `narration-video` | 口播稿落稿锁定不重写、旁白稿由我写 GATE A 交审、按字级时间戳配画面、声音规范与验收清单 | | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 隐喻清单即 script(GATE A 检)→ 静帧即素材(GATE B 检 contact sheet)→ Stage 10 `collage-broll render` 批量 i2v 组装;阶段裁剪表见 workflow | - Brief 指定了 `workflow`:**先读对应文档并直接采用**,不得替换成自创流程。 diff --git a/crews/content-producer/skills/expert-video/workflows/collage-broll.md b/crews/content-producer/skills/expert-video/workflows/collage-broll.md index fec81cb8..c9d55d69 100644 --- a/crews/content-producer/skills/expert-video/workflows/collage-broll.md +++ b/crews/content-producer/skills/expert-video/workflows/collage-broll.md @@ -16,7 +16,7 @@ Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼 | Brief 字段 | 要求 | |-----------|------| -| 文稿 | `voiceover.md`(绝对路径)或 Brief 内文稿字段,逐条可独立成句。**落稿锁定不重写**——隐喻是文稿的视觉转译,不是改写;发现文稿无法转译(一句话塞多个隐喻、超时长带)报 Brief owner | +| 文稿 | 甲方交付:口播形态给 `voiceover.md`(绝对路径),其余给 Brief 内文稿字段,逐条可独立成句。**落稿锁定不重写**——隐喻是文稿的视觉转译,不是改写;发现文稿无法转译(一句话塞多个隐喻、超时长带)报 Brief owner | | form | 画幅 / 时长 / 分辨率 / 声音;未指定按默认(9:16、5s、720P、无声) | | gates | GATE A / GATE B 批准人;代理批准时写明批准范围 | | acceptance | 验收标准(未指定按「交付」节默认) | diff --git a/crews/content-producer/skills/expert-video/workflows/narration-video.md b/crews/content-producer/skills/expert-video/workflows/narration-video.md index b51ace0c..d8cb3900 100644 --- a/crews/content-producer/skills/expert-video/workflows/narration-video.md +++ b/crews/content-producer/skills/expert-video/workflows/narration-video.md @@ -4,21 +4,22 @@ Brief 里写 `workflow: narration-video`,或 Brief 交付了口播文案 / 真 ## 类型定义 -以**人声讲述**为主干的视频。口播文案是甲方(main agent 或用户)的策略产物,已定稿交付;我负责声画实现——配音或录音处理、字级时间戳对齐、按语义配画面、字幕、BGM、成片与封面。 +以**人声讲述**为主干的视频,分两种声音形态、文稿归属不同:**口播**(真人出镜 / 数字人 / 真人口播录音)的口播稿是甲方(main agent 或用户)的策略产物,随 Brief 交付(`voiceover.md` 或录音文件),我落稿锁定只做声画实现;**旁白**(TTS 配音解说、剪辑配的画外音)的文稿完全由我写(据 Brief 创意),GATE A 交审。我负责配音或录音处理、字级时间戳对齐、按语义配画面、字幕、BGM、成片与封面。 ## 三种输入形态 | 形态 | 甲方交付 | 我做什么 | |------|---------|-----------| -| TTS 旁白 + 画面 | `voiceover.md`(口播终稿)+ 素材(可选) | `awk-tts` 生成旁白(音色按 Brief)→ `narration-align` 拿字级时间戳 → 按时间戳切配画面 → 字幕 → BGM | +| TTS 旁白 + 画面 | 素材(可选);旁白稿**由我写**,GATE A 交审 | `awk-tts` 生成旁白(音色按 Brief)→ `narration-align` 拿字级时间戳 → 按时间戳切配画面 → 字幕 → BGM | | 真人口播录音 + 画面 | 录音文件绝对路径 + 素材(可选) | ASR 拿时间戳(Stage 11 场景 D)→ 按时间戳排画面 → 字幕 → 音质差时降噪 → BGM ducking | | 真人出镜口播录像 | 录像素材绝对路径 | 加字幕、BGM、片头片尾、包装、封面;语义级高光剪辑与去口气词交回 main 的 `talking-head-cut` | ## 硬边界 -- **口播文案原样落稿锁定**:落 `script/script.md`,不重写、不"顺手优化"措辞、不增删卖点。 -- 发现文案有问题(超时长带、合规风险、抽象到无法配画面)→ **报甲方**,不自行改。 -- 模式 B(用户直接对话)下用户只给大意时,可代拟文案,但必须发用户确认,定稿后才算 `voiceover`。 +- **口播稿原样落稿锁定**:口播(真人出镜 / 数字人 / 真人录音)的文稿落 `script/script.md`,不重写、不"顺手优化"措辞、不增删卖点。 +- **旁白稿由我写**:TTS 配音解说的文稿据 Brief 创意起草,GATE A 交审,不找甲方要旁白稿。 +- 口播稿发现问题(超时长带、合规风险、抽象到无法配画面)→ **报甲方**,不自行改。 +- 模式 B(用户直接对话)下用户本人出镜口播、只给大意时,可代拟口播稿,但必须发用户确认,定稿后才算 `voiceover`;旁白无需代拟(本就归我)。 - 语义级剪辑(去口气词、智能剪重点)归 main 的 `talking-head-cut`;我只做几何级修整(切段/拼接/混音/烧字幕/补轨)。 ## 声音规范 @@ -31,7 +32,7 @@ Brief 里写 `workflow: narration-video`,或 Brief 交付了口播文案 / 真 ## 验收检查 -1. 口播与甲方交付逐字一致(除甲方书面同意的修改)。 +1. 口播类:成片口播与甲方交付逐字一致(除甲方书面同意的修改);旁白类:旁白与 GATE A 批准稿一致。 2. 音画同步:每句口播对应画面不越界,无黑屏空转、无长静帧。 3. 字幕与口播一致、无错字、在安全区内。 4. 响度归一化(-14 LUFS)已跑并记录。 diff --git a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md index 2d2463ce..e4479c7d 100644 --- a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md +++ b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md @@ -11,7 +11,7 @@ Brief 里写 `workflow: reversal-ad` 时使用。本文是**通用制作流程 | 段 | 时长占比 | 功能 | 硬要求 | |----|---------|------|--------| | 解说正文 | 63%–76% | 讲一个自洽、有冲突、有悬念的故事 | 主悬念一句话可复述,且贯穿到反转点;段尾停在「求助 / 任务 / 待发 / 冲突」节点 | -| 反转过渡 | 3%–13% | 用一句话或一帧把剧情指向产品 | 必须双关或因果可追,不许硬切;口播明写因果 | +| 反转过渡 | 3%–13% | 用一句话或一帧把剧情指向产品 | 必须双关或因果可追,不许硬切;解说明写因果 | | 产品植入 | 15%–27% | 集中讲产品,讲完立即收尾 | 3–4 句单点深打,覆盖 Brief 允许的 ≥3 个价值点,每点有对应画面;片尾 CTA 优先用二次反转剧情化承载 | 反转点落在总时长 **55%–76%** 之间(集中植入偏前,两段式偏后)。占比是创作约束,不是硬编码参数;Brief 指定时以 Brief 为准。产品植入段末尾可设**二次反转**承载 CTA(见「反转幅度与接入丝滑度」),其时长计入产品植入段,不另立第四段。 @@ -24,7 +24,7 @@ Brief 里写 `workflow: reversal-ad` 时使用。本文是**通用制作流程 | 双关置换式 | 剧情核心意象与产品同名同形 | 意象必须在解说正文里先建立,反转时复用同一意象 | | 身份彩蛋式 | 主角身份在反转处变成与产品相关的角色 | 身份转换要由剧情自然推出,不靠旁白硬说 | | 画面转场式 | 素材的某一帧平滑转到产品介绍的首帧(比如说,素材中女主角的眼睛出现倒影,然后放大到产品段首帧) | 转场过渡可以使用 AIGC,同时指定首帧和尾帧。| -| 戏中戏式 | 剧情内的媒介(录像机屏幕、照片、窗外、手机屏)先出现产品画面 | 口播仍讲剧情句,画面先行;随后全屏切产品段 | +| 戏中戏式 | 剧情内的媒介(录像机屏幕、照片、窗外、手机屏)先出现产品画面 | 解说仍讲剧情句,画面先行;随后全屏切产品段 | ## 反转幅度与接入丝滑度(第一质量轴) @@ -49,13 +49,13 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): 3. 接入句的因果能一句话复述吗(丝滑度)? 4. CTA 也是剧情的一部分吗(设了二次反转时)? -> 正例为真人口播场景,其第二人称口语不受本文口播规范约束;由我代笔第三人称解说体时,CTA 句式仍按口播规范,但 CTA **动机必须由剧情给出**。 +> 正例为真人口播场景,其第二人称口语不受本文解说规范约束;由我代笔第三人称解说体时,CTA 句式仍按解说规范,但 CTA **动机必须由剧情给出**。 ## 叙事与植入规则 - **单线闭环**:主角动机一句话说清并在片内闭环;素材撑不住就改写,不留断头线。 - **节拍 ≤ 6**:每节拍一句,句间因果可追。 -- **钩连句**:转生、化身、置换等转折必须用口播明写因果,并用同色、同物或同动作的画面衔接。 +- **钩连句**:转生、化身、置换等转折必须用解说旁白明写因果,并用同色、同物或同动作的画面衔接。 - **意象桥**:产品段第一镜必须复用解说正文的核心意象;禁止无关风景空镜硬切。 - **反转前零产品提及**:不插 punch 句、不散落卖点、不出现品牌暗示(含 logo、包装、界面)。 - **收束**:产品段讲完立即收尾,不加无信息量的仪式句或氛围空镜。 @@ -63,10 +63,10 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): - **音效**:可在反转瞬间用 whoosh 等音效标记剧情→产品切换,但不得用音效替代因果衔接。 - **合规**:正片零 URL、零域名、零联系方式;官网、仓库、价格、活动入口只写进 `final-deliver.md` 交甲方(发布说明由甲方拟)。产品能力只讲 Brief 允许的范围,不承诺收益数字。 -## 口播(旁白) +## 解说旁白与口播 -- 甲方交付 `voiceover.md` 时**原样落稿锁定**(Stage 1 `script-write` 不重写口播终稿,仍跑 `script-self-eval` 做检查)。 -- 未交付时由我写,GATE A 交审。默认规范:第三人称解说体;避免问句、感叹号、第二人称(你/您/家人们)与促销信号词(「接下来介绍」「这是我们的」);句长 10–15 字,语速 7–8 字/秒。 +- 解说是**旁白**,完全由我写,GATE A 交审。默认规范:第三人称解说体;避免问句、感叹号、第二人称(你/您/家人们)与促销信号词(「接下来介绍」「这是我们的」);句长 10–15 字,语速 7–8 字/秒。 +- 真人出镜 / 数字人**口播**形态(甲方交付 `voiceover.md` 或真人录音)时按口播规则:落稿锁定,Stage 1 `script-write` 原样入 `script.md` 不重写,仍跑 `script-self-eval` 做检查。 - 真人口播录音(甲方提供)走 Stage 11 场景 D:ASR 拿时间戳后按时间戳排素材,不重配旁白。 ## 素材 sourcing @@ -97,7 +97,7 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): | implant_cta | Brief「业务植入与 CTA」字段:植入位置与方式、内容与业务的衔接句要求、CTA 主目标与句式;未给时按本 workflow 默认(反转点 55%–76%、植入段单点深打、片尾一个主行动且优先二次反转剧情化承载),并在 GATE A 说明 | | twist_variant | 任务指引式 / 双关置换式 / 身份彩蛋式 / 画面转场式 / 戏中戏式;未指定时由我据素材与故事选定,并在 GATE A 说明理由 | | story_source | 解说正文的故事来源(开源片名 / 用户素材 / AIGC 生成;开源片名优先 Blender 开源电影片库,见「素材 sourcing」) | -| voiceover | 甲方交付口播终稿路径;未交付时写明由我起草 | +| voiceover | 仅真人出镜 / 数字人口播形态填(甲方交付口播终稿路径);解说旁白形态写「不适用」——旁白由我写 | | source_mode | open_license_footage / user_provided / aigc / mixed | | assets | 素材绝对路径、来源 URL、许可证、授权确认记录 | | form | 横竖屏、时长带、画面风格、配音音色与 BGM 倾向 | @@ -110,7 +110,7 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): 1. **规格一次锁定**:横竖屏、目标时长、分辨率与帧率由 Brief 决定;不为"看起来高级"擅自放大素材。需要统一规格时先确认素材源质量与环境约束,再写入 timeline 计划与决策日志。 2. **帧率一致性**:混排来源素材时先确认各段帧率;不一致必须在切片时用 `clip-trim --normalize WxH@FPS` 统一(或 `assemble` 归一化参数),不得不同帧率直接 concat 交付。拼接一律带 `assemble --verify-fps`(断言成片帧率)+ `--expect-durations`(逐段时长 vs 计划 ± 容差),输出后核对音画同步与段边界。 3. **旁白排布**:逐句 TTS 模式(解说/反转植入类默认)走 `narration-layout`——实测镜头时长累积起点、每句对齐镜头起点、防重叠守卫(min_gap)、逐句与末句越界断言、SRT 与混音一步产出;**守卫断言失败改计划(镜头时长/文案),不放宽容差硬过,更不手写排布脚本**。整段旁白模式走 `mix-audio` + `narration-align` 的实测时长 / 字级时间戳。两种模式都不用文本长度估算;每句旁白不得越过对应镜头边界(确需跨镜的桥句在 plan 里显式 `allow_spill`);连续旁白保留呼吸间隔,累计漂移可追溯到 `audio/abs_starts.json` 的每段实测时长。 -4. **字幕安全区**:`subtitles.srt` 必须来自对齐后的口播时间轴;烧录前检查目标画幅安全区,避免字幕落到画面中部或关键主体上。字幕样式由 Brief 决定,不硬编码项目个案参数。 +4. **字幕安全区**:`subtitles.srt` 必须来自对齐后的解说时间轴;烧录前检查目标画幅安全区,避免字幕落到画面中部或关键主体上。字幕样式由 Brief 决定,不硬编码项目个案参数。 5. **AIGC 原声**:AIGC 声画同出素材如含有效环境音,可作低音量音床并在口播下 ducking;无有效音轨时补静音,不用噪声填充。 6. **决策审计**:素材取舍、规格、音色、字幕样式、反转手法选择、fallback 与弃用中间产物都写入 `script/decisions.json` 或 `final-deliver.md`。 @@ -125,6 +125,6 @@ GATE A 交审脚本时必须附四问答案(`script-self-eval` 同查): 5. 反转两轴达标:铺垫段可信为纯内容视频且与产品世界落差足够(幅度)、接入句因果一句话可复述(丝滑);设二次反转时 CTA 是剧情动机的延伸。 6. 产品段占比与反转点位置落在承诺区间,且与选定手法一致。 7. 每段素材来源与授权可追溯;Blender 署名、用户素材授权确认、AIGC 标注要求均已写入 `final-deliver.md`。 -8. 口播、字幕、画面、音效与 Brief / `voiceover.md` 一致。 +8. 解说旁白 / 口播(按形态)、字幕、画面、音效与 GATE A 批准稿 / Brief / `voiceover.md` 一致。 9. `video-review` verdict=pass;未通过不得交付。 10. `final-deliver.md` 含素材来源与协议、口播终稿、各段实际时长、自检结果、弃用产物与遗留问题。 diff --git a/crews/main/skills/expert-douyin/SKILL.md b/crews/main/skills/expert-douyin/SKILL.md index e9785d51..741ca161 100644 --- a/crews/main/skills/expert-douyin/SKILL.md +++ b/crews/main/skills/expert-douyin/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;**口播**(真人出镜 / 数字人 / 真人录音)的口播稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写;**旁白**(剪辑配的解说)完全由 CP 写,main 不出旁白稿。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 风格与 DNA diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md index 2f0e8b18..56105520 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/references/video-dna-framework.md @@ -22,7 +22,7 @@ DNA 文档 -> DNA template |--------|----------| | 选题与观看理由、标题与封面写法、内容创意原型、业务植入套路、互动引导与 CTA 套路、视频内容形态与制作指向、制作规格与视听倾向、(口播类)口播文案子DNA、(对标账号)账号运营子模块 | 创作细节、脚本结构、逐句台词、镜头表、转场与编码参数——那些归 Content Producer | -视频 DNA 的用途是指导 main agent 出具 **Brief.md** 与(口播类的)**口播文案**。template 就是 Brief 正文模板 + 口播文案模板(可选),不是成片制作模板。账号运营子模块(简介写法、内容形式比例、发布习惯)只写进 DNA 文档,**不进 template**。 +视频 DNA 的用途是指导 main agent 出具 **Brief.md** 与**口播**(真人出镜 / 数字人)的**口播文案**;旁白(剪辑配的解说)由 CP 写,不在 DNA 指导范围。template 就是 Brief 正文模板 + 口播文案模板(可选),不是成片制作模板。账号运营子模块(简介写法、内容形式比例、发布习惯)只写进 DNA 文档,**不进 template**。 ## 维度(10 维) @@ -101,7 +101,7 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | DNA 观测到的内容形态 | 制作指向(只能写真实存在的资源名) | Brief `workflow` 字段值 | |---------------------|-----------------------------------|------------------------| | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | -| 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | +| 口播 / 旁白类(真人口播或数字人出镜;或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | | 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | diff --git a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py index 78e398cb..a183f64f 100644 --- a/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-douyin/tools/douyin-style-profiler/scripts/build_style_profile.py @@ -144,7 +144,7 @@ } TEMPLATE_CHECKLISTS = { - "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播终稿(`voiceover.md` 绝对路径)或真人口播录音路径——无论口播子模块是否启用;「不适用」仅限非口播类视频。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", + "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类(真人出镜 / 数字人 / 真人录音)是否附口播终稿(`voiceover.md` 绝对路径)或真人录音路径——无论口播子模块是否启用;剪辑配解说的旁白归 CP 写,Brief 口播字段写「不适用」。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", "note": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题与封面图组是否来自 DNA 文档。\n- 正文表达的每条规则是否可从 DNA 文档推导,未使用空泛形容词。\n- 图组数量、构图与视觉风格是否与 DNA 一致;视觉结论是否有图片证据。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「软性推荐」),CTA 是否每篇只放一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", } diff --git a/crews/main/skills/expert-douyin/workflows/content-production.md b/crews/main/skills/expert-douyin/workflows/content-production.md index 51d9d205..f23de7b3 100644 --- a/crews/main/skills/expert-douyin/workflows/content-production.md +++ b/crews/main/skills/expert-douyin/workflows/content-production.md @@ -203,7 +203,7 @@ DNA 约束的是选题与观看理由、标题与封面写法、内容创意原 - 标题与简介:发布标题、简介文案、话题标签(main 定稿) - 封面要求:封面主文案 + 视觉方向 - 制作规格:横屏 / 竖屏、时长带、画面风格、配音音色与声音形态、BGM 与音效、字幕 -- 口播文案:`voiceover.md` 绝对路径(口播类必填)/ 真人口播录音绝对路径 / 不适用 +- 口播文案:`voiceover.md` 绝对路径 / 真人口播录音绝对路径 / 不适用(口播 = 真人出镜、数字人、真人录音;剪辑配解说的**旁白归 CP 写**,写「不适用」) - 素材清单:逐条**绝对路径** + 来源 + 授权(无素材时写「无,由 CP 按 Brief 取材」) - 交付物与验收:`video.mp4` + `cover.jpg` + `final-deliver.md`,回报三者绝对路径;验收标准 - 闸门:GATE A / GATE B 批准人(用户或 main 代理批准 + 批准范围) @@ -217,7 +217,7 @@ Brief 硬性规则: - **素材给绝对路径**:main 负责素材准备(用户素材预处理、`ui-demo` 录屏、从 `campaign_assets/` 挑选),把绝对路径写进 Brief。 - **甲乙方关系**:需求方向、品牌事实、发布文案归 main;制作方案、分镜、渲染参数归 CP。 -2. 口播类视频:口播终稿一律由 main 写——`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写——落 `douyin/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。 +2. 口播(真人出镜 / 数字人 / 真人录音)视频:口播稿一律由 main 写——`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写——落 `douyin/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。剪辑配解说的旁白由 CP 写,main 不出旁白稿。 3. 参考模式下,把选题与创意结论写进 Brief 的「内容创意」段即可;`viral-chaser` 拆解报告是 main 的采样材料,**不作为 Brief 附件交给 CP**。 4. spawn `content-producer` 委托制作:只交 Brief 一份——素材、口播文案 / 录音均已以绝对路径写在 Brief 内;不指定 CP 的工作区与制作方案。 5. Brief 变更时更新版本并推送变更要点;已开工中间产物按新版取舍,弃用部分记入交付说明。 diff --git a/crews/main/skills/expert-wx-channel/SKILL.md b/crews/main/skills/expert-wx-channel/SKILL.md index 7be776e3..a74901f5 100644 --- a/crews/main/skills/expert-wx-channel/SKILL.md +++ b/crews/main/skills/expert-wx-channel/SKILL.md @@ -45,7 +45,7 @@ metadata: 素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;**口播**(真人出镜 / 数字人 / 真人录音)的口播稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写;**旁白**(剪辑配的解说)完全由 CP 写,main 不出旁白稿。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 平台速查 diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md index 1c3a8153..c92692a7 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/references/video-dna-framework.md @@ -22,7 +22,7 @@ DNA 文档 -> DNA template |--------|----------| | 选题与观看理由、标题与封面写法、内容创意原型、业务植入套路、互动引导与 CTA 套路、视频内容形态与制作指向、制作规格与视听倾向、(口播类)口播文案子DNA、(对标账号)账号运营子模块 | 创作细节、脚本结构、逐句台词、镜头表、转场与编码参数——那些归 Content Producer | -视频 DNA 的用途是指导 main agent 出具 **Brief.md** 与(口播类的)**口播文案**。template 就是 Brief 正文模板 + 口播文案模板(可选),不是成片制作模板。账号运营子模块(简介写法、内容形式比例、发布习惯)只写进 DNA 文档,**不进 template**。 +视频 DNA 的用途是指导 main agent 出具 **Brief.md** 与**口播**(真人出镜 / 数字人)的**口播文案**;旁白(剪辑配的解说)由 CP 写,不在 DNA 指导范围。template 就是 Brief 正文模板 + 口播文案模板(可选),不是成片制作模板。账号运营子模块(简介写法、内容形式比例、发布习惯)只写进 DNA 文档,**不进 template**。 ## 维度(10 维) @@ -101,7 +101,7 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | DNA 观测到的内容形态 | 制作指向(只能写真实存在的资源名) | Brief `workflow` 字段值 | |---------------------|-----------------------------------|------------------------| | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | -| 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | +| 口播 / 旁白类(真人口播或数字人出镜;或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | | 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | diff --git a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py index a8a52e11..39f0fd93 100644 --- a/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-wx-channel/tools/wx-channel-style-profiler/scripts/build_style_profile.py @@ -120,7 +120,7 @@ } TEMPLATE_CHECKLISTS = { - "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 短标题与视频描述是否都已拟定(视频号两者都必须填),且写法来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播终稿(`voiceover.md` 绝对路径)或真人口播录音路径——无论口播子模块是否启用;「不适用」仅限非口播类视频。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", + "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 短标题与视频描述是否都已拟定(视频号两者都必须填),且写法来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类(真人出镜 / 数字人 / 真人录音)是否附口播终稿(`voiceover.md` 绝对路径)或真人录音路径——无论口播子模块是否启用;剪辑配解说的旁白归 CP 写,Brief 口播字段写「不适用」。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", } DNA_SUBMODULE_NOTE = "- **口播文案子模块**(`narration-script`):仅口播类视频启用,用于指导 main agent 写同类型视频的口播文案;它不是独立 DNA,未启用时写「未启用」。\n- **账号运营子模块**(`account-bio`、`content-mix-cadence`):只在样本来自用户提供的对标账号(可从账号发布列表批量提取)时填写;结论只写进本 DNA 文档,不进 template;样本不足写「未观测」。" diff --git a/crews/main/skills/expert-wx-channel/workflows/content-production.md b/crews/main/skills/expert-wx-channel/workflows/content-production.md index 81f70b7a..7f45a82f 100644 --- a/crews/main/skills/expert-wx-channel/workflows/content-production.md +++ b/crews/main/skills/expert-wx-channel/workflows/content-production.md @@ -207,7 +207,7 @@ DNA 约束的是选题与观看理由、短标题与视频描述写法、内容 - 业务植入与 CTA:植入位置与方式原型 + 内容与业务的衔接句要求 + CTA 主目标与句式 - 封面要求:封面主文案(用短标题或核心传达)+ 视觉方向 - 制作规格:横屏 / 竖屏、时长带、画面风格、配音音色与声音形态、BGM 与音效、字幕 -- 口播文案:`voiceover.md` 绝对路径(口播类必填)/ 真人口播录音绝对路径 / 不适用 +- 口播文案:`voiceover.md` 绝对路径 / 真人口播录音绝对路径 / 不适用(口播 = 真人出镜、数字人、真人录音;剪辑配解说的**旁白归 CP 写**,写「不适用」) - 素材清单:逐条**绝对路径** + 来源 + 授权(无素材时写「无,由 CP 按 Brief 取材」) - 交付物与验收:`video.mp4` + `cover.jpg` + `final-deliver.md`,回报三者绝对路径;验收标准 - 闸门:GATE A / GATE B 批准人(用户或 main 代理批准 + 批准范围) @@ -223,7 +223,7 @@ Brief 硬性规则: - **不写实现路径**:Brief 只写需求与验收标准(「植入段必须动态化、禁静态贴图」「字幕逐句对齐不飘」及分辨率/帧率/时长带等交付规格),不写实现手段——❌「参考/照改某脚本」、❌ ffmpeg/引擎参数表(CRF、threads、nice、MarginV 等)、❌ 工具链内部细节;用什么工具、什么参数达成需求归 CP。例外:用户点名的工程参数照传并注明「用户指定」。发现需求必须指定脚本/参数才说得清时,改写成可观察的验收结果——那是需求没写清,不是越界的许可。 - **甲乙方关系**:需求方向、品牌事实、发布文案归 main;制作方案、分镜、渲染参数归 CP。 -2. 口播类视频:口播终稿一律由 main 写——`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写——落 `wx_channel/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。「不适用」仅限非口播类视频。用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 +2. 口播(真人出镜 / 数字人 / 真人录音)视频:口播稿一律由 main 写——`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写——落 `wx_channel/outputs//voiceover.md`,Brief 里给绝对路径;真人口播时指导用户按口播稿录音,完成后向用户取得录音文件。剪辑配解说的旁白归 CP 写,main 不出旁白稿;「不适用」即旁白类或非口播视频。用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 3. 参考模式下,把选题与创意结论写进 Brief 的「内容创意」段即可;`viral-chaser` 拆解报告是 main 的采样材料,**不作为 Brief 附件交给 CP**。 4. spawn `content-producer` 委托制作:只交 Brief 一份——素材、口播文案 / 录音均已以绝对路径写在 Brief 内;不指定 CP 的工作区与制作方案。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做(那是 CP 的基准准则,不是备选 workflow),叙事 / 动效 / 蒙太奇手法由 CP 据创意自定;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。 5. Brief 变更时更新版本并推送变更要点;已开工中间产物按新版取舍,弃用部分记入交付说明。制作中发现 Brief 无法执行(素材缺失、时长超标)时,由 CP 回报、main 与用户确认后改 Brief,CP 不擅自改策略。 diff --git a/crews/main/skills/expert-xhs/SKILL.md b/crews/main/skills/expert-xhs/SKILL.md index c130475d..bc05573c 100644 --- a/crews/main/skills/expert-xhs/SKILL.md +++ b/crews/main/skills/expert-xhs/SKILL.md @@ -50,7 +50,7 @@ DNA 存储目录是 `xhs/dna/`。未指定 DNA 时默认使用并更新 `dna-0` DNA 是**从一批作品样本提取并聚合出的内容生产规则集**:图文 10 维——选题与观看理由、标题与封面图组、内容创意、**匹配的用户问题**、正文表达与语气、图组视觉、**业务植入套路**、**互动引导与 CTA 套路**,加账号运营子模块(简介写法、内容形式比例、发布习惯);视频 11 维——前四项加**业务植入套路**、**互动引导与 CTA 套路**、视频内容形态与制作指向、制作规格与视听倾向、口播文案子模块与账号运营子模块。搜索维度是小红书必备:最大流量池来自搜索,关键词必须落到用户可能的提问原句。DNA 指导 main agent 写图文或出视频 Brief(+ 口播文案),不规定创作细节与成片制作。维度框架 v2 位于 `xhs-style-profiler` 的 `references/note-dna-framework.md` 与 `references/video-dna-framework.md`。 -**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;口播类视频的口播终稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 +**视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;**口播**(真人出镜 / 数字人 / 真人录音)的口播稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写;**旁白**(剪辑配的解说)完全由 CP 写,main 不出旁白稿。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 ## 数据与记录 diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md index da8f8077..36196b98 100644 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/references/video-dna-framework.md @@ -22,7 +22,7 @@ DNA 文档 -> DNA template |--------|----------| | 选题与观看理由、标题与封面写法、内容创意原型、匹配的用户问题(搜索流量)、业务植入套路、互动引导与 CTA 套路、视频内容形态与制作指向、制作规格与视听倾向、(口播类)口播文案子DNA、(对标账号)账号运营子模块 | 创作细节、脚本结构、逐句台词、镜头表、转场与编码参数——那些归 Content Producer | -视频 DNA 的用途是指导 main agent 出具 **Brief.md** 与(口播类的)**口播文案**。template 就是 Brief 正文模板 + 口播文案模板(可选),不是成片制作模板。账号运营子模块(简介写法、内容形式比例、发布习惯)只写进 DNA 文档,**不进 template**。 +视频 DNA 的用途是指导 main agent 出具 **Brief.md** 与**口播**(真人出镜 / 数字人)的**口播文案**;旁白(剪辑配的解说)由 CP 写,不在 DNA 指导范围。template 就是 Brief 正文模板 + 口播文案模板(可选),不是成片制作模板。账号运营子模块(简介写法、内容形式比例、发布习惯)只写进 DNA 文档,**不进 template**。 ## 维度(11 维) @@ -104,7 +104,7 @@ DNA template = **Brief.md 正文模板 + 口播文案模板(可选)**,固 | DNA 观测到的内容形态 | 制作指向(只能写真实存在的资源名) | Brief `workflow` 字段值 | |---------------------|-----------------------------------|------------------------| | 影视解说 / 剧情解说 + 反转植入(「万万没想到」式) | Content Producer `expert-video` → **Reversal Ad** workflow | `reversal-ad` | -| 口播类(真人口播出镜,或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | +| 口播 / 旁白类(真人口播或数字人出镜;或旁白 + 画面) | Content Producer `expert-video` → **Narration Video** workflow | `narration-video` | | 一句文稿转视觉隐喻的纸拼贴动画 | Content Producer `expert-video` → **Collage B-roll** workflow | `collage-broll` | | 纯 AIGC 动画 / 剧情短片 / 蒙太奇拼接(需从零出脚本分镜) | Content Producer `expert-video` → **不指定类型 workflow**:CP 按其通用制作流程做,据创意自定叙事 / 动效 / 蒙太奇手法 | 省略 | | 已有素材简单拼接、加旁白、烧字幕 | main `video-edit`(不委托 CP) | — | diff --git a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py index 73072216..d6226e85 100755 --- a/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py +++ b/crews/main/skills/expert-xhs/tools/xhs-style-profiler/scripts/build_style_profile.py @@ -146,7 +146,7 @@ } TEMPLATE_CHECKLISTS = { - "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类是否附口播终稿(`voiceover.md` 绝对路径)或真人口播录音路径——无论口播子模块是否启用;「不适用」仅限非口播类视频。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 关键词是否落到用户可能的提问原句(不只平台标签),并与内容形式匹配。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", + "video": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题 / 描述、封面是否来自 DNA 文档。\n- 内容创意是否落到可复用的创意原型,而不是照抄样本主题。\n- 视频形态是否明确指向 Content Producer `expert-video` 的某个 workflow,或 main 的某个素材加工技能。\n- Brief 是否只含制作所需信息(不含 DNA 内容),素材是否给了绝对路径与授权说明。\n- 口播类(真人出镜 / 数字人 / 真人录音)是否附口播终稿(`voiceover.md` 绝对路径)或真人录音路径——无论口播子模块是否启用;剪辑配解说的旁白归 CP 写,Brief 口播字段写「不适用」。\n- 制作规格(横竖屏、时长带、画面风格、配音音色)是否尊重样本覆盖度;样本不足时是否标注未观测。\n- 关键词是否落到用户可能的提问原句(不只平台标签),并与内容形式匹配。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「自然植入」),CTA 是否只有一个主行动、句式可执行且未越合规红线。\n- 用户输入是否已转译为具体执行规则。", "note": "- 是否只用一个 DNA,且作品类型与该 DNA 的 `kind` 一致。\n- 选题、标题与封面图组是否来自 DNA 文档。\n- 正文表达的每条规则是否可从 DNA 文档推导,未使用空泛形容词。\n- 图组数量、构图与视觉风格是否与 DNA 一致;视觉结论是否有图片证据。\n- 业务植入是否落到位置 + 载体 + 衔接句(而不是只写「软性推荐」),CTA 是否每篇只放一个主行动、句式可执行且未越合规红线。\n- 关键词是否落到用户可能的提问原句(不只平台标签),并与内容形式匹配。\n- 用户输入是否已转译为具体执行规则。", } diff --git a/crews/main/skills/expert-xhs/workflows/content-production.md b/crews/main/skills/expert-xhs/workflows/content-production.md index 9a3e69f1..5d154cf2 100644 --- a/crews/main/skills/expert-xhs/workflows/content-production.md +++ b/crews/main/skills/expert-xhs/workflows/content-production.md @@ -171,7 +171,7 @@ DNA template 是 main agent 的生产输入模板:**图文 DNA 的 template = - 标题与简介:笔记标题、发布正文、话题标签(main 定稿) - 封面要求:封面主文案 + 视觉方向 - 制作规格:横屏 / 竖屏、时长带、画面风格、配音音色与声音形态、BGM 与音效、字幕 -- 口播文案:`voiceover.md` 绝对路径(口播类必填)/ 真人口播录音绝对路径 / 不适用 +- 口播文案:`voiceover.md` 绝对路径 / 真人口播录音绝对路径 / 不适用(口播 = 真人出镜、数字人、真人录音;剪辑配解说的**旁白归 CP 写**,写「不适用」) - 素材清单:逐条**绝对路径** + 来源 + 授权(无素材时写「无,由 CP 按 Brief 取材」) - 交付物与验收:`video.mp4` + `cover.jpg` + `final-deliver.md`,回报三者绝对路径;验收标准 - 闸门:GATE A / GATE B 批准人(用户或 main 代理批准 + 批准范围) @@ -187,9 +187,9 @@ Brief 硬性规则: ### 口播文案规则 -- **口播类视频的口播终稿一律由 main agent 出**:`narration-script`(口播文案子模块)启用时按其结构写,未启用时按用户要求与 Brief 核心传达写,保存为作品目录下的 `voiceover.md`,在 Brief 中给**绝对路径**。CP 不重写,只做声画实现。 +- **口播(真人出镜 / 数字人 / 真人录音)的口播稿一律由 main agent 出**:`narration-script`(口播文案子模块)启用时按其结构写,未启用时按用户要求与 Brief 核心传达写,保存为作品目录下的 `voiceover.md`,在 Brief 中给**绝对路径**。CP 不重写,只做声画实现;剪辑配解说的**旁白归 CP 写**,main 不出旁白稿。 - **真人口播**:明确要用用户真人声音时,指导用户按口播稿录音,完成后向用户取得录音文件,落到作品目录并在 Brief 中给绝对路径。 -- **口播子模块未启用**:口播终稿仍由 main 写——按用户要求与 Brief 的核心传达 / CTA 要求直接撰写(不依赖 DNA 子模块),落 `voiceover.md` 并在 Brief 给绝对路径;「不适用」仅限非口播类视频。 +- **口播子模块未启用**:口播终稿仍由 main 写——按用户要求与 Brief 的核心传达 / CTA 要求直接撰写(不依赖 DNA 子模块),落 `voiceover.md` 并在 Brief 给绝对路径;「不适用」即旁白类(剪辑配解说归 CP 写)或非口播视频。 - 用户必用的事实、案例、承诺和 CTA 必须进入 Brief 或口播终稿,不得为了形式删除关键事实。 ## 【确认】正文 / Brief From 8511c792d662293e9924cddcd1192c07e85948a8 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 16:48:26 +0800 Subject: [PATCH 11/25] =?UTF-8?q?refactor(expert-video):=20type=20workflow?= =?UTF-8?q?=20=E9=87=8D=E6=96=B0=E5=AE=9A=E4=BD=8D=E2=80=94=E2=80=94script?= =?UTF-8?q?=20=E7=94=9F=E4=BA=A7=E6=8C=87=E5=8D=97=EF=BC=88=E5=90=AB?= =?UTF-8?q?=E8=87=AA=E6=A3=80=EF=BC=89=EF=BC=8C=E5=BA=9F=E5=BC=83=E3=80=8C?= =?UTF-8?q?=E5=8F=A0=E5=8A=A0=E7=BB=86=E5=8C=96=E5=B1=82=E3=80=8D=E6=97=A7?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户第三次澄清架构:三个 type workflow(reversal-ad / narration-video / collage-broll) 的定义是**指导从 Brief 生产 script,含这一步之后的自检**(GATE A 质检标准), 不是叠加在通用制作流程上的「细化层」(旧定义是设计不清晰时期的产物); story-develop 维持已回正口径——没有 Brief 或 Brief 不清晰时与甲方探讨收敛 Brief。 - SKILL.md:三层表 type 类定义、底座 blockquote、类型 workflow 表标题与列头、 workflow 采用规则(「按它生产 script 并按它自检」)、通用流程引言与制作约定条款, 全部改为 Stage 1–2(Brief→script→自检)类型化指导的口径 - AGENTS.md:铁律段「叠加在基准上的进一步细化」废弃,路由表四行改 「按 生产 script 与自检」 - 三个 type workflow 头部:各自重述为「指导从 Brief 生产该类型 script 及自检, 并附该类型制作与验收约定」;story-develop 对 type workflow 的旁述同步 - video-producer.sh help 同步 workflow 文档中的类型制作约定(阶段裁剪 / 素材 sourcing / 验收清单)内容不动, 定位为 GATE A 批准后按通用流程执行时适用的类型约定 --- crews/content-producer/AGENTS.md | 10 +++++----- .../content-producer/skills/expert-video/SKILL.md | 14 +++++++------- .../tools/video-producer/video-producer.sh | 2 +- .../skills/expert-video/workflows/collage-broll.md | 2 +- .../expert-video/workflows/narration-video.md | 2 +- .../skills/expert-video/workflows/reversal-ad.md | 2 +- .../skills/expert-video/workflows/story-develop.md | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crews/content-producer/AGENTS.md b/crews/content-producer/AGENTS.md index 0af3fdd8..df7448f6 100644 --- a/crews/content-producer/AGENTS.md +++ b/crews/content-producer/AGENTS.md @@ -11,14 +11,14 @@ ## 能力方向路由 -**视频类铁律**:只要接的是视频制作活儿,`expert-video` 的**通用制作流程**(Stage 0→15 阶段链 + GATE A/B 两闸门 + 护栏 + 工作区与交付约定)**一律适用**——它是基准准则,不是"没匹配到类型时的备选",也不与类型 workflow 并列。下表匹配到的类型 workflow 只是叠加在基准上的进一步细化。 +**视频类铁律**:只要接的是视频制作活儿,`expert-video` 的**通用制作流程**(Stage 0→15 阶段链 + GATE A/B 两闸门 + 护栏 + 工作区与交付约定)**一律适用**——它是基准准则,不是"没匹配到类型时的备选",也不与类型 workflow 并列。类型 workflow 只负责其中 **script 生产(Brief→script→自检→GATE A 质检)** 的类型化指导,并附该类型的制作与验收约定——不接管、不裁掉流程本身。 | 入口信号 | 专家包 | 怎么做 | |---------|--------|--------| -| Brief 指定 `workflow`(如 `reversal-ad`) | `expert-video` | 通用制作流程 + 读 Brief 指定的 `workflows/<值>.md`,按其阶段裁剪执行 | -| 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | `expert-video` | 通用制作流程 + Reversal Ad 细化 | -| 口播(真人出镜 / 数字人 / 真人录音,甲方出口播稿)或旁白(TTS 配音解说,稿由 CP 写)要合成声画 | `expert-video` | 通用制作流程 + Narration Video 细化 | -| "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | `expert-video` | 通用制作流程 + Collage B-roll 细化 | +| Brief 指定 `workflow`(如 `reversal-ad`) | `expert-video` | 通用制作流程 + 读 Brief 指定的 `workflows/<值>.md`,按它生产 script 并按它自检 | +| 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | `expert-video` | 通用制作流程 + 按 reversal-ad 生产 script 与自检 | +| 口播(真人出镜 / 数字人 / 真人录音,甲方出口播稿)或旁白(TTS 配音解说,稿由 CP 写)要合成声画 | `expert-video` | 通用制作流程 + 按 narration-video 生产 script 与自检 | +| "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | `expert-video` | 通用制作流程 + 按 collage-broll 生产 script(隐喻清单)与自检 | | "从零做视频""出一支完整视频""按这个主题拍片子"(无匹配类型) | `expert-video` | 只按通用制作流程走(叙事 / 动效 / 蒙太奇手法由我据创意自定);Brief 缺失或创意不清时先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write` | | 已有素材要剪辑、修整、拼接、配音、烧字幕 | `expert-video` | 通用制作流程的 Stage 12 工具箱(只做几何级修整) | | "做网页/落地页/APP 界面/品牌视觉体系" | `expert-design` | Web Page / App UI / Brand Visual | diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index fceefbad..21db1d4b 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -56,22 +56,22 @@ metadata: | 层 | 是什么 | 怎么用 | |----|--------|--------| | **通用制作流程**(本文下方) | 我做**任何**视频制作工作都必须遵循的准则:Stage 0→15 阶段链、GATE A / GATE B 两闸门、返工与耗时上限、决策审计链、工作区与交付约定 | 永远适用,不因视频类型而跳过或替换 | -| **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:Stage 0 创意模糊时与甲方对话收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:在通用制作流程之上细化某类视频的阶段裁剪、叙事套路、声音 / 画面规范、验收) | type 类:Brief 指定 `workflow` 时必读必用,冲突时以 workflow 为准但**闸门与护栏不让步**;intake 类:Brief 缺失或创意不足以直接写剧本时触发 | +| **workflow**(`workflows/*.md`) | 两类——**intake 类**(`story-develop`:没有 Brief 或 Brief 不清晰时与甲方探讨收敛 Brief,**不是 `Brief.workflow` 取值**);**type 类**(`narration-video` / `collage-broll` / `reversal-ad`:**指导从 Brief 生产该类型的 script,含这一步之后的自检与 GATE A 质检标准**;并附该类型的制作与验收约定,GATE A 批准后按通用流程执行时适用) | type 类:Brief 指定 `workflow` 时必读必用,按它生产 script、按它自检;intake 类:Brief 缺失或创意不足以直接写剧本时触发 | | **工具说明**(`tools/<工具>/SKILL.md`) | 每个子命令的入参、产物路径、退出码与旁路条件 | 调用前查;本文不重复参数细节 | -> 通用制作流程**不是**与类型 workflow 并列的第四条路,也**不是**"Brief 没指定类型时的 fallback"。它是底座;类型 workflow 只在底座上细化,产出特定类型的视频。 +> 通用制作流程**不是**与类型 workflow 并列的一条路,也**不是**"Brief 没指定类型时的 fallback"。它是我做任何视频都必走的全程流程;类型 workflow 只负责其中 **Stage 1–2(Brief→script→自检)** 这一段的类型化指导,并附该类型的制作约定——不接管、不裁掉流程本身。 命名约定:Workflow 名与 Tool 名是包内**逻辑资源名**,不是 Workspace 路径,不要拼成相对路径执行;只有工具清单里列出的 wrapper 名能直接当 shell 命令调用。`output_videos/`、`design_assets/` 才是 Workspace 相对路径(从 Content Producer workspace 根解析)。 -## 类型 workflow(细化层) +## 类型 workflow(script 生产指南) -| 视频类型 / 入口信号 | workflow | Brief `workflow` 值 | 它细化了什么 | +| 视频类型 / 入口信号 | workflow | Brief `workflow` 值 | 它指导什么 | |--------------------|----------|---------------------|--------------| | 影视解说 / 剧情解说 + 突然反转插入品宣("万万没想到"式) | Reversal Ad | `reversal-ad` | 三段结构占比、反转点落在 55%–76%、四种反转手法、反转幅度与接入丝滑度两轴(含二次反转 CTA)、素材三模式 sourcing(Blender 片库 / 用户直供三查 / AIGC)、意象桥与钩连句、植入段约束 | | 口播 / 旁白类(口播:真人出镜、数字人、真人录音;旁白:TTS 配音解说) | Narration Video | `narration-video` | 口播稿落稿锁定不重写、旁白稿由我写 GATE A 交审、按字级时间戳配画面、声音规范与验收清单 | | "把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴" | Collage B-roll | `collage-broll` | 隐喻清单即 script(GATE A 检)→ 静帧即素材(GATE B 检 contact sheet)→ Stage 10 `collage-broll render` 批量 i2v 组装;阶段裁剪表见 workflow | -- Brief 指定了 `workflow`:**先读对应文档并直接采用**,不得替换成自创流程。 +- Brief 指定了 `workflow`:**先读对应文档,按它生产 script 并按它自检**(GATE A 质检标准同此),不得替换成自创流程;其制作与验收约定(阶段裁剪、素材 sourcing 等)在该类型视频上生效。 - Brief 未指定 `workflow`:仍走通用制作流程,叙事 / 动效 / 蒙太奇的处理手法由我据创意自定并记 `decisions.json`;Brief 创意不足以直接写剧本时,先走 `story-develop` intake workflow 与甲方收敛 Brief,再进 Stage 1 `script-write`。 - 已有素材只要剪辑、修整、拼接、配音、烧字幕:仍走通用制作流程,中间阶段按实际裁剪,重心落在 Stage 12 工具箱(只做几何级修整;语义级高光剪辑归甲方 main)。 @@ -112,7 +112,7 @@ workflow 文档在技能包内,不是项目目录内容;项目目录只放 B ## 通用制作流程(Stage 0→15,两闸门) -**我做任何视频都走这条链**;类型 workflow 只在此基础上裁剪与细化。每段的子命令是 `video-producer` 工具下的一个独立脚本,按流程逐个调。 +**我做任何视频都走这条链**;类型 workflow 只指导其中 **Stage 1–2(Brief→script→自检)** 的类型化生产,并附该类型的制作约定,不裁掉流程本身。每段的子命令是 `video-producer` 工具下的一个独立脚本,按流程逐个调。 ``` Stage 0 Brief intake 读甲方 Brief,核对字段,缺口向 Brief owner 澄清; @@ -147,7 +147,7 @@ Stage 15 交付 回报成片 + 封面 + final-deliver.md 的绝对 ``` - **产物文件存在性即 checkpoint**:子命令先查产物文件是否存在,存在则 load 不重生成(允许手改 JSON 后续跑);要改哪段就重跑对应子命令,未改的不会重生成。 -- 类型 workflow 指定时,阶段裁剪以该 workflow 文档为准;**闸门位置与"停下发甲方"的纪律不变**。甲方已在 Brief 中代理批准某道闸门时,把批准范围落 `gates/` 后继续。 +- 类型 workflow 附带的制作约定(阶段裁剪、素材 sourcing、验收清单)在该类型视频上生效;**闸门位置与"停下发甲方"的纪律不变**。甲方已在 Brief 中代理批准某道闸门时,把批准范围落 `gates/` 后继续。 - 可选工具 `reference-concepts`:甲方给了参考视频拆解报告时,据报告出 2–3 个差异化概念落 `reference/concepts.md`。 ## 闸门与护栏 diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh index 298a57a9..f9a55f3a 100755 --- a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh +++ b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh @@ -24,7 +24,7 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 流程: 通用制作流程(expert-video SKILL.md 的 Stage 0→15 + 两闸门)是做**任何**视频都要遵循的基准, 不是"没指定类型时的备选"。Brief 指定 workflow 时,先读包内 workflows/.md, - 按其阶段裁剪调用下列子命令;未指定时只按通用制作流程走。Brief 创意不足以直接写剧本时, + 按它生产 script 并按它自检(其制作约定在该类型上生效);未指定时只按通用制作流程走。Brief 创意不足以直接写剧本时, 先走 story-develop intake workflow(workflows/story-develop.md)与甲方收敛 Brief,再进 script-write。 子命令(按阶段序): diff --git a/crews/content-producer/skills/expert-video/workflows/collage-broll.md b/crews/content-producer/skills/expert-video/workflows/collage-broll.md index c9d55d69..11f0cb8c 100644 --- a/crews/content-producer/skills/expert-video/workflows/collage-broll.md +++ b/crews/content-producer/skills/expert-video/workflows/collage-broll.md @@ -1,6 +1,6 @@ # Workflow:Collage B-roll(纸拼贴组装动画) -Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴"时使用。本文是**通用制作流程在纸拼贴 B-roll 上的细化**:方法论移植自 gbro-collage-broll(半调纸拼贴 + assemble-from-empty),阶段链按下表裁剪,闸门收敛为 GATE A / GATE B 两道——**GATE A 检隐喻清单(即本类型的 script),GATE B 检静帧 contact sheet(即素材)**。不替代通用流程;原子能力、护栏、工作区与交付约定照 `expert-video` 的 SKILL.md 执行;本文与通用流程冲突处以本文为准,但闸门与护栏不让步。 +Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼贴 B-roll""纸拼贴动画""半调拼贴"时使用。本文指导**从 Brief 生产纸拼贴 B-roll 的 script**(隐喻清单)及这一步之后的自检(隐喻自检,GATE A 质检标准),并附该类型的制作约定(方法论移植自 gbro-collage-broll:半调纸拼贴 + assemble-from-empty);闸门收敛为 GATE A / GATE B 两道——**GATE A 检隐喻清单(即本类型的 script),GATE B 检静帧 contact sheet(即素材)**。不替代通用流程;原子能力、护栏、工作区与交付约定照 `expert-video` 的 SKILL.md 执行;本文与通用流程冲突处以本文为准,但闸门与护栏不让步。 ## 类型定义 diff --git a/crews/content-producer/skills/expert-video/workflows/narration-video.md b/crews/content-producer/skills/expert-video/workflows/narration-video.md index d8cb3900..f275a4e8 100644 --- a/crews/content-producer/skills/expert-video/workflows/narration-video.md +++ b/crews/content-producer/skills/expert-video/workflows/narration-video.md @@ -1,6 +1,6 @@ # Workflow:Narration Video(口播类视频) -Brief 里写 `workflow: narration-video`,或 Brief 交付了口播文案 / 真人口播录音时使用。本文是**通用制作流程在口播类视频上的细化**:声画实现套路、阶段裁剪与验收补充,不替代通用流程——阶段链、GATE A/B 闸门纪律、护栏、工作区与交付约定一律照 `expert-video` 的 SKILL.md 执行;本文与通用流程冲突处以本文为准,但闸门与护栏不让步。 +Brief 里写 `workflow: narration-video`,或 Brief 交付了口播文案 / 真人口播录音时使用。本文指导**从 Brief 生产口播 / 旁白类视频的 script**(口播形态落稿锁定、旁白稿由我写)及这一步之后的自检(GATE A 质检标准),并附该类型的声画实现约定与验收清单——阶段链、GATE A/B 闸门纪律、护栏、工作区与交付约定一律照 `expert-video` 的 SKILL.md 执行;本文与通用流程冲突处以本文为准,但闸门与护栏不让步。 ## 类型定义 diff --git a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md index e4479c7d..272abf65 100644 --- a/crews/content-producer/skills/expert-video/workflows/reversal-ad.md +++ b/crews/content-producer/skills/expert-video/workflows/reversal-ad.md @@ -1,6 +1,6 @@ # Workflow:Reversal Ad(「万万没想到」式反转植入) -Brief 里写 `workflow: reversal-ad` 时使用。本文是**通用制作流程在反转植入类视频上的细化**:三段结构、反转手法、素材与口播约束、阶段裁剪,不替代通用流程——原子能力、GATE A/B 闸门纪律、护栏、工作区与交付约定一律照 `expert-video` 的 SKILL.md 执行;本文与通用流程冲突处以本文为准,但闸门与护栏不让步。 +Brief 里写 `workflow: reversal-ad` 时使用。本文指导**从 Brief 生产反转植入类视频的 script**(三段结构、反转手法、解说旁白规范)及这一步之后的自检(GATE A 四问质检标准),并附该类型的素材与制作约定——原子能力、GATE A/B 闸门纪律、护栏、工作区与交付约定一律照 `expert-video` 的 SKILL.md 执行;本文与通用流程冲突处以本文为准,但闸门与护栏不让步。 ## 类型定义 diff --git a/crews/content-producer/skills/expert-video/workflows/story-develop.md b/crews/content-producer/skills/expert-video/workflows/story-develop.md index 3a126eab..58145784 100644 --- a/crews/content-producer/skills/expert-video/workflows/story-develop.md +++ b/crews/content-producer/skills/expert-video/workflows/story-develop.md @@ -1,6 +1,6 @@ # Workflow:Story Develop(Brief intake · 创意澄清) -**这是 intake 类 workflow,不是 type 类 workflow。** type workflow(`narration-video` / `collage-broll` / `reversal-ad`)细化"某类视频怎么制作",是 `Brief.workflow` 的取值;本 workflow 解决"甲方还没把创意讲清楚时,怎么和他对话把 Brief 收敛出来",**不是 `Brief.workflow` 的取值**,也不与 type workflow 互斥。它在 **Stage 0(Brief intake)** 阶段触发,与任何 type workflow 正交可组合——收敛出 Brief 后,仍按通用制作流程 + 对应 type workflow 执行。 +**这是 intake 类 workflow,不是 type 类 workflow。** type workflow(`narration-video` / `collage-broll` / `reversal-ad`)指导从 Brief 生产 script(含自检),是 `Brief.workflow` 的取值;本 workflow 解决"甲方还没把创意讲清楚时,怎么和他对话把 Brief 收敛出来",**不是 `Brief.workflow` 的取值**,也不与 type workflow 互斥。它在 **Stage 0(Brief intake)** 阶段触发,与任何 type workflow 正交可组合——收敛出 Brief 后,仍按通用制作流程 + 对应 type workflow 执行。 ## 何时触发 From ad57833bc7a08c0d7df0e11d86966343265fbc33 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 17:49:59 +0800 Subject: [PATCH 12/25] =?UTF-8?q?feat(video-producer):=20=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E9=93=BE=E8=84=9A=E6=9C=AC=20workflow=20=E6=84=9F?= =?UTF-8?q?=E7=9F=A5=E2=80=94=E2=80=94script-write/self-eval=20=E6=8C=89?= =?UTF-8?q?=E5=8F=98=E4=BD=93=E8=90=BD=E6=A8=A1=E6=9D=BF=EF=BC=8Ccollage-b?= =?UTF-8?q?roll=20=E8=A3=81=E5=89=AA=E9=98=B6=E6=AE=B5=E5=8A=A0=E5=AE=88?= =?UTF-8?q?=E5=8D=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 type workflow 新定义(指导 Brief→script 生产,含自检)把脚本层对齐: - 新增 _brief.py 共享助手:解析 Brief 的 workflow 字段(含模板未填 / 省略标注 → None)与口播交付形态(voiceover.md / 录音 / 旁白), 及 collage_guard 阶段裁剪守卫 - script-write.py 重写:按 workflow 变体落对应 script 模板—— narration-video+口播稿 → 原样落稿锁定(脚本直接逐字拷入,不重写); +真人录音 → 排布计划;+旁白 → 旁白稿(由我写 GATE A 交审); reversal-ad → 三段结构剧本(附 GATE A 四问提示);collage-broll → 隐喻清单;未指定 → 通用分场剧本。[next] 提示全部 workflow 感知 - script-self-eval.py 重写:自检维度按 workflow 变体——reversal-ad GATE A 四问+闭环合规、collage-broll 隐喻自检五条、narration-video 落稿锁定模式(只检查不改写)/旁白稿规范/录音排布、通用五维; stub 记 workflow 与 mode 字段 - 10 个被 collage-broll 裁剪的链式脚本(storyboard-build / shot-decompose / character-register / slot-plan / asset-resolve / slideshow-risk / delivery-promise-lock / render-shot / mix-audio / assemble)加守卫:collage-broll 项目调用时打印 [skip] 指引并退出 0, 指向静帧生成 / collage-broll render,不再误导 agent 走 storyboard 链 - duck.py:gen.py 旧脚本名残留改 aigc-video-gen 实测六种变体(reversal-ad / narration-video 三形态 / collage-broll / 模板未填)+ 守卫触发与不触发路径,行为全部符合预期 --- .../tools/video-producer/scripts/_brief.py | 85 +++++++++ .../tools/video-producer/scripts/assemble.py | 4 + .../video-producer/scripts/asset-resolve.py | 4 + .../scripts/character-register.py | 4 + .../scripts/delivery-promise-lock.py | 4 + .../tools/video-producer/scripts/duck.py | 4 +- .../tools/video-producer/scripts/mix-audio.py | 4 + .../video-producer/scripts/render-shot.py | 4 + .../scripts/script-self-eval.py | 92 +++++++-- .../video-producer/scripts/script-write.py | 177 ++++++++++++++---- .../video-producer/scripts/shot-decompose.py | 4 + .../video-producer/scripts/slideshow-risk.py | 4 + .../tools/video-producer/scripts/slot-plan.py | 4 + .../scripts/storyboard-build.py | 4 + 14 files changed, 345 insertions(+), 53 deletions(-) create mode 100644 crews/content-producer/skills/expert-video/tools/video-producer/scripts/_brief.py diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/_brief.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/_brief.py new file mode 100644 index 00000000..2337f0c8 --- /dev/null +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/_brief.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""_brief.py — Brief 字段解析共享助手(video-producer 流程链子命令用)。 + +从项目 brief.md 解析 workflow 字段与口播交付形态,供 script-write / +script-self-eval 做 workflow 感知,供各阶段脚本做 collage-broll 阶段裁剪守卫。 + +Brief 字段格式见 main 侧各平台 content-production 的 Brief 模板 +("- workflow:<值>",中文冒号;口播文案行给 voiceover.md 绝对路径 / 录音路径 / 不适用)。 +""" + +from __future__ import annotations + +import re +from pathlib import Path + +TYPE_WORKFLOWS = {"reversal-ad", "narration-video", "collage-broll"} + +_AUDIO_EXTS = {".mp3", ".wav", ".m4a", ".aac", ".flac", ".ogg", ".amr"} + + +def read_brief(project: Path) -> str: + brief = Path(project) / "brief.md" + if not brief.is_file(): + return "" + return brief.read_text(encoding="utf-8") + + +def parse_workflow(brief_text: str) -> str | None: + """解析 Brief 的 workflow 字段。 + + 返回 'reversal-ad' / 'narration-video' / 'collage-broll'; + 未指定、模板未填(枚举行原样 / 省略标注)或无 Brief → None。 + """ + for line in brief_text.splitlines(): + m = re.match(r"^-\s*workflow\s*[::]\s*(.+)$", line.strip()) + if not m: + continue + val = m.group(1).strip().strip("`\"'") + # 模板未填(多值枚举行原样)或明确省略 → 未指定 + if "省略" in val or "未指定" in val or val.startswith("reversal-ad /"): + return None + if val in TYPE_WORKFLOWS: + return val + # 单值带尾注(如 "reversal-ad(…)") + head = val.split("(")[0].split("(")[0].strip() + return head if head in TYPE_WORKFLOWS else None + return None + + +def detect_voiceover(project: Path, brief_text: str) -> tuple[str | None, Path | None]: + """解析口播交付形态(口播 = 真人出镜 / 数字人 / 真人录音)。 + + 返回 ('voiceover', Path) / ('recording', Path) / (None, None)。 + 都没有 → 旁白形态(TTS 配音解说,文稿由 CP 写)。 + """ + project = Path(project) + cand = project / "voiceover.md" + if cand.is_file(): + return "voiceover", cand + for line in brief_text.splitlines(): + if "口播" not in line: + continue + for tok in re.findall(r"[\w./\-]+", line): + p = Path(tok) + if not p.suffix: + continue + if p.suffix == ".md" and "voiceover" in p.stem.lower() and p.is_file(): + return "voiceover", p + if p.suffix.lower() in _AUDIO_EXTS and p.is_file(): + return "recording", p + return None, None + + +def collage_guard(project: Path, stage: str) -> bool: + """collage-broll 阶段裁剪守卫。 + + Brief 指定 collage-broll 且本阶段被其裁剪时,打印跳过指引并返回 True + (caller 应据此 return,退出码 0)。其余情况返回 False,照常执行。 + """ + if parse_workflow(read_brief(project)) != "collage-broll": + return False + print(f"[skip] collage-broll 类型裁剪本阶段({stage})——阶段裁剪表见 workflows/collage-broll.md:") + print(" Stage 3–9 由静帧生成(Phase 2)替代;视频生成走 `collage-broll render`(Phase 3);") + print(" 默认无声、逐条独立成片不经拼接。按 workflow 文档执行,不要调用本命令。") + return True diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/assemble.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/assemble.py index 339d40d7..ef35dd9b 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/assemble.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/assemble.py @@ -32,6 +32,8 @@ import sys from pathlib import Path +import _brief + VALID_TRANSITIONS = {"hard", "fade", "dissolve", "xfade"} XFADE_TYPES = {"fade": "fade", "dissolve": "dissolve", "xfade": "fade"} @@ -412,6 +414,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 12 assemble"): + return source_dir = project / args.source_dir if args.source_dir else project / "render" preset, crf = encode_opts(args.low_memory) audio_sr, audio_ch = parse_audio_format(args.audio_format) diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py index 605af479..cdedec71 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/asset-resolve.py @@ -29,6 +29,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -43,6 +45,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 7 asset-resolve"): + return plan_path = project / "slots" / "slot-plan.json" if not plan_path.is_file(): die(f"前置缺失: slot-plan.json 不存在") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py index eacb6e5e..c15c2197 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py @@ -21,6 +21,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -33,6 +35,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 5 character-register"): + return decompose_path = project / "storyboard" / "shot_decompose.json" script_path = project / "script" / "script.md" if not decompose_path.is_file() or not script_path.is_file(): diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py index b92f1803..245cd7c8 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/delivery-promise-lock.py @@ -25,6 +25,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -37,6 +39,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 9 delivery-promise-lock"): + return board_path = project / "storyboard" / "storyboard.json" if not board_path.is_file(): die(f"前置缺失: storyboard.json 不存在") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/duck.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/duck.py index 80f0fd43..4e373685 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/duck.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/duck.py @@ -2,13 +2,13 @@ """BGM ducking — narration/dialog drives BGM auto-ducking via sidechain. 把 BGM 轨在旁白/对话出现时自动压低,旁白停了再放开——专业混音的标配。 -只在声画同出模式(gen.py 出的片旁白+BGM 同轨)且用户要专业混音时用。 +只在声画同出模式(aigc-video-gen 出的片旁白+BGM 同轨)且用户要专业混音时用。 ⚠️ 可选步骤,不是必跑。通用制作流程默认不做混音处理—— assemble.py / normalize.py 都只碰整体响度,不动轨间电平。 **仅当用户明确说"要混音"/"做 ducking"/"BGM 压旁白"/"professional mix"时才跑**。 -前置:要有可分离的 BGM 轨和旁白轨。AI 声画同出模式 gen.py 出的片是 +前置:要有可分离的 BGM 轨和旁白轨。AI 声画同出模式 aigc-video-gen 出的片是 **混轨单声道**——duck.py 没法从混轨里分离 BGM 和旁白。所以本脚本实际 只在以下场景能用: 1. assemble.py 走 Stock Footage + TTS 模式:素材视频(含 BGM/环境音)+ diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py index d7958b7f..01eeec67 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/mix-audio.py @@ -42,6 +42,8 @@ import sys from pathlib import Path +import _brief + def main() -> None: parser = argparse.ArgumentParser(description="Stage 11 mix-audio") @@ -49,6 +51,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 11 mix-audio"): + return script_path = project / "script" / "script.md" if not script_path.is_file(): die(f"前置缺失: script.md 不存在") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py index 1a11c31b..fe8f05e0 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py @@ -22,6 +22,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -36,6 +38,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 10 render-shot"): + return decompose_path = project / "storyboard" / "shot_decompose.json" resolve_path = project / "slots" / "asset-resolve.json" if not decompose_path.is_file() or not resolve_path.is_file(): diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py index e108932e..9d44f364 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-self-eval.py @@ -1,20 +1,20 @@ #!/usr/bin/env python3 -"""Stage 2 — script-self-eval:剧本自评 N 维打分,任一维 <3 必返工。 +"""Stage 2 — script-self-eval:按 workflow 的自检标准给 script 打分。 Usage: python3 scripts/script-self-eval.py -入:project_dir/script/script.md(Stage 1) +入:project_dir/script/script.md(Stage 1)+ project_dir/brief.md(读 workflow) 出:project_dir/script/self-eval.json(N 维分 1–5 + 总评 + 是否必返工) -N 维(硬约束五条): -1. 可拍化:无不可见物描写 -2. 场次划分:同时间同地点一场 -3. 对白格式:引号统一 -4. enhancement_cues:六型齐 -5. delivery_cues:语气/语速/重音齐 +自检维度按 Brief 的 workflow 变体(脚手架据此落对应模板): +- 未指定 workflow → 通用五维(可拍化 / 场次 / 对白格式 / cues 齐) +- reversal-ad → GATE A 四问 + 叙事闭环 + 合规 +- narration-video → 口播稿落稿锁定模式(只检查不改写)或旁白稿规范 +- collage-broll → 隐喻自检五条 agent 据此逐维打分填 self-eval.json。脚本不做 NLP 判分——是 agent 的自检脚手架。 +任一维 <3 必返工;落稿锁定模式只检查不改写,问题报甲方。 """ import argparse @@ -22,13 +22,15 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) sys.exit(1) -EVAL_DIMS = [ +GENERIC_DIMS = [ ("filmable", "可拍化", "无不可见物描写('想起了'/'觉得'改外化动作)"), ("scene_split", "场次划分", "同时间同地点一场"), ("dialog_format", "对白格式", "引号「」统一"), @@ -36,9 +38,44 @@ def die(msg: str) -> None: ("delivery_cues", "delivery_cues 齐", "语气/语速/重音/情感控制"), ] +REVERSAL_DIMS = [ + ("setup_credible", "铺垫可信", "反转点之前观众会认真相信这是纯内容视频(零广告感)"), + ("twist_magnitude", "反转幅度", "反转瞬间能让观众脱口而出「万万没想到」(题材/身份/场景/情绪跳得够远)"), + ("twist_smoothing", "接入丝滑", "接入句因果一句话可复述(自问自答/因果链/意象复用,非硬切)"), + ("cta_story", "CTA 剧情化", "设二次反转时 CTA 动机由剧情给出(未设则记 5 并注明未设)"), + ("narrative_loop", "叙事单线闭环", "节拍 ≤6、句间因果可追、无断头线;意象桥成立(产品段第一镜复用正文核心意象)"), + ("compliance", "合规", "正片零 URL/域名/联系方式,无收益承诺,产品能力不越 Brief"), +] + +NARRATION_LOCK_DIMS = [ + ("verbatim", "逐字一致", "与甲方 voiceover.md 逐字一致(落稿锁定只检查不改写,问题报甲方)"), + ("duration_fit", "时长适配", "文案长度适配 Brief 时长带(语速 6–8 字/秒;超带报甲方定夺)"), + ("compliance", "合规", "无越 Brief 的品牌事实、承诺与红线内容"), +] + +NARRATION_TEXT_DIMS = [ + ("filmable", "可配画面", "每句可独立配画面(抽象概念落到具体视觉)"), + ("core_alignment", "核心传达一致", "旁白与 Brief 核心传达 / CTA 要求一致"), + ("duration_fit", "时长适配", "语速 6–8 字/秒下适配 Brief 时长带"), + ("delivery_cues", "delivery_cues 齐", "语气/语速/重音/情感控制"), +] + +RECORDING_DIMS = [ + ("timeline_complete", "时间轴完整", "句 → 时间段 → 画面对应齐全(时间戳 Stage 11 场景 D ASR 后回填)"), + ("duration_fit", "时长适配", "录音总长与 Brief 时长带匹配"), +] + +COLLAGE_DIMS = [ + ("single_metaphor", "单一隐喻", "一条文稿只表达一个清晰隐喻,不逐字进画面"), + ("object_count", "物件 3–6", "关键物件 3–6 个,不是满屏碎片"), + ("color_semantics", "色彩语义", "底色与点色按语义色场表选,有理由"), + ("batch_narrative", "批量叙事", "批量时前后叙事成立,或每条独立成立"), + ("translatability", "可转译", "隐喻一眼可懂、可拆 3–6 个可分离纸片组供静帧生成"), +] + def main() -> None: - parser = argparse.ArgumentParser(description="Stage 2 script-self-eval") + parser = argparse.ArgumentParser(description="Stage 2 script-self-eval(按 workflow 自检标准)") parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") args = parser.parse_args() @@ -47,6 +84,23 @@ def main() -> None: if not script_path.is_file(): die(f"前置缺失: script.md 不存在,先跑 script-write(Stage 1)") + brief_text = _brief.read_brief(project) + workflow = _brief.parse_workflow(brief_text) + vo_mode, _ = _brief.detect_voiceover(project, brief_text) + + # 维度按 workflow + 口播交付形态选择 + if workflow == "reversal-ad": + dims, mode_note = REVERSAL_DIMS, "reversal-ad:GATE A 四问质检标准" + elif workflow == "collage-broll": + dims, mode_note = COLLAGE_DIMS, "collage-broll:隐喻自检(GATE A 质检标准)" + elif workflow == "narration-video" and vo_mode in ("voiceover", "recording"): + dims, mode_note = (NARRATION_LOCK_DIMS if vo_mode == "voiceover" else RECORDING_DIMS), \ + f"narration-video · {'口播稿落稿锁定——只检查不改写,问题报甲方' if vo_mode == 'voiceover' else '真人录音定稿——只检查排布计划'}" + elif workflow == "narration-video": + dims, mode_note = NARRATION_TEXT_DIMS, "narration-video · TTS 旁白稿规范" + else: + dims, mode_note = GENERIC_DIMS, "通用五维(未指定 workflow)" + eval_path = project / "script" / "self-eval.json" eval_path.parent.mkdir(parents=True, exist_ok=True) @@ -59,17 +113,27 @@ def main() -> None: stub = { "stage": "2", + "workflow": workflow, + "mode": mode_note, "dims": [ {"key": k, "name": n, "criteria": c, "score": None, "note": ""} - for k, n, c in EVAL_DIMS + for k, n, c in dims ], "overall": None, "must_rework": None, - "instruction": "agent 据每维 criteria 打分 1–5,note 写扣分理由。任一维 <3 必须 rework(重跑 script-write 改对应段后再跑本评估)。", + "instruction": ( + f"agent 据每维 criteria 打分 1–5,note 写扣分理由。任一维 <3 必须 rework(重跑 script-write 改对应段后再跑本评估)。" + + ("落稿锁定模式:只检查不改写——发现问题报甲方,不自行改稿。" if workflow == "narration-video" and vo_mode == "voiceover" else "") + ), } eval_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") - print(f"[done] self-eval.json 模板已落:{eval_path}") - print(f"[next] agent 逐维打分 → 任一维 <3 必返工 → 全维 ≥3 跑 storyboard-build(Stage 3)") + print(f"[done] self-eval.json 模板已落({mode_note}):{eval_path}") + if workflow == "collage-broll": + print("[next] agent 逐维打分 → 全维 ≥3 后 GATE A 呈交隐喻清单 → 批准后进 Phase 2 静帧生成(不走 storyboard-build)") + elif workflow == "reversal-ad": + print("[next] agent 逐维打分(四问答案随 GATE A 呈交)→ 全维 ≥3 跑 storyboard-build(Stage 3)") + else: + print("[next] agent 逐维打分 → 任一维 <3 必返工 → 全维 ≥3 跑 storyboard-build(Stage 3)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py index 7c0fa3a9..7bf7f7b9 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/script-write.py @@ -1,54 +1,40 @@ #!/usr/bin/env python3 -"""Stage 1 — script-write:Brief 创意 → 分场剧本。 +"""Stage 1 — script-write:按 Brief(及其指定的 workflow)生产 script(分场剧本)。 Usage: python3 scripts/script-write.py -入:project_dir/brief.md(甲方 Brief:创意 + 规格;口播类为 voiceover.md 落稿) -出:project_dir/script/script.md(分场剧本,含 enhancement_cues 六型 + delivery_cues) +入:project_dir/brief.md(甲方 Brief) +出:project_dir/script/script.md(按 workflow 变体) -剧本硬约束: -- 同时间同地点分一场 -- 可拍化描述(不写不可见物) -- 对白引号格式统一 -- enhancement_cues 六型:动作/表情/环境/心理/节奏/视觉锚点 -- delivery_cues:交付旁白时的语气/语速/重音指令(后续 awk-tts / 内置 TTS 用) +变体(脚手架读 Brief 的 workflow 字段与口播交付自动选择): +- 未指定 workflow → 通用分场剧本(可拍化描述 + enhancement_cues 六型 + delivery_cues) +- narration-video + 口播稿 → 口播稿原样落稿锁定(不重写,只做声画实现) +- narration-video + 真人录音 → 录音排布计划(时间戳来自 Stage 11 场景 D ASR) +- narration-video + 旁白 → 旁白稿(由我写,GATE A 交审) +- reversal-ad → 三段结构反转剧本(解说旁白由我写) +- collage-broll → 隐喻清单(本类型的 script) + +Brief 创意不足以直接写剧本 → 先走 story-develop intake workflow 与甲方收敛 Brief。 """ import argparse -import json import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) sys.exit(1) -def main() -> None: - parser = argparse.ArgumentParser(description="Stage 1 script-write") - parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") - args = parser.parse_args() - - project = Path(args.project_dir).resolve() - brief_path = project / "brief.md" - if not brief_path.is_file(): - die(f"前置缺失: brief.md 不存在,先完成 Stage 0 Brief intake(创意模糊时走 story-develop intake workflow)") - - script_path = project / "script" / "script.md" - script_path.parent.mkdir(parents=True, exist_ok=True) - - # checkpoint - if script_path.is_file(): - print(f"[checkpoint] script.md 已存在,沿用:{script_path}") - return - - stub = f"""# 分场剧本(Stage 1) +GENERIC_STUB = """# 分场剧本(Stage 1 · 通用) -> 据 Brief 创意({brief_path.name})拆成可拍化分场剧本。每场含:场景描述、出场人物、对白、动作、enhancement_cues、delivery_cues。 +> 据 Brief 创意拆成可拍化分场剧本。每场含:场景描述、出场人物、对白、动作、enhancement_cues、delivery_cues。 -## 场 1:(场名,如"主角家中—清晨") +## 场 1:(场名,如"主角家中—清晨") ### 场景描述 > agent 填。可拍化——只写镜头能看到的。不写"她想起了童年"(不可见),改写"她抚摸旧照片,眼神放空"。 @@ -71,17 +57,134 @@ def main() -> None: ### delivery_cues(旁白指令,后续 awk-tts / 内置 TTS 用) > agent 填。语气 / 语速 / 重音 / 情感控制。 -- 语气:(agent 填,如"怀念") -- 语速:(agent 填,如"慢") -- 重音:(agent 填,如"童年") -- 情感控制:(agent 填,如"用怀念温暖的语气",传 awk-tts --context-text) ## 场 2(如有) (agent 同上填) """ - script_path.write_text(stub, encoding="utf-8") + +NARRATION_STUB = """# 旁白稿(Stage 1 · narration-video · TTS 旁白形态) + +> 旁白(TTS 配音解说)文稿由我据 Brief 创意写,GATE A 交审——口播稿才由甲方出,本形态无口播交付。 +> 声音规范:音色按 Brief(未指定选与内容气质匹配的,备选+理由记 decisions.json);语速默认 6–8 字/秒。 + +## 旁白全文(agent 填,逐句可独立配画面) + +(句 1) +(句 2) +… + +### delivery_cues(旁白指令,后续 awk-tts / 内置 TTS 用) +- 语气 / 语速 / 重音 / 情感控制(agent 填) +""" + +RECORDING_STUB = """# 口播录音合成计划(Stage 1 · narration-video · 真人录音形态) + +> 甲方口播录音已定稿(口播稿落稿锁定,不重写);Stage 11 场景 D 跑 narration-align +> 拿 utterance 级真实时间戳,按时间戳排画面——不重配旁白、不改录音内容。 + +## 时间轴(agent 填:句 → 时间段 → 对应画面) +| 句 | 起止(ASR 后回填) | 画面 slot / 素材 | +|----|--------------------|------------------| +| 1 | | | +""" + +REVERSAL_STUB = """# 反转植入剧本(Stage 1 · reversal-ad) + +> 解说旁白由我写(旁白归 CP),第三人称解说体:避免问句、感叹号、第二人称与促销信号词; +> 句长 10–15 字,语速 7–8 字/秒。反转点落在总时长 55%–76%;反转前零产品提及。 +> GATE A 四问(script-self-eval 同查):铺垫可信吗 / 幅度够吗 / 接入因果一句话可复述吗 / CTA 是剧情动机吗。 + +## 解说正文(63%–76%:自洽、有冲突、有悬念;段尾停在求助/任务/待发/冲突节点) +(agent 填) + +## 反转过渡(3%–13%:一句话或一帧把剧情指向产品,双关或因果可追,口播明写因果) +(agent 填) + +## 产品植入(15%–27%:3–4 句单点深打,覆盖 Brief 允许的 ≥3 个价值点,每点有对应画面) +(agent 填) + +## 片尾 CTA(优先二次反转剧情化承载) +(agent 填) +""" + +COLLAGE_STUB = """# 隐喻清单(Stage 1 · collage-broll——本类型的 script) + +> 把每条文稿压成一个 sharp visual idea:一条文稿只做一个隐喻,3–6 个关键物件; +> 不要把文稿逐字放进画面。色彩按语义色场表选(Phase 2)。逐条格式: + +1. 核心意思:(观众最终要看懂什么) + 视觉隐喻:(一句话视觉命题) + 关键物件:(3–6 个) + 色彩:(底色 + 点色,按语意) + 组装顺序:(元素滑入次序) +""" + + +def main() -> None: + parser = argparse.ArgumentParser(description="Stage 1 script-write(按 Brief 的 workflow 变体)") + parser.add_argument("project_dir", help="项目目录(CP 自建工作区 output_videos//)") + args = parser.parse_args() + + project = Path(args.project_dir).resolve() + brief_path = project / "brief.md" + if not brief_path.is_file(): + die(f"前置缺失: brief.md 不存在,先完成 Stage 0 Brief intake(创意模糊时走 story-develop intake workflow)") + brief_text = brief_path.read_text(encoding="utf-8") + + workflow = _brief.parse_workflow(brief_text) + vo_mode, vo_path = _brief.detect_voiceover(project, brief_text) + + script_path = project / "script" / "script.md" + script_path.parent.mkdir(parents=True, exist_ok=True) + + # checkpoint + if script_path.is_file(): + print(f"[checkpoint] script.md 已存在,沿用:{script_path}") + print("[hint] 要改就手改或删掉重跑本命令(产物文件存在性即 checkpoint)") + return + + if workflow == "narration-video" and vo_mode == "voiceover": + # 口播稿原样落稿锁定:不重写、不优化、不增删卖点 + content = vo_path.read_text(encoding="utf-8") + script_path.write_text(content, encoding="utf-8") + print(f"[mode] narration-video · 口播稿(甲方交付 {vo_path.name})——原样落稿锁定") + print(f"[done] 口播稿已原样拷入:{script_path}(不重写、不顺手优化、不增删卖点)") + print("[next] 跑 script-self-eval(Stage 2)——落稿锁定模式只检查不改写,问题报甲方;GATE A 呈交后进 Stage 3") + return + + if workflow == "narration-video" and vo_mode == "recording": + print("[mode] narration-video · 真人录音——口播稿落稿锁定(录音即定稿),本文件只记排布计划") + script_path.write_text(RECORDING_STUB, encoding="utf-8") + print(f"[done] 录音排布计划模板已落:{script_path}") + print("[next] agent 填时间轴(时间戳待 Stage 11 场景 D ASR)→ 跑 script-self-eval(Stage 2)") + return + + if workflow == "narration-video": + print("[mode] narration-video · TTS 旁白——旁白稿由我写(口播稿才归甲方),GATE A 交审") + script_path.write_text(NARRATION_STUB, encoding="utf-8") + print(f"[done] 旁白稿模板已落:{script_path}") + print("[next] agent 填旁白全文 + delivery_cues → 跑 script-self-eval(Stage 2)") + return + + if workflow == "reversal-ad": + print("[mode] reversal-ad——解说旁白由我写,按三段结构出剧本;GATE A 附四问答案") + script_path.write_text(REVERSAL_STUB, encoding="utf-8") + print(f"[done] 反转植入剧本模板已落:{script_path}") + print("[next] agent 按三段结构填剧本 → 跑 script-self-eval(Stage 2,四问质检)→ GATE A 交审") + return + + if workflow == "collage-broll": + print("[mode] collage-broll——script 即隐喻清单(见 workflows/collage-broll.md Phase 1)") + script_path.write_text(COLLAGE_STUB, encoding="utf-8") + print(f"[done] 隐喻清单模板已落:{script_path}") + print("[next] agent 填隐喻清单 → 跑 script-self-eval(Stage 2,隐喻自检)→ GATE A 呈交 →") + print(" 批准后进 Phase 2 静帧生成(Stage 3–9 由其替代,不走 storyboard-build)") + return + + print("[mode] 未指定 workflow——通用分场剧本;叙事 / 动效 / 蒙太奇手法由我据创意自定") + script_path.write_text(GENERIC_STUB, encoding="utf-8") print(f"[done] script.md 模板已落:{script_path}") - print(f"[next] agent 填剧本 → 跑 script-self-eval(Stage 2)") + print("[next] agent 填剧本 → 跑 script-self-eval(Stage 2)") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py index 5aea173e..e5b4af4f 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/shot-decompose.py @@ -20,6 +20,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -35,6 +37,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 4 shot-decompose"): + return board_path = project / "storyboard" / "storyboard.json" if not board_path.is_file(): die(f"前置缺失: storyboard.json 不存在,先跑 storyboard-build(Stage 3)") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py index 61780ba1..7741725c 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slideshow-risk.py @@ -23,6 +23,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -46,6 +48,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 8 slideshow-risk"): + return resolve_path = project / "slots" / "asset-resolve.json" if not resolve_path.is_file(): die(f"前置缺失: asset-resolve.json 不存在") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py index a7832205..5ece21ac 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py @@ -24,6 +24,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -46,6 +48,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 6 slot-plan"): + return decompose_path = project / "storyboard" / "shot_decompose.json" if not decompose_path.is_file(): die(f"前置缺失: shot_decompose.json 不存在") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py index 66fa854e..b356332c 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/storyboard-build.py @@ -21,6 +21,8 @@ import sys from pathlib import Path +import _brief + def die(msg: str) -> None: print(f"[error] {msg}", file=sys.stderr) @@ -33,6 +35,8 @@ def main() -> None: args = parser.parse_args() project = Path(args.project_dir).resolve() + if _brief.collage_guard(project, "Stage 3 storyboard-build"): + return script_path = project / "script" / "script.md" if not script_path.is_file(): die(f"前置缺失: script.md 不存在") From 0c9a9be7a659db54e0605e12fe917fef25fedce4 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Wed, 16 Sep 2026 17:50:12 +0800 Subject: [PATCH 13/25] =?UTF-8?q?feat(published-track,main):=20=E5=8F=96?= =?UTF-8?q?=E6=95=B0=E8=8C=83=E5=9B=B4=E6=94=B6=E7=AA=84=E4=B8=BA=20Expert?= =?UTF-8?q?=20=E6=9E=B6=E6=9E=84=204=20=E5=B9=B3=E5=8F=B0=EF=BC=88douyin/x?= =?UTF-8?q?hs/wx=5Fmp/wx=5Fchannel=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HEARTBEAT Step 1/2/时效窗口改为仅 douyin 走 published-track fetch-metrics, xhs/wx_mp/wx_channel 走各专家包 engagement 工具(不变); kuaishou/bilibili 移出取数范围,只保留发布记录/查询 - fetch-and-update-metrics.sh:SCRIPT_PLATFORMS 收窄为 douyin, bilibili/kuaishou 入口门禁 exit 1 报 PLATFORM_OUT_OF_FETCH_SCOPE; 清理不可达的 bilibili/kuaishou 分支(extract_content_id / PLATFORM_HOME) - SKILL.md 与 wrapper usage 文案同步收窄口径 --- crews/main/HEARTBEAT.md | 18 +++++----- crews/main/skills/published-track/SKILL.md | 4 +-- .../skills/published-track/published-track.sh | 2 +- .../scripts/fetch-and-update-metrics.sh | 36 ++++++++++--------- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/crews/main/HEARTBEAT.md b/crews/main/HEARTBEAT.md index 49e24c62..4de7f2df 100644 --- a/crews/main/HEARTBEAT.md +++ b/crews/main/HEARTBEAT.md @@ -47,8 +47,10 @@ #### Step 1: 通过 published-track 读取待取数的已发布内容 +> **取数范围仅限完全支持 Expert 架构的 4 个平台**:douyin / xhs / wx_mp / wx_channel。kuaishou / bilibili 等其余平台不取数(发布记录照常入库,仅不抓互动数据),见 Step 2 第 5 条。 + ```bash -# 对纯 HTTP 脚本平台(douyin / kuaishou / bilibili),查询近期记录(取数时效窗口内,见 Step 2) +# 对纯 HTTP 脚本平台(douyin),查询近期记录(取数时效窗口内,见 Step 2) published-track query --platform douyin --limit 50 ``` @@ -58,9 +60,9 @@ published-track query --platform douyin --limit 50 #### Step 2: 依次获取已发布内容的互动数据并更新到 published-track -按平台分四种情况。第 1 条纯 HTTP 平台按 id 逐条取数;第 2/3/4 条 camoufox 平台**每平台只跑一次 `fetch-all`**——打开后台列表**首页**一次,解析页内全部作品,匹配 DB 全部行逐行写库,首页没有的行报 `NOT_ON_FIRST_PAGE` 跳过(这是设计,见「取数时效窗口」)。 +按平台分两类情况:取数范围内的 4 个 Expert 架构平台(第 1–4 条)逐平台取数;其余平台(第 5 条)一律跳过。第 1 条纯 HTTP 平台按 id 逐条取数;第 2/3/4 条 camoufox 平台**每平台只跑一次 `fetch-all`**——打开后台列表**首页**一次,解析页内全部作品,匹配 DB 全部行逐行写库,首页没有的行报 `NOT_ON_FIRST_PAGE` 跳过(这是设计,见「取数时效窗口」)。 -1. **douyin / kuaishou / bilibili** —— 走 `published-track fetch-metrics`(纯 HTTP+cookie 链路:login-manager 探活 → fetch-retro-data.ts → 写库),对 Step 1 查出的每条记录按 id 逐条调: +1. **douyin(抖音)** —— 走 `published-track fetch-metrics`(纯 HTTP+cookie 链路:login-manager 探活 → fetch-retro-data.ts → 写库),对 Step 1 查出的每条记录按 id 逐条调: ```bash published-track fetch-metrics \ @@ -69,35 +71,35 @@ published-track query --platform douyin --limit 50 脚本封装了完整流程,返回统一 JSON 结果。**xhs / wx_mp / wx_channel 不走这个脚本**——机制不同,见下方第 2/3/4 条。 -2. **小红书 (xhs)** —— **走 `expert-xhs` 包内 `xhs-engagement` 技能**(PATH wrapper 同名),camoufox 抓 creator 创作服务平台后台方案,与第 1 条三个纯 HTTP+cookie 平台机制完全不同,两条路独立、不耦合: +2. **小红书 (xhs)** —— **走 `expert-xhs` 包内 `xhs-engagement` 技能**(PATH wrapper 同名),camoufox 抓 creator 创作服务平台后台方案,与第 1 条 douyin 的纯 HTTP+cookie 链路机制完全不同,两条路独立、不耦合: ```bash xhs-engagement fetch-all ``` > ⚠️ 不要调 `published-track fetch-metrics --platform xhs`——该子命令对 xhs 直接 exit 1 报错提示走 xhs-engagement。两条链路独立维护,避免机制错配。 -3. **微信公众号 (wx_mp)** -- **走 `expert-wx-mp` 包内 `wx-mp-engagement` 工具**(PATH wrapper 同名),camoufox 抓创作者中心方案,与第 1 条三个平台的纯 HTTP+cookie 链路完全不同,两条路独立、不耦合: +3. **微信公众号 (wx_mp)** -- **走 `expert-wx-mp` 包内 `wx-mp-engagement` 工具**(PATH wrapper 同名),camoufox 抓创作者中心方案,与第 1 条 douyin 的纯 HTTP+cookie 链路完全不同,两条路独立、不耦合: ```bash wx-mp-engagement fetch-all ``` > ⚠️ 不要调 `published-track fetch-metrics --platform wx_mp`——该子命令对 wx_mp 直接 exit 1 报错提示走 wx-mp-engagement。两条链路独立维护,避免机制错配。 -4. **微信视频号 (wx_channel)** —— **走 `expert-wx-channel` 包内 `wx-channel-engagement` 工具**(PATH wrapper 同名),camoufox 抓视频号助手后台方案,与 wx_mp 同源(camoufox + 解析 innerText)、与第 1 条三个纯 HTTP+cookie 平台机制完全不同,两条路独立、不耦合: +4. **微信视频号 (wx_channel)** —— **走 `expert-wx-channel` 包内 `wx-channel-engagement` 工具**(PATH wrapper 同名),camoufox 抓视频号助手后台方案,与 wx_mp 同源(camoufox + 解析 innerText)、与第 1 条 douyin 的纯 HTTP+cookie 链路机制完全不同,两条路独立、不耦合: ```bash wx-channel-engagement fetch-all ``` > ⚠️ 不要调 `published-track fetch-metrics --platform wx_channel`——该子命令对 wx_channel 直接 exit 1 报错提示走 wx-channel-engagement。两条链路独立维护,避免机制错配。 -5. **其他平台**(如有) —— 除 douyin / xhs / kuaishou / bilibili / wx_mp / wx_channel 外,其他平台暂不支持自动取数,直接跳过。 +5. **其他平台**(kuaishou / bilibili 等)—— **不在取数范围**。自动取数仅覆盖完全支持 Expert 架构的 4 个平台(douyin / xhs / wx_mp / wx_channel);kuaishou / bilibili 等其余平台只保留发布记录(`record` / `query` 照常支持),**不抓互动数据**,直接跳过,不要尝试任何取数动作。 ##### 取数时效窗口 **发布超过 30 天的内容不再每天抓取互动数据**——数据已稳定,边际变化可忽略,反复抓只浪费配额/增加风控暴露。按平台类型: - **camoufox 后台方案**(xhs / wx_mp / wx_channel):`fetch-all` **永远只打开并解析后台列表首页,不翻页**——首页本身就是天然窗口,页内有什么解析什么;首页之外的老作品报 `NOT_ON_FIRST_PAGE` 自然跳过,**这是设计不是 bug**,不要加翻页逻辑去补抓老内容,也不要按天数过滤 DB 行(少操作一次页面就少一次风控暴露)。 -- **纯 HTTP 脚本方案**(bilibili / douyin / kuaishou):Step 1 查询时加 `publish_date >= date('now', '-30 days')` 过滤,超过 30 天的行直接跳过不调 `published-track fetch-metrics`。 +- **纯 HTTP 脚本方案**(douyin):Step 1 查询时加 `publish_date >= date('now', '-30 days')` 过滤,超过 30 天的行直接跳过不调 `published-track fetch-metrics`。 **DNA 评估(Step 3)不受此限** diff --git a/crews/main/skills/published-track/SKILL.md b/crews/main/skills/published-track/SKILL.md index 3522c1f0..4edbc101 100644 --- a/crews/main/skills/published-track/SKILL.md +++ b/crews/main/skills/published-track/SKILL.md @@ -112,7 +112,7 @@ published-track record \ ### 流程 2A·自动更新(定时任务用) -`fetch-and-update-metrics.sh` 封装探活 → API 抓取 → DB 写入,凌晨复盘心跳调用(仅 bilibili / douyin / kuaishou 三个纯 HTTP 平台): +`fetch-and-update-metrics.sh` 封装探活 → API 抓取 → DB 写入,凌晨复盘心跳调用(仅 douyin 一个纯 HTTP 平台): ```bash # 通过 source-folder 从 DB 查 publish_url → 抓取 → 写入 @@ -135,7 +135,7 @@ published-track fetch-metrics \ Exit codes:0=成功/浏览器/手动(非错误),1=一般错误,2=SESSION_EXPIRED。 -- **脚本支持**:bilibili、douyin、kuaishou(走 `fetch-retro-data.ts` 纯 HTTP + cookie + UA)。**xhs / wx_mp / wx_channel 均不走本技能的 fetch-metrics**(收到这三个平台直接 exit 1 指路)——xhs 走 `expert-xhs` 专家包内的 `xhs-engagement` 工具,wx_mp 走 `expert-wx-mp` 专家包内的 `wx-mp-engagement` 工具,wx_channel 走 `expert-wx-channel` 专家包内的 `wx-channel-engagement` 工具,三者都是 camoufox 抓平台后台方案,与纯 HTTP 链路机制不同。其他平台暂不支持自动抓取互动数据。 +- **脚本支持**:douyin(走 `fetch-retro-data.ts` 纯 HTTP + cookie + UA)。**自动取数仅覆盖完全支持 Expert 架构的 4 个平台**:douyin 走本技能 `fetch-metrics`;xhs / wx_mp / wx_channel 均不走本技能的 fetch-metrics(收到这三个平台直接 exit 1 指路)——xhs 走 `expert-xhs` 专家包内的 `xhs-engagement` 工具,wx_mp 走 `expert-wx-mp` 专家包内的 `wx-mp-engagement` 工具,wx_channel 走 `expert-wx-channel` 专家包内的 `wx-channel-engagement` 工具,三者都是 camoufox 抓平台后台方案,与纯 HTTP 链路机制不同。**bilibili / kuaishou 及其余平台不做自动取数**(收到直接 exit 1 报 `PLATFORM_OUT_OF_FETCH_SCOPE`)——发布记录与查询照常支持,只是不抓互动数据。 ### 流程 2B·用户提供数据(Agent 补录) diff --git a/crews/main/skills/published-track/published-track.sh b/crews/main/skills/published-track/published-track.sh index 3ce4552f..9da286a8 100755 --- a/crews/main/skills/published-track/published-track.sh +++ b/crews/main/skills/published-track/published-track.sh @@ -39,7 +39,7 @@ case "$cmd" in 子命令: record 发布记录入库(upsert;自动读 dna-meta.json 落 dna_id) update-metrics 更新单条/同 folder 记录的互动指标 - fetch-metrics 探活→API 抓取→写库(xhs/bilibili/douyin/kuaishou;wx_mp/wx_channel 不走这里) + fetch-metrics 探活→API 抓取→写库(仅 douyin;xhs/wx_mp/wx_channel 走各专家包 engagement 工具) query 通用查询(--platform [--limit]) query-pending 查询待分发内容 check-published 查某作品是否已发布 diff --git a/crews/main/skills/published-track/scripts/fetch-and-update-metrics.sh b/crews/main/skills/published-track/scripts/fetch-and-update-metrics.sh index e3819b64..fbe0cba2 100755 --- a/crews/main/skills/published-track/scripts/fetch-and-update-metrics.sh +++ b/crews/main/skills/published-track/scripts/fetch-and-update-metrics.sh @@ -33,18 +33,10 @@ extract_content_id() { local url="$2" case "$platform" in - bilibili) - # https://www.bilibili.com/video/BVxxxxx → BVxxxxx - echo "$url" | sed -n 's|.*/video/\(BV[^/?]*\).*|\1|p' - ;; douyin) # https://www.douyin.com/video/1234567890 → 1234567890 echo "$url" | sed -n 's|.*/video/\([0-9]*\).*|\1|p' ;; - kuaishou) - # https://www.kuaishou.com/short-video/xxx 或 /video/xxx - echo "$url" | sed -n 's|.*/short-video/\([^/?]*\).*|\1|p; s|.*/video/\([^/?]*\).*|\1|p' - ;; *) echo "" ;; @@ -56,10 +48,13 @@ extract_content_id() { # 脚本支持的平台(fetch-retro-data.ts 能处理的) # 2026-08-22:xhs 移出——走 xhs-engagement 技能(camoufox creator 后台方案), # 与 wx_mp/wx_channel 同模式,见下方平台路由 -SCRIPT_PLATFORMS="bilibili douyin kuaishou" +# 2026-09-16:bilibili / kuaishou 移出——自动取数范围收窄为完全支持 Expert 架构的 +# 4 个平台(douyin 走本脚本;xhs/wx_mp/wx_channel 走各自专家包的 engagement 工具), +# bilibili/kuaishou 只保留发布记录/查询,不抓互动数据,见下方平台路由 +SCRIPT_PLATFORMS="douyin" # 需要 cookie 的平台 -COOKIE_PLATFORMS="douyin kuaishou" +COOKIE_PLATFORMS="douyin" # 只能手动提供数据的平台 # Phase 4.6:wx_mp 已接入 wx-mp-engagement skill 自动抓取,移出手动列表 @@ -93,15 +88,23 @@ fi LM_PLATFORM="$PLATFORM" case "$LM_PLATFORM" in douyin) PLATFORM_HOME="https://www.douyin.com/" ;; - bilibili) PLATFORM_HOME="https://www.bilibili.com/" ;; - kuaishou) PLATFORM_HOME="https://www.kuaishou.com/" ;; *) PLATFORM_HOME="" ;; esac # ─── 平台路由 ────────────────────────────────────────────────────────────── +# bilibili / kuaishou **不走本脚本**——2026-09-16 起自动取数范围收窄为完全支持 +# Expert 架构的 4 个平台(douyin 走本脚本;xhs/wx_mp/wx_channel 走各自专家包的 +# engagement 工具)。bilibili/kuaishou 只保留发布记录/查询,不自动抓互动数据。 +case "$PLATFORM" in + bilibili|kuaishou) + echo "{\"ok\":false,\"error\":\"PLATFORM_OUT_OF_FETCH_SCOPE\",\"platform\":\"$PLATFORM\",\"hint\":\"自动取数仅覆盖完全支持 Expert 架构的 4 个平台(douyin/xhs/wx_mp/wx_channel)。bilibili/kuaishou 不做自动取数,发布记录与查询照常支持\"}" + exit 1 + ;; +esac + # wx_mp(微信公众号)**不走本脚本**——它走 camoufox 抓创作者中心的方案, -# 与 bilibili/douyin/kuaishou 的纯 HTTP+cookie 链路完全不同, +# 与 douyin 的纯 HTTP+cookie 链路完全不同, # 由 expert-wx-mp 专家包内的 wx-mp-engagement 工具独立承担(agent 直调 wx-mp-engagement wrapper)。 # 见 crews/main/HEARTBEAT.md Step 2 与 crews/main/skills/expert-wx-mp/tools/wx-mp-engagement/SKILL.md。 if [ "$PLATFORM" = "wx_mp" ]; then @@ -118,7 +121,7 @@ if [ "$PLATFORM" = "xhs" ]; then fi # wx_channel(微信视频号)**不走本脚本**——它走 camoufox 抓视频号助手后台的方案, -# 与 bilibili/douyin/kuaishou 的纯 HTTP+cookie 链路完全不同, +# 与 douyin 的纯 HTTP+cookie 链路完全不同, # 由 expert-wx-channel 专家包内的 wx-channel-engagement 工具独立承担(agent 直调同名 wrapper)。 # 见 crews/main/HEARTBEAT.md Step 2 与 crews/main/skills/expert-wx-channel/tools/wx-channel-engagement/SKILL.md。 if [ "$PLATFORM" = "wx_channel" ]; then @@ -288,9 +291,8 @@ if (!data.ok) { console.log('__fetch_failed__:' + (data.error || 'UNKNOWN') + ': const stats = data.stats || {}; const args = []; const mapping = { - // viewCount → 'plays':pub_bilibili / pub_kuaishou 的播放列叫 plays(非 views)。 - // 此 mapping 仅对 SCRIPT_PLATFORMS=bilibili/douyin/kuaishou 生效,其中 - // bili/kuaishou 返回 viewCount 且 DB 列为 plays;douyin 返回 playCount,均不受影响。 + // viewCount → 'plays' 等通用映射保留(fetch-retro-data.ts 可能返回多种键名); + // 当前 SCRIPT_PLATFORMS 仅 douyin,返回 playCount → plays。 viewCount: 'plays', plays: 'plays', playCount: 'plays', views: 'views', likeCount: 'likes', likes: 'likes', commentCount: 'comments', comments: 'comments', From e11ca196a0c6124e32eb4c34dc826038f881899d Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Thu, 17 Sep 2026 06:58:44 +0800 Subject: [PATCH 14/25] =?UTF-8?q?refactor(asr):=20=E7=BB=9F=E4=B8=80=20ASR?= =?UTF-8?q?=20=E8=B7=AF=E7=94=B1=E5=B1=82=E2=80=94=E2=80=94=E7=81=AB?= =?UTF-8?q?=E5=B1=B1=E4=BC=98=E5=85=88=20=E2=86=92=20=E7=99=BE=E7=82=BC?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E7=A9=BA=E9=97=B4=20=E2=86=92=20=E7=99=BE?= =?UTF-8?q?=E7=82=BC=20agent=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 crews/main/skills/_shared/bailian_asr.py:百炼同步 Flash ASR (qwen-audio-3.0-asr-flash,multimodal-generation + input_audio base64 直传, X-DashScope-SSE: disable 一次拿全量词级时间戳;>9MB base64 自动 ffmpeg 压 32kbps mp3 再传)。百炼把整段并成单 sentence,按 words[].punctuation 句末 标点切回多 utterance,与火山返回同粒度。 - 新增 _shared/asr.py 路由入口:凭据在哪家走哪家,某家失败自动落下一家; 全缺时 error 保留 VOLC_ASR/凭证未配置 标记(cut_plan.py 以此判 exit 2)。 - 三处消费方改走路由:viral-chaser/transcriber.ts(python3 -c 内联段)、 talking-head-cut/cut_plan.py、video-producer/narration-align.py; volc_asr.py 降为路由的火山后端,文档注明勿直连。 - viral-chaser frontmatter 去掉 requires.env 硬声明 VOLC_ASR_APP_ID (纯百炼凭据实例也应 eligible),开通指引改为三凭据任选。 - 实测:火山/百炼业务空间双路径转写多句音频,utterance 切分与时间戳正确。 --- .../video-producer/scripts/narration-align.py | 31 +- crews/main/skills/_shared/asr.py | 73 +++++ crews/main/skills/_shared/bailian_asr.py | 265 ++++++++++++++++++ crews/main/skills/_shared/volc_asr.py | 14 +- crews/main/skills/talking-head-cut/SKILL.md | 6 +- .../talking-head-cut/scripts/cut_plan.py | 17 +- crews/main/skills/viral-chaser/SKILL.md | 16 +- .../viral-chaser/scripts/transcriber.ts | 41 ++- .../SKILL.md | 0 .../awk-img-gen.sh} | 0 .../scripts/gen.py | 0 .../scripts/tests/test_gen.py | 0 12 files changed, 405 insertions(+), 58 deletions(-) create mode 100644 crews/main/skills/_shared/asr.py create mode 100644 crews/main/skills/_shared/bailian_asr.py rename skills/{siliconflow-img-gen => awk-img-gen}/SKILL.md (100%) rename skills/{siliconflow-img-gen/siliconflow-img-gen.sh => awk-img-gen/awk-img-gen.sh} (100%) rename skills/{siliconflow-img-gen => awk-img-gen}/scripts/gen.py (100%) rename skills/{siliconflow-img-gen => awk-img-gen}/scripts/tests/test_gen.py (100%) diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py index 115c2fb5..02f8ffae 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py @@ -15,11 +15,12 @@ 路径优先级: 1. TTS 原生字级时间戳(narration.subtitle.json 存在时直接复用,零额外调用) - 2. 火山 ASR 极速版回退(narration.subtitle.json 缺失时,base64 直传 narration.mp3 - 调 volc.bigasr.auc_turbo,拿 utterance 级真实时间戳) + 2. 公共 ASR 路由回退(narration.subtitle.json 缺失时,base64 直传 narration.mp3, + 拿 utterance/word 级真实时间戳) -凭据复用 viral-chaser 同池:VOLC_ASR_APP_ID + VOLC_ASR_ACCESS_KEY(旧控制台双头) -或 VOLC_ASR_APP_KEY(新控制台单头)。 +ASR 供应商路由(_shared/asr.py):火山极速版 → 百炼业务空间 → 百炼 agent plan。 +凭据:VOLC_ASR_*(火山)或 WORKSPACE_ID+MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY +(百炼业务空间)或 AWK_API_KEY(百炼 agent plan),任一组即可。 agent 拿到 segments 后,按各 shot 时长把旁白切片对应到镜。 """ @@ -30,10 +31,10 @@ import sys from pathlib import Path -# 注入 main 侧 _shared 到 sys.path,复用公共火山 ASR 脚本(与 talking-head-cut/scripts/cut_plan.py 同范式) -# 跨 crew 引用:content-producer → main/_shared,凭据同池 VOLC_ASR_*,无新增配置 +# 注入 main 侧 _shared 到 sys.path,复用公共 ASR 路由(与 talking-head-cut/scripts/cut_plan.py 同范式) +# 跨 crew 引用:content-producer → main/_shared,供应商路由 火山→百炼业务空间→百炼 agent plan,凭据同池无新增配置 sys.path.insert(0, str(Path(__file__).resolve().parents[5] / "crews" / "main" / "skills" / "_shared")) -from volc_asr import volc_asr # noqa: E402 +from asr import asr # noqa: E402 def die(msg: str) -> None: @@ -116,22 +117,22 @@ def try_tts_native(narration: Path, subtitle_arg: str | None, out_path: Path) -> def fallback_asr(narration: Path, out_path: Path) -> None: - """回退路径:调公共 volc_asr(火山录音文件极速版),拿 word 级真实时间戳。 + """回退路径:调公共 ASR 路由(火山 → 百炼),拿 word 级真实时间戳。 - 统一走 _shared/volc_asr.py,与 talking-head-cut / viral-chaser 共一份逻辑。 + 统一走 _shared/asr.py,与 talking-head-cut / viral-chaser 共一份逻辑。 原先本函数只拿 utterance 级,现统一拿 word 级(更精细对齐)。 """ - print(f"[info] {narration.stem}.subtitle.json 缺失,调火山 ASR 极速版转写 {narration.name} ...") - result = volc_asr(str(narration)) + print(f"[info] {narration.stem}.subtitle.json 缺失,调公共 ASR 路由转写 {narration.name} ...") + result = asr(str(narration)) if not result.get("ok"): - die(f"火山 ASR 失败: {result.get('error', '未知错误')}") + die(f"ASR 失败: {result.get('error', '未知错误')}") words = result.get("words") or [] if not words: # word 级为空时兜底用 utterance 级 utterances = result.get("utterances") or [] if not utterances: - die("火山 ASR 未返回 utterances/words,无法对齐") + die("ASR 未返回 utterances/words,无法对齐") segs = [ {"start": round(u["start"], 3), "end": round(u["end"], 3), "text": u["text"]} for u in utterances @@ -198,9 +199,9 @@ def main() -> None: if try_tts_native(narration, args.subtitle, out_path): print(f"[done] {out_path.name} 已落(TTS 原生字级时间戳):{out_path}") else: - # 回退路径:火山 ASR 极速版 + # 回退路径:公共 ASR 路由(火山 → 百炼) fallback_asr(narration, out_path) - print(f"[done] {out_path.name} 已落(火山 ASR 回退):{out_path}") + print(f"[done] {out_path.name} 已落(ASR 回退):{out_path}") # 统一打印结果摘要 result = json.loads(out_path.read_text(encoding="utf-8")) diff --git a/crews/main/skills/_shared/asr.py b/crews/main/skills/_shared/asr.py new file mode 100644 index 00000000..7a0c738c --- /dev/null +++ b/crews/main/skills/_shared/asr.py @@ -0,0 +1,73 @@ +"""asr.py — ASR 统一路由(单入口),返回结构与 volc_asr/bailian_asr 同构: + + {ok: True, text, utterances:[{start,end,text}], words:[{start,end,text}]}(时间戳秒) + {ok: False, error} + +供应商优先级(2026-09 拍板): + 1. 火山录音文件极速版 —— VOLC_ASR_APP_ID+VOLC_ASR_ACCESS_KEY(旧控制台双头) + 或 VOLC_ASR_APP_KEY(新控制台单头)在环境中即启用 + 2. 百炼业务空间 —— WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY + 3. 百炼 agent plan —— AWK_API_KEY(token-plan 端点) + +按序尝试有凭据的供应商:某家调用失败(网络/配额/接口错误)自动落到下一家; +全部失败时 error 汇总各家原因。所有凭据都未配置时 error 含 +「凭证未配置」与「VOLC_ASR」标记(talking-head-cut/cut_plan.py 以此判 exit 2)。 + +消费方(import 本模块的 asr(),不再直连 volc_asr): + - crews/main/skills/viral-chaser/scripts/transcriber.ts(python3 -c 内联段) + - crews/main/skills/talking-head-cut/scripts/cut_plan.py + - crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py +""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + +# 本模块与 volc_asr/bailian_asr 同目录(crews/main/skills/_shared/)。 +# 消费方 sys.path 注入的是本目录,但保险起见自注入一次,允许直接以文件路径加载。 +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +from bailian_asr import bailian_asr, list_bailian_endpoints # noqa: E402 +from volc_asr import load_env_file, volc_asr # noqa: E402 + +__all__ = ["asr", "load_env_file", "volc_asr", "bailian_asr"] + + +def _volc_configured() -> bool: + app_id = (os.environ.get("VOLC_ASR_APP_ID") or "").strip() + access_key = (os.environ.get("VOLC_ASR_ACCESS_KEY") or "").strip() + app_key = (os.environ.get("VOLC_ASR_APP_KEY") or "").strip() + return bool((app_id and access_key) or app_key) + + +def asr(audio_path: str) -> dict: + """按 火山 → 百炼业务空间 → 百炼 agent plan 顺序路由转写。""" + load_env_file() + + errors: list[str] = [] + tried = 0 + + if _volc_configured(): + tried += 1 + result = volc_asr(audio_path) + if result.get("ok"): + return result + errors.append(f"火山: {result.get('error', '未知错误')}") + + for endpoint in list_bailian_endpoints(): + tried += 1 + result = bailian_asr(audio_path, endpoint=endpoint) + if result.get("ok"): + return result + errors.append(f"百炼({endpoint[2]}): {result.get('error', '未知错误')}") + + if tried == 0: + return { + "ok": False, + "error": "ASR 凭证未配置:需 VOLC_ASR_APP_ID+VOLC_ASR_ACCESS_KEY 或 VOLC_ASR_APP_KEY(火山)," + "或 WORKSPACE_ID+MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY(百炼业务空间)," + "或 AWK_API_KEY(百炼 agent plan)", + } + return {"ok": False, "error": " | ".join(errors)} diff --git a/crews/main/skills/_shared/bailian_asr.py b/crews/main/skills/_shared/bailian_asr.py new file mode 100644 index 00000000..815a91a1 --- /dev/null +++ b/crews/main/skills/_shared/bailian_asr.py @@ -0,0 +1,265 @@ +"""bailian_asr.py — 阿里云百炼同步 Flash ASR(qwen-audio-3.0-asr-flash)公共调用。 + +与 volc_asr.py 返回同构: + {ok: True, text, utterances:[{start,end,text}], words:[{start,end,text}]} + {ok: False, error} +时间戳统一为秒(百炼返毫秒)。 + +协议(input-audio family,参考百炼录音文件识别 HTTP API 文档 +与 modelstudioai/cli packages/core/src/client/asr-routes.ts): + POST {base}/services/aigc/multimodal-generation/generation + headers: Authorization Bearer + Content-Type application/json + X-DashScope-SSE: disable + body: {model, input:{messages:[{role:"user", + content:[{type:"input_audio", input_audio:{data:"data:audio/;base64,..."}}]}]}, + parameters:{format}} + 响应: output.text 全文 + output.sentence.words[](整段合并为单 sentence, + 每 word 带 begin_time/end_time 毫秒与 punctuation)。 + +端点/key 双模式(resolve_bailian_endpoint): + - WORKSPACE_ID 在 → 业务空间 https://{wsid}.cn-beijing.maas.aliyuncs.com/api/v1, + key = MODELSTUDIO_API_KEY / DASHSCOPE_API_KEY + - 否则 → agent plan https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1, + key = AWK_API_KEY + +音频以 base64 data URI 直传,编码后限 10MB;超限自动 ffmpeg 压成 +16kHz 单声道 32kbps mp3 再传(10 分钟音频约 2.4MB,稳过)。 + +utterances 合成:百炼把整段音频并成一个 sentence,本模块按 words[].punctuation +中的句末标点把词流切回多个 utterance,保持与火山返回同粒度(viral-chaser / +talking-head-cut 按 utterance 切段)。 +""" + +from __future__ import annotations + +import base64 +import os +import subprocess +import tempfile +from pathlib import Path + +DEFAULT_ASR_MODEL = "qwen-audio-3.0-asr-flash" + +WS_BASE_TEMPLATE = "https://{wsid}.cn-beijing.maas.aliyuncs.com/api/v1" +AGENT_PLAN_BASE = "https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1" +ASR_PATH = "/services/aigc/multimodal-generation/generation" + +# 编码后 base64 体积上限(百炼文档:编码后 ≤10MB,留安全余量) +MAX_B64_BYTES = 9 * 1024 * 1024 + +# 句末标点:出现即收束一个 utterance +SENTENCE_END_PUNCT = set("。!?;!?;\n") + +MIME_BY_EXT = { + "mp3": "audio/mpeg", + "wav": "audio/x-wav", + "ogg": "audio/ogg", + "opus": "audio/opus", + "m4a": "audio/mp4", + "aac": "audio/aac", + "flac": "audio/flac", + "amr": "audio/amr", +} + + +BailianEndpoint = tuple[str, str, str] # (base, api_key, mode) + + +def list_bailian_endpoints() -> list[BailianEndpoint]: + """列出所有已配置凭据的百炼端点,按优先级排序。 + + 业务空间(WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY)在前, + agent plan(AWK_API_KEY,token-plan 端点)在后。未配置的不出现。 + mode: "workspace" 或 "agent-plan"。 + """ + endpoints: list[BailianEndpoint] = [] + wsid = (os.environ.get("WORKSPACE_ID") or "").strip() + if wsid: + key = ( + os.environ.get("MODELSTUDIO_API_KEY") + or os.environ.get("DASHSCOPE_API_KEY") + or "" + ).strip() + if key: + endpoints.append((WS_BASE_TEMPLATE.format(wsid=wsid), key, "workspace")) + awk_key = (os.environ.get("AWK_API_KEY") or "").strip() + if awk_key: + endpoints.append((AGENT_PLAN_BASE, awk_key, "agent-plan")) + return endpoints + + +def resolve_bailian_endpoint() -> BailianEndpoint | None: + """取最高优先级的已配置端点;无凭据返回 None。""" + endpoints = list_bailian_endpoints() + return endpoints[0] if endpoints else None + + +def _transcode_to_mp3(audio_path: str) -> str | None: + """ffmpeg 压成 16kHz 单声道 32kbps mp3,返回临时文件路径;失败返回 None。""" + fd, tmp_path = tempfile.mkstemp(suffix=".mp3", prefix="bailian-asr-") + os.close(fd) + try: + subprocess.run( + ["ffmpeg", "-y", "-v", "error", "-i", audio_path, + "-codec:a", "libmp3lame", "-b:a", "32k", "-ac", "1", "-ar", "16000", + tmp_path], + check=True, capture_output=True, timeout=300, + ) + return tmp_path + except (OSError, subprocess.SubprocessError): + try: + os.unlink(tmp_path) + except OSError: + pass + return None + + +def _build_data_uri(audio_path: str) -> tuple[str, str, list[str]]: + """读音频构造 base64 data URI。超限自动转码。 + + 返回 (data_uri, format_hint, 待清理临时文件列表)。 + """ + cleanups: list[str] = [] + path = audio_path + raw = Path(path).read_bytes() + if len(raw) * 4 / 3 > MAX_B64_BYTES: + transcoded = _transcode_to_mp3(path) + if transcoded is None: + raise RuntimeError( + f"音频 {len(raw)} 字节超百炼 base64 上限且 ffmpeg 转码失败" + ) + cleanups.append(transcoded) + path = transcoded + raw = Path(path).read_bytes() + + ext = Path(path).suffix.lower().lstrip(".") + fmt = ext if ext in MIME_BY_EXT else "wav" + mime = MIME_BY_EXT.get(fmt, "audio/x-wav") + b64 = base64.b64encode(raw).decode("ascii") + return f"data:{mime};base64,{b64}", fmt, cleanups + + +def _split_utterances(sentence: dict, words: list[dict]) -> list[dict]: + """按 words[].punctuation 的句末标点把单 sentence 切回多个 utterance。 + + words 为原始百炼 word 条目(含 begin_time/end_time 毫秒、punctuation)。 + 无法切分(无词/无标点)时整段作一个 utterance。 + """ + utterances: list[dict] = [] + buf: list[dict] = [] + for w in words: + buf.append(w) + punct = w.get("punctuation") or "" + if any(ch in SENTENCE_END_PUNCT for ch in punct): + utterances.append({ + "start": float(buf[0].get("begin_time", 0)) / 1000.0, + "end": float(buf[-1].get("end_time", 0)) / 1000.0, + "text": "".join((x.get("text") or "") + (x.get("punctuation") or "") for x in buf), + }) + buf = [] + if buf: + utterances.append({ + "start": float(buf[0].get("begin_time", 0)) / 1000.0, + "end": float(buf[-1].get("end_time", 0)) / 1000.0, + "text": "".join((x.get("text") or "") + (x.get("punctuation") or "") for x in buf), + }) + if not utterances: + # 无词级信息时退化为整句一个 utterance + utterances.append({ + "start": float(sentence.get("begin_time", 0)) / 1000.0, + "end": float(sentence.get("end_time", 0)) / 1000.0, + "text": sentence.get("text", "") or "", + }) + return [u for u in utterances if u["text"].strip()] + + +def bailian_asr(audio_path: str, endpoint: BailianEndpoint | None = None) -> dict: + """调百炼同步 Flash ASR,返回 {ok, text, utterances, words}(与 volc_asr 同构)。 + + endpoint 显式传入 (base, api_key, mode);缺省按 resolve_bailian_endpoint() 取最高优先级。 + """ + try: + import requests + except ImportError as e: + return {"ok": False, "error": f"requests 不可用: {e}"} + + if endpoint is None: + endpoint = resolve_bailian_endpoint() + if endpoint is None: + return { + "ok": False, + "error": "百炼 ASR 凭据未配置:需 WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY" + "(业务空间)或 AWK_API_KEY(agent plan)", + } + base, api_key, mode = endpoint + model = (os.environ.get("BAILIAN_ASR_MODEL") or DEFAULT_ASR_MODEL).strip() + + try: + data_uri, fmt, cleanups = _build_data_uri(audio_path) + except Exception as e: + return {"ok": False, "error": f"读取/转码音频失败: {e}"} + + body = { + "model": model, + "input": {"messages": [{ + "role": "user", + "content": [{"type": "input_audio", "input_audio": {"data": data_uri}}], + }]}, + "parameters": {"format": fmt}, + } + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + "X-DashScope-SSE": "disable", + } + + try: + r = requests.post(f"{base}{ASR_PATH}", json=body, headers=headers, timeout=300) + except Exception as e: + return {"ok": False, "error": f"百炼 ASR 请求失败 ({mode}): {e}"} + finally: + for tmp in cleanups: + try: + os.unlink(tmp) + except OSError: + pass + + if r.status_code != 200: + return { + "ok": False, + "error": f"百炼 ASR 失败 ({mode}, HTTP {r.status_code}): {r.text[:500]}", + } + + try: + resp = r.json() + except Exception as e: + return {"ok": False, "error": f"响应解析失败: {e}; raw={r.text[:500]}"} + + output = resp.get("output") or {} + text = output.get("text") or "" + sentence = output.get("sentence") or {} + if isinstance(sentence, list): + sentence = sentence[-1] if sentence else {} + + raw_words = sentence.get("words") or [] + words = [] + for w in raw_words: + try: + words.append({ + "start": float(w.get("begin_time", 0)) / 1000.0, + "end": float(w.get("end_time", 0)) / 1000.0, + "text": (w.get("text") or "").strip(), + }) + except (TypeError, ValueError): + continue + words = [w for w in words if w["text"]] + + if not text: + text = sentence.get("text") or "" + if not text: + return { + "ok": False, + "error": f"百炼 ASR 返回空文本 ({mode}): request_id={resp.get('request_id', '')}", + } + + utterances = _split_utterances(sentence, raw_words) + return {"ok": True, "text": text, "utterances": utterances, "words": words} diff --git a/crews/main/skills/_shared/volc_asr.py b/crews/main/skills/_shared/volc_asr.py index f4e9ceb2..cfd3901e 100644 --- a/crews/main/skills/_shared/volc_asr.py +++ b/crews/main/skills/_shared/volc_asr.py @@ -1,11 +1,15 @@ -"""volc_asr.py — 火山方舟豆包语音极速版 ASR 公共调用(三处共用单源)。 +"""volc_asr.py — 火山方舟豆包语音极速版 ASR 公共调用(火山后端单源)。 -抽出前散在三处: - - crews/main/skills/talking-head-cut/scripts/cut_plan.py 的 volc_asr() - - crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py 的 fallback_asr() +⚠️ 消费方不要直接 import 本模块——统一走同目录 asr.py 路由 +(火山 → 百炼业务空间 → 百炼 agent plan,见 asr.py / bailian_asr.py)。 +本模块只作为路由的火山后端。 + +三处消费方(均已经由 asr.py): + - crews/main/skills/talking-head-cut/scripts/cut_plan.py + - crews/content-producer/skills/expert-video/tools/video-producer/scripts/narration-align.py - crews/main/skills/viral-chaser/scripts/transcriber.ts 的 PYTHON_SCRIPT 内联段 -三方调同一火山接口(volc.bigasr.auc_turbo),凭据同池: +火山接口(volc.bigasr.auc_turbo),凭据: 旧控制台双头 VOLC_ASR_APP_ID + VOLC_ASR_ACCESS_KEY 新控制台单头 VOLC_ASR_APP_KEY diff --git a/crews/main/skills/talking-head-cut/SKILL.md b/crews/main/skills/talking-head-cut/SKILL.md index cc7f9674..3c0e385f 100644 --- a/crews/main/skills/talking-head-cut/SKILL.md +++ b/crews/main/skills/talking-head-cut/SKILL.md @@ -158,13 +158,13 @@ video-review /highlight.mp4 | 依赖 | 来源 | 说明 | |------|------|------| | ffmpeg / ffprobe | 系统 | 抽 WAV、剪拼、concat | -| 火山引擎豆包语音极速版 | env `VOLC_ASR_*` | ASR 转写拿 word 级时间戳 | -| requests | 仓根 requirements.txt | 调火山 ASR HTTP API | +| 公共 ASR 路由 | env 任一组凭据 | 火山极速版(`VOLC_ASR_*`)→ 百炼业务空间 → 百炼 agent plan;拿 word 级时间戳 | +| requests | 仓根 requirements.txt | 调 ASR HTTP API | | `video-edit` 技能 | 同 workspace | Step 3 剪拼(apply-cut)+ 后续加 BGM(audio-mix) | | `video-review` 技能 | 公共 skills | Step 4 成片自检 | | `bgm-library` 技能 | 公共 skills | 加 BGM 时的曲源(ccMixter 免版税,免 key,优先于 aigc-video-gen music) | -**火山 ASR 凭证**:需 `VOLC_ASR_APP_ID` + `VOLC_ASR_ACCESS_KEY`(旧控制台双头)或 `VOLC_ASR_APP_KEY`(新控制台单头)。未配置时退出码 2 并提示走 viral-chaser 开通流程。 +**ASR 凭证**(任一组):火山 `VOLC_ASR_APP_ID` + `VOLC_ASR_ACCESS_KEY`(旧控制台双头)或 `VOLC_ASR_APP_KEY`(新控制台单头);百炼业务空间 `WORKSPACE_ID` + `MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY`;百炼 agent plan `AWK_API_KEY`。全部未配置时退出码 2 并提示走 viral-chaser 开通流程。 --- diff --git a/crews/main/skills/talking-head-cut/scripts/cut_plan.py b/crews/main/skills/talking-head-cut/scripts/cut_plan.py index 848c8380..41f9d786 100644 --- a/crews/main/skills/talking-head-cut/scripts/cut_plan.py +++ b/crews/main/skills/talking-head-cut/scripts/cut_plan.py @@ -8,8 +8,9 @@ 流程: 1. ffmpeg 抽 16kHz mono WAV - 2. 调火山方舟豆包语音极速版(volc.bigasr.auc_turbo)拿 utterance + word 级时间戳 - (复用 viral-chaser 的鉴权约定:VOLC_ASR_APP_ID+VOLC_ASR_ACCESS_KEY 或 VOLC_ASR_APP_KEY) + 2. 调公共 ASR 路由(_shared/asr.py:火山极速版 → 百炼业务空间 → 百炼 agent plan) + 拿 utterance + word 级时间戳 + (凭据:VOLC_ASR_* 或 WORKSPACE_ID+MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY 或 AWK_API_KEY) 3. 多层检测(按 --mode 决定保留策略): - fillers:语气词清单匹配(zh: 嗯/呃/额/唔/哎/诶/欸;en: um/uh/uhm/er) - silence:word gap > --silence-gap 秒 @@ -23,7 +24,7 @@ 依赖: - ffmpeg/ffprobe(系统) - - 火山 ASR env(VOLC_ASR_*) + - ASR 凭据(VOLC_ASR_* / WORKSPACE_ID+MODELSTUDIO_API_KEY / AWK_API_KEY 任一组) - requests(Python 包,仓根 requirements.txt 已声明) 无第三方 ASR/VAD 包——不引入 faster-whisper / Silero VAD,与 main stdlib + ffmpeg 范式一致。 @@ -31,7 +32,7 @@ 退出码: 0 = 成功 1 = 参数错误 / 文件不存在 - 2 = 火山 ASR env 未配置(提示用户走 viral-chaser 开通流程) + 2 = ASR 凭据未配置(提示用户走 viral-chaser 开通流程配火山,或配百炼 key) 3 = ffmpeg/ffprobe 不存在 """ @@ -47,9 +48,9 @@ import uuid from pathlib import Path -# 注入 _shared 到 sys.path,复用公共火山 ASR 脚本(与 xhs-publish/scripts/publish_xhs.py 同范式) +# 注入 _shared 到 sys.path,复用公共 ASR 路由(与 xhs-publish/scripts/publish_xhs.py 同范式) sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "_shared")) -from volc_asr import volc_asr # noqa: E402 +from asr import asr # noqa: E402 # 语气词清单 FILLERS_ZH = {"嗯", "呃", "额", "唔", "哎", "诶", "欸", "啊", "呀", "嘛", "呢", "吧"} @@ -338,8 +339,8 @@ def main() -> None: pass sys.exit(3) - # 2. 火山 ASR - asr_result = volc_asr(wav_path) + # 2. ASR 路由(火山 → 百炼业务空间 → 百炼 agent plan) + asr_result = asr(wav_path) try: os.unlink(wav_path) except OSError: diff --git a/crews/main/skills/viral-chaser/SKILL.md b/crews/main/skills/viral-chaser/SKILL.md index 069647ef..231b8470 100644 --- a/crews/main/skills/viral-chaser/SKILL.md +++ b/crews/main/skills/viral-chaser/SKILL.md @@ -8,13 +8,21 @@ metadata: bins: - node - ffmpeg - env: - - VOLC_ASR_APP_ID --- -## 🔑 前置:开通火山语音模型(仅首次) +## 🔑 前置:ASR 凭据(仅首次) -本技能的语音转写(ASR)使用**火山引擎豆包语音 · 录音文件极速版**(资源 ID `volc.bigasr.auc_turbo`)。即便账号已订购火山 Code Plan,语音模型仍需**单独开通**,否则调用会返回鉴权/权限错误。 +本技能的语音转写走公共 ASR 路由(`crews/main/skills/_shared/asr.py`),凭据在哪家走哪家: + +1. **火山录音文件极速版**(`VOLC_ASR_*`,优先)——需单独开通语音模型,见下 +2. **百炼业务空间**(`WORKSPACE_ID` + `MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY`) +3. **百炼 agent plan**(`AWK_API_KEY`) + +三组任一组在环境里即可运行;都缺失时分析器退出码 2 并提示配置。 + +### 火山开通指引(选火山路线时) + +火山引擎豆包语音 · 录音文件极速版(资源 ID `volc.bigasr.auc_turbo`)。即便账号已订购火山 Code Plan,语音模型仍需**单独开通**,否则调用会返回鉴权/权限错误。 **判断是否已开通**:直接跑 Step 3 分析器,若 ASR 报错含 `status=45xxxxx` 或权限相关码,说明未开通,按下面流程开通一次即可。 diff --git a/crews/main/skills/viral-chaser/scripts/transcriber.ts b/crews/main/skills/viral-chaser/scripts/transcriber.ts index 9ec81f8c..f8e76d2e 100644 --- a/crews/main/skills/viral-chaser/scripts/transcriber.ts +++ b/crews/main/skills/viral-chaser/scripts/transcriber.ts @@ -1,28 +1,22 @@ #!/usr/bin/env -S node --experimental-strip-types /** - * transcriber.ts — ASR transcription via 火山引擎豆包语音(录音文件极速版) + * transcriber.ts — ASR transcription via 公共 ASR 路由(_shared/asr.py) * - * 接口:POST https://openspeech.bytedance.com/api/v3/auc/bigmodel/recognize/flash - * 资源 ID:volc.bigasr.auc_turbo(需在火山控制台「开通管理 → 语音模型」开通) + * 供应商优先级(2026-09 拍板): + * 1. 火山录音文件极速版(volc.bigasr.auc_turbo,VOLC_ASR_* 凭据) + * 2. 百炼业务空间(qwen-audio-3.0-asr-flash,WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY) + * 3. 百炼 agent plan(qwen-audio-3.0-asr-flash,AWK_API_KEY,token-plan 端点) + * 某家失败自动落下一家;协议细节见 _shared/volc_asr.py 与 _shared/bailian_asr.py。 * - * 选型说明:viral-chaser 的输入是本地 audio.wav(16kHz mono,≤10min), - * 极速版支持 audio.data(base64)直传本地文件,一次请求即返回,无需对象 - * 存储/公网 URL,且原生返回 utterances 带 start_time/end_time(毫秒)和 - * word 级时间戳——正好替代原先 SiliconFlow SenseVoiceSmall 无时间戳、 - * 靠字数比例估算的方案。标准版 2.0(volc.seedasr.auc)单价更低但只接受 - * audio.url,需自备 TOS 托管,未采用。 - * - * 鉴权:兼容新旧控制台(二选一,优先旧控制台双头)。 - * - 旧控制台双头:VOLC_ASR_APP_ID(数字 APP ID)+ VOLC_ASR_ACCESS_KEY(Access Token) - * → X-Api-App-Key=APP_ID, X-Api-Access-Key=Token, user.uid=APP_ID - * - 新控制台单头:VOLC_ASR_APP_KEY(APP Key)→ X-Api-Key=APP_KEY, user.uid=APP_KEY - * 注意:旧控制台 X-Api-App-Key 要的是数字 APP ID,不是 Secret Key/APP Key - * (把 Secret Key 塞进 X-Api-App-Key 会得到 45000010 request and grant appid mismatch)。 + * 选型说明:viral-chaser 的输入是本地 audio.wav(16kHz mono,≤10min)。 + * 两家都支持 base64 直传本地文件并返回 word 级时间戳(百炼超 10MB 自动 + * ffmpeg 压成 32kbps mp3 再传),无需对象存储/公网 URL。百炼把整段并成 + * 单 sentence,_shared/bailian_asr.py 按词级标点切回 utterances,与火山同构。 * * 实现说明:沿用 xhs.ts 同一模式(python3 -c 内联脚本调 requests),避免 * Node fetch/FormData 在部分环境的兼容异常。 * - * 注意:保留 synthesizeSegments 作为兜底——正常情况下火山会返回真实 + * 注意:保留 synthesizeSegments 作为兜底——正常情况下路由会返回真实 * utterances,estimated=false;仅当接口异常未返回 utterances 时才按音频 * 时长估算,estimated=true。 */ @@ -94,16 +88,16 @@ function synthesizeSegments(text: string, durationSeconds: number): TranscriptSe return segs } -// 调公共 _shared/volc_asr.py(与 talking-head-cut / video-producer narration-align 共一份逻辑)。 -// 范式:python3 -c 加载 _shared 到 sys.path,import volc_asr,调它拿 {ok, text, utterances, words}, +// 调公共 _shared/asr.py 路由(与 talking-head-cut / video-producer narration-align 共一份逻辑)。 +// 范式:python3 -c 加载 _shared 到 sys.path,import asr,调它拿 {ok, text, utterances, words}, // 输出 JSON 到 stdout 供 Node 解析。_shared 路径按本剧本位置算(crews/main/skills/_shared)。 const SHARED_DIR = fileURLToPath(new URL("../../_shared/", import.meta.url)) const PYTHON_CALL = ` import json, os, sys sys.path.insert(0, ${JSON.stringify(SHARED_DIR)}) -from volc_asr import volc_asr, load_env_file +from asr import asr, load_env_file load_env_file() -result = volc_asr(sys.argv[1]) +result = asr(sys.argv[1]) # 降级到 utterance 级供旧 TranscriptResult 结构兼容(viral-chaser 只用 utterance 级) segs = [] if result.get("ok"): @@ -117,10 +111,11 @@ export async function transcribeAudio(audioPath: string, durationSeconds = 0): P throw new Error(`音频文件不存在: ${audioPath}`) } - // 极速版硬限 100MB;本地 audio.wav(16kHz mono ≤10min)约 19MB,远低于上限。 + // 兜底上限 100MB(火山极速版硬限;百炼路径 >10MB 会在 _shared 内自动转码压缩)。 + // 本地 audio.wav(16kHz mono ≤10min)约 19MB,正常远低于上限。 const sizeMb = statSync(audioPath).size / (1024 * 1024) if (sizeMb > 100) { - throw new Error(`音频文件过大 (${sizeMb.toFixed(1)}MB),火山极速版上限 100MB`) + throw new Error(`音频文件过大 (${sizeMb.toFixed(1)}MB),超出 ASR 输入上限 100MB`) } const { stdout } = await execFileAsync( diff --git a/skills/siliconflow-img-gen/SKILL.md b/skills/awk-img-gen/SKILL.md similarity index 100% rename from skills/siliconflow-img-gen/SKILL.md rename to skills/awk-img-gen/SKILL.md diff --git a/skills/siliconflow-img-gen/siliconflow-img-gen.sh b/skills/awk-img-gen/awk-img-gen.sh similarity index 100% rename from skills/siliconflow-img-gen/siliconflow-img-gen.sh rename to skills/awk-img-gen/awk-img-gen.sh diff --git a/skills/siliconflow-img-gen/scripts/gen.py b/skills/awk-img-gen/scripts/gen.py similarity index 100% rename from skills/siliconflow-img-gen/scripts/gen.py rename to skills/awk-img-gen/scripts/gen.py diff --git a/skills/siliconflow-img-gen/scripts/tests/test_gen.py b/skills/awk-img-gen/scripts/tests/test_gen.py similarity index 100% rename from skills/siliconflow-img-gen/scripts/tests/test_gen.py rename to skills/awk-img-gen/scripts/tests/test_gen.py From 8f88937ddb06c8eb4c5b35b8eaa7897c7e81b855 Mon Sep 17 00:00:00 2001 From: codes-factory-of-bg Date: Thu, 17 Sep 2026 10:27:39 +0800 Subject: [PATCH 15/25] add aigc support for bailian agent plan --- README.md | 6 +- awada/src/audio-transcribe.ts | 249 ++++++++-- awada/src/message-handler.ts | 2 +- crews/content-producer/AGENTS.md | 2 +- .../openclaw_setting_sample.json | 2 +- .../skills/expert-design/SKILL.md | 6 +- .../expert-design/workflows/web-page.md | 2 +- .../skills/expert-video/SKILL.md | 8 +- .../collage-broll/scripts/check_setup.sh | 2 +- .../tools/video-producer/SKILL.md | 8 +- .../scripts/character-register.py | 6 +- .../video-producer/scripts/make-cover.py | 8 +- .../video-producer/scripts/render-shot.py | 8 +- .../tools/video-producer/scripts/slot-plan.py | 2 +- .../tools/video-producer/video-producer.sh | 2 +- .../expert-video/workflows/collage-broll.md | 12 +- crews/main/TOOLS.md | 4 +- crews/main/skills/expert-douyin/SKILL.md | 2 +- .../skills/expert-douyin/workflows/editing.md | 4 +- .../expert-ir/workflows/investor-materials.md | 2 +- crews/main/skills/expert-wx-channel/SKILL.md | 4 +- .../expert-wx-channel/workflows/editing.md | 2 +- .../workflows/content-production.md | 2 +- .../workflows/content-production.md | 4 +- .../skills/expert-xhs/workflows/editing.md | 4 +- crews/main/skills/video-edit/SKILL.md | 4 +- crews/sales-cs/skills/demo-send/SKILL.md | 43 +- docs/d21-symlink-skill.md | 8 +- package.json | 5 +- skills/README.md | 2 +- skills/aigc-video-gen/SKILL.md | 19 +- skills/aigc-video-gen/aigc-video-gen.sh | 12 +- .../aigc-video-gen/scripts/gen_dashscope.py | 56 ++- skills/awk-img-gen/SKILL.md | 261 +++------- skills/awk-img-gen/awk-img-gen.sh | 4 +- skills/awk-img-gen/scripts/gen.py | 440 +++++++++-------- skills/awk-img-gen/scripts/tests/test_gen.py | 423 ++++++++-------- skills/awk-tts/SKILL.md | 93 ++-- skills/awk-tts/scripts/tts.py | 459 +++++++++++++++--- 39 files changed, 1313 insertions(+), 869 deletions(-) diff --git a/README.md b/README.md index 4cc30894..786f212e 100755 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ irm https://raw.atomgit.com/wiseflow/xiaobei/raw/master/scripts/install-atomgit. > > 默认配置下,小贝的记忆走 FTS 全文检索,已经够用且零额外配置。如果你记忆体量很大、想要更好的语义召回,可以接入一个 embedding 模型;也可以选择打开凌晨"做梦"机制让小贝在夜间整理记忆。 > -> 推荐用 [SiliconFlow](https://cloud.siliconflow.cn/i/WNLYbBpi)(🎁 xiaobei 邀请链接,注册认证后你可获得一张 16 元代金券),它提供 `BAAI/bge-m3` 与 `Qwen/Qwen3-VL-Embedding` 系列,均为 OpenAI 接口格式,可直接配置为 `memorySearch` 的 embedding provider。配置方法:把 `agents.defaults.memorySearch.provider` 从 `"none"` 改为 `"openai-compatible"`,并补上 `remote.baseUrl` / `remote.apiKey` / `model`;想开做梦就把 `plugins.entries.memory-core.config.dreaming.enabled` 改回 `true`。改完重启 gateway 生效。可以让小贝帮你完成配置。 +> 任何 OpenAI 接口格式的 embedding 服务都可以接(如阿里云百炼的 `text-embedding-v3/v4` 系列)。配置方法:把 `agents.defaults.memorySearch.provider` 从 `"none"` 改为 `"openai-compatible"`,并补上 `remote.baseUrl` / `remote.apiKey` / `model`;想开做梦就把 `plugins.entries.memory-core.config.dreaming.enabled` 改回 `true`。改完重启 gateway 生效。可以让小贝帮你完成配置。 ### 配置繁琐,不想操心? @@ -340,7 +340,7 @@ wiseflow/ - html-video(nexu-io 的 HTML 视频渲染方案 — `video-producer` 的 Stage 10 静帧→成片渲染思路与素材组装约定参考自此) https://github.com/nexu-io/html-video - ViMax(HKUDS 的视频生成框架 — `video-producer` 的机位一致性约束与素材 slot 规划借鉴其镜头规划策略) https://github.com/HKUDS/ViMax - OpenMontage(calesthio 的开源蒙太奇剪辑方案 — `video-producer` 的 Stage 12 拼接成片+转场工作流借鉴其片段组装与节奏控制思路) https://github.com/calesthio/OpenMontage -- gbro-collage-broll(MIT — 半调纸拼贴 B-roll 三闸门方法论 — `expert-video` 的 Collage B-roll workflow 移植自此:隐喻设计法、语义色场表、visual-spec schema、静帧/视频 QA 标准照搬,闸门映射为 GATE A/B、渲染栈换成 siliconflow-img-gen + aigc-video-gen i2v) https://github.com/pyang5166/gbro-collage-broll +- gbro-collage-broll(MIT — 半调纸拼贴 B-roll 三闸门方法论 — `expert-video` 的 Collage B-roll workflow 移植自此:隐喻设计法、语义色场表、visual-spec schema、静帧/视频 QA 标准照搬,闸门映射为 GATE A/B、渲染栈换成 awk-img-gen + aigc-video-gen i2v) https://github.com/pyang5166/gbro-collage-broll - agent-skills-launch-pack_(起号方法论知识来源) https://github.com/chenjin-cmd/agent-skills-launch-pack_ ## Citation @@ -356,4 +356,4 @@ https://github.com/TeamWiseFlow/xiaobei ## 友情链接 -[atomgit](https://gitcode.com/atomgit_atomcode)      [siliconflow](https://cloud.siliconflow.cn/i/WNLYbBpi) +[atomgit](https://gitcode.com/atomgit_atomcode) diff --git a/awada/src/audio-transcribe.ts b/awada/src/audio-transcribe.ts index a677cb90..698dc9ce 100644 --- a/awada/src/audio-transcribe.ts +++ b/awada/src/audio-transcribe.ts @@ -1,64 +1,247 @@ /** - * Audio transcription via SiliconFlow API. + * Audio transcription via 公共 ASR 路由(与 crews/main/skills/_shared/asr.py 同优先级)。 * - * Env vars: - * SILICONFLOW_API_KEY — API key (required) - * ASR_MODEL — model name (required, e.g. "FunAudioLLM/SenseVoiceSmall") + * 供应商优先级(2026-09 拍板,凭据在哪家走哪家): + * 1. 火山录音文件极速版 — VOLC_ASR_APP_ID+VOLC_ASR_ACCESS_KEY(旧控制台双头) + * 或 VOLC_ASR_APP_KEY(新控制台单头) + * POST https://openspeech.bytedance.com/api/v3/auc/bigmodel/recognize/flash + * 2. 百炼业务空间 — WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY + * POST https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation + * 3. 百炼 agent plan — AWK_API_KEY + * POST https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation + * 模型 qwen-audio-3.0-asr-flash(BAILIAN_ASR_MODEL 可覆盖) * - * API: POST https://api.siliconflow.cn/v1/audio/transcriptions - * multipart/form-data { file, model } - * Response: { text: string } + * 某家失败自动落下一家;全部失败时 error 汇总各家原因。 + * 百炼以 base64 data URI 直传(编码后 ≤10MB,语音消息远低于此限)。 */ -const SILICONFLOW_ENDPOINT = "https://api.siliconflow.cn/v1/audio/transcriptions"; +const VOLC_ASR_ENDPOINT = + "https://openspeech.bytedance.com/api/v3/auc/bigmodel/recognize/flash"; +const BAILIAN_WS_BASE_TEMPLATE = "https://{wsid}.cn-beijing.maas.aliyuncs.com/api/v1"; +const BAILIAN_AGENT_PLAN_BASE = "https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1"; +const BAILIAN_ASR_PATH = "/services/aigc/multimodal-generation/generation"; +const BAILIAN_DEFAULT_ASR_MODEL = "qwen-audio-3.0-asr-flash"; +const MAX_BAILIAN_B64_BYTES = 9 * 1024 * 1024; + +const AUDIO_MIME_BY_EXT: Record = { + mp3: "audio/mpeg", + wav: "audio/x-wav", + ogg: "audio/ogg", + opus: "audio/opus", + m4a: "audio/mp4", + aac: "audio/aac", + flac: "audio/flac", + amr: "audio/amr", +}; export type TranscribeResult = | { ok: true; text: string } | { ok: false; error: string }; -/** - * Transcribe an audio buffer using SiliconFlow's ASR API. - */ -export async function transcribeAudio( +interface BailianEndpoint { + base: string; + apiKey: string; + mode: "workspace" | "agent-plan"; +} + +function audioFormatHint(fileName: string): string { + const ext = fileName.split(".").pop()?.toLowerCase() ?? ""; + return ext in AUDIO_MIME_BY_EXT ? ext : "wav"; +} + +function listBailianEndpoints(): BailianEndpoint[] { + const endpoints: BailianEndpoint[] = []; + const wsid = process.env.WORKSPACE_ID?.trim(); + if (wsid) { + const key = + process.env.MODELSTUDIO_API_KEY?.trim() || + process.env.DASHSCOPE_API_KEY?.trim(); + if (key) { + endpoints.push({ + base: BAILIAN_WS_BASE_TEMPLATE.replace("{wsid}", wsid), + apiKey: key, + mode: "workspace", + }); + } + } + const awkKey = process.env.AWK_API_KEY?.trim(); + if (awkKey) { + endpoints.push({ base: BAILIAN_AGENT_PLAN_BASE, apiKey: awkKey, mode: "agent-plan" }); + } + return endpoints; +} + +async function transcribeVolc( audioBuffer: Buffer, fileName: string, ): Promise { - const apiKey = process.env.SILICONFLOW_API_KEY?.trim(); - if (!apiKey) { - return { ok: false, error: "SILICONFLOW_API_KEY not set" }; - } + const appId = process.env.VOLC_ASR_APP_ID?.trim(); + const accessKey = process.env.VOLC_ASR_ACCESS_KEY?.trim(); + const appKey = process.env.VOLC_ASR_APP_KEY?.trim(); + const resourceId = + process.env.VOLC_ASR_RESOURCE_ID?.trim() || "volc.bigasr.auc_turbo"; - const model = process.env.ASR_MODEL?.trim(); - if (!model) { - return { ok: false, error: "ASR_MODEL not set" }; + const headers: Record = { + "Content-Type": "application/json", + "X-Api-Resource-Id": resourceId, + "X-Api-Request-Id": crypto.randomUUID(), + "X-Api-Sequence": "-1", + }; + let uid: string; + if (appId && accessKey) { + headers["X-Api-App-Key"] = appId; + headers["X-Api-Access-Key"] = accessKey; + uid = appId; + } else if (appKey) { + headers["X-Api-Key"] = appKey; + uid = appKey; + } else { + return { ok: false, error: "火山 ASR 凭据未配置" }; } - const form = new FormData(); - form.append("file", new Blob([audioBuffer]), fileName); - form.append("model", model); + const body = { + user: { uid }, + audio: { + data: audioBuffer.toString("base64"), + format: audioFormatHint(fileName), + }, + request: { + model_name: "bigmodel", + show_utterances: false, + enable_itn: true, + enable_punc: true, + }, + }; try { - const res = await fetch(SILICONFLOW_ENDPOINT, { + const res = await fetch(VOLC_ASR_ENDPOINT, { method: "POST", - headers: { Authorization: `Bearer ${apiKey}` }, - body: form, + headers, + body: JSON.stringify(body), }); + const status = res.headers.get("X-Api-Status-Code") ?? ""; + if (status !== "20000000") { + const msg = res.headers.get("X-Api-Message") ?? ""; + const snippet = (await res.text().catch(() => "")).slice(0, 200); + return { ok: false, error: `火山 ASR 失败 (status=${status}, msg=${msg}): ${snippet}` }; + } + const json = (await res.json()) as { result?: { text?: string } }; + const text = json.result?.text?.trim() ?? ""; + if (!text) { + return { ok: false, error: "火山 ASR 返回空文本" }; + } + return { ok: true, text }; + } catch (err) { + return { ok: false, error: `火山 ASR 请求失败: ${String(err)}` }; + } +} + +async function transcribeBailian( + audioBuffer: Buffer, + fileName: string, + endpoint: BailianEndpoint, +): Promise { + if (audioBuffer.length * (4 / 3) > MAX_BAILIAN_B64_BYTES) { + return { + ok: false, + error: `音频 ${audioBuffer.length} 字节超百炼 base64 上限(10MB)`, + }; + } + const fmt = audioFormatHint(fileName); + const mime = AUDIO_MIME_BY_EXT[fmt] ?? "audio/x-wav"; + const model = + process.env.BAILIAN_ASR_MODEL?.trim() || BAILIAN_DEFAULT_ASR_MODEL; + + const body = { + model, + input: { + messages: [ + { + role: "user", + content: [ + { + type: "input_audio", + input_audio: { + data: `data:${mime};base64,${audioBuffer.toString("base64")}`, + }, + }, + ], + }, + ], + }, + parameters: { format: fmt }, + }; + try { + const res = await fetch(`${endpoint.base}${BAILIAN_ASR_PATH}`, { + method: "POST", + headers: { + Authorization: `Bearer ${endpoint.apiKey}`, + "Content-Type": "application/json", + "X-DashScope-SSE": "disable", + }, + body: JSON.stringify(body), + }); if (!res.ok) { - const body = await res.text().catch(() => ""); - return { ok: false, error: `SiliconFlow API ${res.status}: ${body.slice(0, 200)}` }; + const snippet = (await res.text().catch(() => "")).slice(0, 200); + return { + ok: false, + error: `百炼 ASR 失败 (${endpoint.mode}, HTTP ${res.status}): ${snippet}`, + }; } - - const json = (await res.json()) as { text?: string }; - const text = json.text?.trim(); + const json = (await res.json()) as { output?: { text?: string } }; + const text = json.output?.text?.trim() ?? ""; if (!text) { - return { ok: false, error: "SiliconFlow returned empty transcript" }; + return { ok: false, error: `百炼 ASR 返回空文本 (${endpoint.mode})` }; } - return { ok: true, text }; } catch (err) { - return { ok: false, error: `SiliconFlow request failed: ${String(err)}` }; + return { ok: false, error: `百炼 ASR 请求失败 (${endpoint.mode}): ${String(err)}` }; + } +} + +function volcConfigured(): boolean { + const appId = process.env.VOLC_ASR_APP_ID?.trim(); + const accessKey = process.env.VOLC_ASR_ACCESS_KEY?.trim(); + const appKey = process.env.VOLC_ASR_APP_KEY?.trim(); + return Boolean((appId && accessKey) || appKey); +} + +/** + * Transcribe an audio buffer: 火山 → 百炼业务空间 → 百炼 agent plan 依次尝试。 + */ +export async function transcribeAudio( + audioBuffer: Buffer, + fileName: string, +): Promise { + const errors: string[] = []; + + if (volcConfigured()) { + const result = await transcribeVolc(audioBuffer, fileName); + if (result.ok) { + return result; + } + errors.push(`火山: ${result.error}`); + } + + for (const endpoint of listBailianEndpoints()) { + const result = await transcribeBailian(audioBuffer, fileName, endpoint); + if (result.ok) { + return result; + } + errors.push(`百炼(${endpoint.mode}): ${result.error}`); + } + + if (errors.length === 0) { + return { + ok: false, + error: + "ASR 凭证未配置:需 VOLC_ASR_APP_ID+VOLC_ASR_ACCESS_KEY 或 VOLC_ASR_APP_KEY(火山)," + + "或 WORKSPACE_ID+MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY(百炼业务空间)," + + "或 AWK_API_KEY(百炼 agent plan)", + }; } + return { ok: false, error: errors.join(" | ") }; } /** diff --git a/awada/src/message-handler.ts b/awada/src/message-handler.ts index 0e429001..015f921f 100644 --- a/awada/src/message-handler.ts +++ b/awada/src/message-handler.ts @@ -247,7 +247,7 @@ async function _dispatchAwadaEvent(entry: AwadaDebounceEntry): Promise { conversation_id: meta.conversation_id, }); - // ---- Handle audio: transcribe via SiliconFlow, then treat as text ---- + // ---- Handle audio: transcribe via ASR 路由(火山→百炼),then treat as text ---- let audioTranscript = ""; for (const audio of audios) { const audioUrl = audio.file_url; diff --git a/crews/content-producer/AGENTS.md b/crews/content-producer/AGENTS.md index df7448f6..b0aaf82e 100644 --- a/crews/content-producer/AGENTS.md +++ b/crews/content-producer/AGENTS.md @@ -35,7 +35,7 @@ - **每接到一个活儿先自建工作区**:视频类走 `output_videos//`,平面设计类走 `design_assets/YYYY-MM-DD-<任务名>/`(由 `design-full init` 建)。甲方传入的现成目录只作素材来源,不当自己的工作区。 - **Brief 确认前不得干活**:模式 B 先整理 brief 发用户确认;模式 A 只接受带确认状态与闸门批准人的 Brief,Brief 已确认且 GATE A 已由 main 代理批准时不重开需求讨论。 - **成品交付前必跑自检**:视频走公共 `video-review` + 响度归一化(`video-producer normalize`,-14 LUFS 必跑),平面设计走视觉 review(对照 brief + DESIGN.md)。 -- **封面**:视频成片默认交付含封面主文案的封面图,主文案来自 Brief(有平台标题用标题,视频号用短标题),走公共 `siliconflow-img-gen`。 +- **封面**:视频成片默认交付含封面主文案的封面图,主文案来自 Brief(有平台标题用标题,视频号用短标题),走公共 `awk-img-gen`。 - **不许声称没做过的事**:没有 tool result 或产物文件证明,不许声称已生成/已渲染/已改动。 - **平台运营不在 CP**:发布到抖音/视频号/小红书/B站等归 main agent 的各平台专家包,CP 不碰;也不私信用户、不代拟运营话术。 - **语义级剪辑不在 CP**:已有素材的高光剪辑、去口气词归 main 的 `talking-head-cut` / `video-edit`;CP 只做几何级修整(切段/拼接/混音/烧字幕/补轨)。 diff --git a/crews/content-producer/openclaw_setting_sample.json b/crews/content-producer/openclaw_setting_sample.json index 2c6e1033..49abd09b 100644 --- a/crews/content-producer/openclaw_setting_sample.json +++ b/crews/content-producer/openclaw_setting_sample.json @@ -16,7 +16,7 @@ "browser-guide", "pexels-footage", "pixabay-footage", - "siliconflow-img-gen", + "awk-img-gen", "smart-search", "video-review", "wxwork-drive", diff --git a/crews/content-producer/skills/expert-design/SKILL.md b/crews/content-producer/skills/expert-design/SKILL.md index 56405b07..ac4e0f88 100644 --- a/crews/content-producer/skills/expert-design/SKILL.md +++ b/crews/content-producer/skills/expert-design/SKILL.md @@ -45,7 +45,7 @@ metadata: 三条 workflow 共用下方 Step 1/2/3/6/7 骨架,差异只在 brief 必含字段与 Step 4/5。 -不适用:视频 / 动画 / 封面图 → `expert-video`(封面走其 Stage 14a);单张配图生成 → 公共 `siliconflow-img-gen`。 +不适用:视频 / 动画 / 封面图 → `expert-video`(封面走其 Stage 14a);单张配图生成 → 公共 `awk-img-gen`。 ## 工具清单 @@ -53,7 +53,7 @@ metadata: |------|------|------| | `design-full` | 建任务工作区 + brief 模板;从内置 14+ 套设计系统库匹配风格 | `design-full init <任务名>` / `design-full pick "<风格描述>"` | -跨领域公共技能:`pexels-footage` / `pixabay-footage`(配图与背景图首选)、`siliconflow-img-gen`(配图备选)、`smart-search`(参考站点调研)。 +跨领域公共技能:`pexels-footage` / `pixabay-footage`(配图与背景图首选)、`awk-img-gen`(配图备选)、`smart-search`(参考站点调研)。 ## 通用骨架(七步,两闸门) @@ -93,7 +93,7 @@ design-full pick "<风格描述>" ### Step 4:素材获取 - **优先**:公共 `pexels-footage` / `pixabay-footage` 搜索下载 -- **备选**:公共 `siliconflow-img-gen` 生成(参数记 `prompts.json`) +- **备选**:公共 `awk-img-gen` 生成(参数记 `prompts.json`) - 甲方给的素材入 `source/`,记录来源与授权 ### Step 5:HTML + CSS 编写 diff --git a/crews/content-producer/skills/expert-design/workflows/web-page.md b/crews/content-producer/skills/expert-design/workflows/web-page.md index 427ffa90..efe889f7 100644 --- a/crews/content-producer/skills/expert-design/workflows/web-page.md +++ b/crews/content-producer/skills/expert-design/workflows/web-page.md @@ -14,7 +14,7 @@ ## Step 4:素材 -页面所需配图 / 背景图 / 参考图:`pexels-footage` / `pixabay-footage` 优先,`siliconflow-img-gen` 备选,全部落 `source/` 并记 `prompts.json`。 +页面所需配图 / 背景图 / 参考图:`pexels-footage` / `pixabay-footage` 优先,`awk-img-gen` 备选,全部落 `source/` 并记 `prompts.json`。 ## Step 5:编写 diff --git a/crews/content-producer/skills/expert-video/SKILL.md b/crews/content-producer/skills/expert-video/SKILL.md index 21db1d4b..72492703 100644 --- a/crews/content-producer/skills/expert-video/SKILL.md +++ b/crews/content-producer/skills/expert-video/SKILL.md @@ -130,7 +130,7 @@ Stage 7 asset-resolve 按 slot 取素材(Fast path:多源并发搜 + Stage 8 slideshow-risk 六维幻灯风险打分(pre-compose 闸门,≥4.0 fail 不许进 compose) Stage 9 delivery-promise-lock 交付承诺八类锁定 + motion_ratio 预估 ────── GATE B:素材闸门(素材齐+计划过审,停,发甲方看 contact sheet)────── -Stage 10 render-shot 按 slot 渲染(AIGC 走 aigc-video-gen i2v 首尾帧插值;静图走 siliconflow-img-gen) +Stage 10 render-shot 按 slot 渲染(AIGC 走 aigc-video-gen i2v 首尾帧插值;静图走 awk-img-gen) motion-graphics Stage 10 第二条渲染路径:程序化逐帧动态图形(声明式 spec,产品段动效/标题动画/ 录屏圈选;确定性渲染不走 AIGC,与 render-shot 并列按镜头性质二选一) Stage 11 mix-audio 配音配乐四场景分流(A 人物对话声画同出 / B 旁白一次性 TTS 带字级时间戳 + 对齐 / @@ -142,7 +142,7 @@ Stage 12 assemble 按序拼接成片(原子工具箱,见下节, Stage 13a video-review 公共 video-review 技术自检(强制闸门,verdict=pass 才继续) Stage 13b motion-audit motion_led 抽查(兑付 delivery-promise) Stage 13c normalize 响度归一化到 -14 LUFS(**必跑**:`video-producer normalize`) -Stage 14a make-cover 封面(siliconflow-img-gen,必含封面主文案) +Stage 14a make-cover 封面(awk-img-gen,必含封面主文案) Stage 15 交付 回报成片 + 封面 + final-deliver.md 的绝对路径与关键参数 ``` @@ -198,9 +198,9 @@ Stage 15 交付 回报成片 + 封面 + final-deliver.md 的绝对 | `video-producer` | 阶段链全部原子能力(剧本 / 分镜、素材 slot 与解析、渲染、混音对齐、拼接合成、动效审计、封面)+ 后期处理(`normalize` **必跑**、`burn-srt` / `duck` / `denoise` / `interp` 可选,全部干湿分离不覆盖输入) | `video-producer <子命令>`;`video-producer help` 列全量 | | `collage-broll` | 纸拼贴 B-roll 的环境自检与 Stage 10 批量 i2v 调度(0 全通 / 1 参数错 / 2 部分失败,只重跑失败条目) | `collage-broll check-setup` / `collage-broll render --batch [--dry-run]` | -跨领域公共技能:`aigc-video-gen`(视频片段生成 / i2v 首尾帧插值,Stage 7/10;输出路径须落在 `output_videos/` 下,调用时 workdir 是 Content Producer workspace 根)、`siliconflow-img-gen`(静帧、角色三视图、封面,Stage 5/10/14a)、`awk-tts`(旁白 TTS,带字级时间戳,Stage 11B;`--enable-subtitle` 让火山流式 HTTP 原生返回时间戳)、`bgm-library`(ccMixter 免版税 + 自动 TASL 署名,商用安全,Stage 11C 优先)、`pexels-footage` / `pixabay-footage`(免版税素材与 BGM 搜索)、`video-review`(成片技术自检闸门,Stage 13a)、`video-edit subtitles`(main crew 暴露的烧字幕原子;不可用时向 Brief owner 报工具缺口,不手写 ffmpeg)。 +跨领域公共技能:`aigc-video-gen`(视频片段生成 / i2v 首尾帧插值,Stage 7/10;输出路径须落在 `output_videos/` 下,调用时 workdir 是 Content Producer workspace 根)、`awk-img-gen`(静帧、角色三视图、封面,Stage 5/10/14a)、`awk-tts`(旁白 TTS,带字级时间戳,Stage 11B;多供应商路由 火山→百炼,`--enable-subtitle` 两家都出字级时间戳)、`bgm-library`(ccMixter 免版税 + 自动 TASL 署名,商用安全,Stage 11C 优先)、`pexels-footage` / `pixabay-footage`(免版税素材与 BGM 搜索)、`video-review`(成片技术自检闸门,Stage 13a)、`video-edit subtitles`(main crew 暴露的烧字幕原子;不可用时向 Brief owner 报工具缺口,不手写 ffmpeg)。 -env 依赖:`AWK_API_KEY`(静帧 / 视频生成)、`VOLC_ASR_*`(`narration-align` 回退路径与甲方口播录音转写;旧控制台双头 `VOLC_ASR_APP_ID` + `VOLC_ASR_ACCESS_KEY`,或新控制台单头 `VOLC_ASR_APP_KEY`)。缺 env 时子命令 exit 2,补齐属 IT engineer 职责,不要静默降级。Python 依赖 `requests`、`Pillow`(`motion-graphics` 逐帧绘制)在仓根 `requirements.txt`。系统依赖:`motion-graphics` 需要 Noto Sans SC/CJK 字体(探测 `/usr/share/fonts/opentype/noto-sc` 等候选目录,缺失 exit 2;可用 spec `font_dir` 或 env `MG_FONT_DIR` 覆盖)。机器资源约束(线程数、分辨率上限、低载编码)读本 workspace `MEMORY.md` 或 Brief 的环境约束,不写死在技能包里(`motion-graphics` 默认即低载:nice19/veryfast/crf18/threads2)。 +env 依赖:`AWK_API_KEY`(agent plan 生图/视频/TTS/ASR 兜底)、`WORKSPACE_ID`+`MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY`(百炼业务空间,优先)、`VOLC_ASR_*`(`narration-align` 回退路径与甲方口播录音转写的火山优先路由;旧控制台双头 `VOLC_ASR_APP_ID` + `VOLC_ASR_ACCESS_KEY`,或新控制台单头 `VOLC_ASR_APP_KEY`)。ASR/TTS 凭据任一组在即可路由;全缺时子命令 exit 2,补齐属 IT engineer 职责,不要静默降级。Python 依赖 `requests`、`Pillow`(`motion-graphics` 逐帧绘制)在仓根 `requirements.txt`。系统依赖:`motion-graphics` 需要 Noto Sans SC/CJK 字体(探测 `/usr/share/fonts/opentype/noto-sc` 等候选目录,缺失 exit 2;可用 spec `font_dir` 或 env `MG_FONT_DIR` 覆盖)。机器资源约束(线程数、分辨率上限、低载编码)读本 workspace `MEMORY.md` 或 Brief 的环境约束,不写死在技能包里(`motion-graphics` 默认即低载:nice19/veryfast/crf18/threads2)。 ## 禁止事项(强制) diff --git a/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh b/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh index db08aa1f..701bf303 100644 --- a/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh +++ b/crews/content-producer/skills/expert-video/tools/collage-broll/scripts/check_setup.sh @@ -12,7 +12,7 @@ FAIL=0 ok() { printf 'PASS %s\n' "$1"; } bad() { printf 'FAIL %s\n' "$1"; FAIL=1; } -# 1. AWK_API_KEY(Phase 2 静帧生成要——siliconflow-img-gen / Seedream) +# 1. 百炼生图凭据(Phase 2 静帧生成要——awk-img-gen:WORKSPACE_ID+MODELSTUDIO_API_KEY 或 AWK_API_KEY) if [ -n "${AWK_API_KEY:-}" ]; then ok "AWK_API_KEY 已设置(Phase 2 静帧可用)" else diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md b/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md index addec00c..f29821f5 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md +++ b/crews/content-producer/skills/expert-video/tools/video-producer/SKILL.md @@ -24,15 +24,15 @@ description: 视频制作原子能力集——剧本/分镜、素材 slot 与解 | `script-self-eval` | script.md | `script/self-eval.json` | 脚本自评 N 维打分,任一维 <3 必返工 | | `storyboard-build` | script.md | `storyboard/storyboard.json` | 剧本 → 镜头表(每镜叙事目的/机位复用/位置朝向/不写不可见) | | `shot-decompose` | storyboard.json | `storyboard/shot_decompose.json` | 每镜拆首帧静照/尾帧静照/运动描述(variation_type 三档) | -| `character-register` | shot_decompose.json + script.md | `characters/registry.json` + 三视图 png | 角色 static/dynamic features 拆分 + front/side/back(调 `siliconflow-img-gen`) | +| `character-register` | shot_decompose.json + script.md | `characters/registry.json` + 三视图 png | 角色 static/dynamic features 拆分 + front/side/back(调 `awk-img-gen`) | | `slot-plan` | storyboard.json + shot_decompose.json | `slots/slot-plan.json` | 素材 slot 规划(template + hero slot + tone→slot 数) | | `asset-resolve` | slot-plan.json | `slots/asset-resolve.json`(含 rejected_picks)+ 素材落 `raw_materials/` | 按 slot 拉素材(Fast path:多源并发搜 + 缩略图人核;调 pexels-footage / pixabay-footage / aigc-video-gen) | | `slideshow-risk` | storyboard.json + slot-plan.json + asset-resolve.json | `slots/slideshow-risk.json` | 六维幻灯风险打分(pre-compose 闸门,≥4.0 fail) | | `delivery-promise-lock` | storyboard.json + brief.md | `slots/delivery-promise.json` | 交付承诺八类锁定 + motion_ratio 预估 | -| `render-shot` | shot_decompose.json + characters/ + slot-picks | `render/shot-NN/` 下产物 | 按 slot 渲染(AIGC 走 `aigc-video-gen` i2v 首尾帧插值;静图走 `siliconflow-img-gen`) | +| `render-shot` | shot_decompose.json + characters/ + slot-picks | `render/shot-NN/` 下产物 | 按 slot 渲染(AIGC 走 `aigc-video-gen` i2v 首尾帧插值;静图走 `awk-img-gen`) | | `motion-graphics` | ` --spec mg.json [--out clip.mp4] [--duration] [--force]` | 单个动态图形 clip.mp4(默认 `render/mg//`) | 程序化逐帧动画(Stage 10 第二条渲染路径,与 render-shot 并列):声明式 spec,内置四模板(dimension_grid 逐维点亮 / scroll_cards 滚动卡组 / crew_panel 角色卡入位 / rec_highlight 录屏圈选)+ 基础元素(text/card/photo_circle/glow/band/highlight_zone/progress_bar)+ custom 插件逃生舱;帧级 checkpoint、时长/帧率断言;只出单段,不拼接不混音不做字幕 | | `mix-audio` | script.md(delivery_cues) | `audio/` 目录 + `subtitles.srt` 模板 | 配音配乐四场景分流:A 人物对话声画同出 / B 旁白一次性 TTS 带字级时间戳 + 对齐 / C BGM 成片后统一生成 / D 甲方口播录音 → ASR 时间戳 → 按时间戳补素材 | -| `narration-align` | audio/narration.mp3 + audio/narration.subtitle.json | `audio/narration-segments.json` | 旁白字级时间戳对齐(**整段模式**:一条连续 narration.mp3;优先复用 `awk-tts --enable-subtitle` 的原生时间戳,缺失时回退火山 ASR 极速版,凭据 `VOLC_ASR_*`) | +| `narration-align` | audio/narration.mp3 + audio/narration.subtitle.json | `audio/narration-segments.json` | 旁白字级时间戳对齐(**整段模式**:一条连续 narration.mp3;优先复用 `awk-tts --enable-subtitle` 的原生时间戳,缺失时回退公共 ASR 路由:火山 → 百炼) | | `narration-layout` | ` --plan narration_plan.json [--srt ...] [--mix ...] [--force]` | `audio/abs_starts.json` + SRT + 可选混音 | 逐句旁白排布(**逐句模式**:每句独立 mp3,与 narration-align 互补):实测镜头时长累积起点 → 每句对齐镜头起点 + 防重叠守卫 → 逐句/末句越界断言(违反非零退出打印明细)→ SRT(样式参数化,force_style 落 abs_starts.json 供 burn-srt 引用)→ 可选一步混音(内部复用 audio-mix:N 路旁白 + BGM fade) | | `clip-trim` | `--input/--output/--start/--end/--speed/--sync-audio/--pre-buffer/--duration/--normalize/--grade/--windows/--zoompan/--low-load` | 切好的片段 | 精确切素材段(入点/出点/倍速/前置缓冲,视频、音频、图片分别处理;`--pre-buffer 0.5` 防切 MP3 吞首字);`--normalize 1920x1080@25` 切片即归一(scale+pad+sar+fps);`--grade warm` 预设调色;`--windows "12.0:3.2,44.5:1.4"` 一镜多窗切后 concat;`--zoompan 1.08` 定帧缓推(视频源在 --start 取帧);`--low-load` 低载编码(nice19/veryfast/crf18/threads2) | | `audio-mix` | `--track(可重复)/--delay/--volume/--fadein/--fadeout/--output/--duration` | 混合音频 | 多轨混音(每轨独立延时、音量与淡入淡出;`--duration` 为硬上限:短轨补虚、超出截断) | @@ -42,7 +42,7 @@ description: 视频制作原子能力集——剧本/分镜、素材 slot 与解 | `add-silent-audio` | `--input/--output/--duration/--sample-rate/--channels` | 含静音音轨的视频 | 给无音频片段补静音轨(concat 前置;assemble 内部也自动调) | | `make-outro` | ` --image <形象图> --slogan <文本> [--color color.json] [--duration 5] [--width 1080] [--fps 30]` | 标准比例片尾段 | 形象图 + 黑边 + 烧字幕 + 静音轨 | | `motion-audit` | video.mp4 + delivery-promise.json | `review/motion-audit.json` | motion_led 抽查(兑付交付承诺) | -| `make-cover` | brief.md(封面主文案)+ storyboard 关键帧 | `cover.jpg` | 封面生成(调 `siliconflow-img-gen`,必含封面主文案) | +| `make-cover` | brief.md(封面主文案)+ storyboard 关键帧 | `cover.jpg` | 封面生成(调 `awk-img-gen`,必含封面主文案) | ## 注意事项 diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py index c15c2197..3e3da99e 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/character-register.py @@ -6,7 +6,7 @@ 入:project_dir/storyboard/shot_decompose.json(Stage 4)+ script.md(Stage 1 出场人物) 出:project_dir/characters/registry.json(每个角色 static/dynamic features) - + project_dir/characters//front.png + side.png + back.png(调 siliconflow-img-gen) + + project_dir/characters//front.png + side.png + back.png(调 awk-img-gen) 三视图:front / side / back 三张同角色不同视角的静照,保证后续镜头里角色机位一致性。 static features:跨镜不变的(发色/衣着/体型/年龄感) @@ -57,7 +57,7 @@ def main() -> None: "characters": [], "instruction": ( "agent 据 script.md 出场人物 + shot_decompose.json 列出所有出场角色,每个角色填 schema 并调 " - "siliconflow-img-gen 生成 front/side/back 三视图落 characters//。" + "awk-img-gen 生成 front/side/back 三视图落 characters//。" "static features 跨镜不变(发色/衣着/体型/年龄感),dynamic features 随镜变(表情/姿势/光影)。" "best_image_selector 走 agent 看 contact sheet 人核,不引 CLIP。" ), @@ -81,7 +81,7 @@ def main() -> None: } registry_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") print(f"[done] registry.json 模板已落:{registry_path}") - print(f"[next] agent 填角色 schema + 调 siliconflow-img-gen 生成三视图 → GATE A 文本闸门") + print(f"[next] agent 填角色 schema + 调 awk-img-gen 生成三视图 → GATE A 文本闸门") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/make-cover.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/make-cover.py index 60b679c0..fdbb11c4 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/make-cover.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/make-cover.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Stage 14a — make-cover:封面(siliconflow-img-gen,必含封面主文案)。 +"""Stage 14a — make-cover:封面(awk-img-gen,必含封面主文案)。 Usage: python3 scripts/make-cover.py --title "..." @@ -7,7 +7,7 @@ 入:project_dir/brief.md(封面主文案)+ storyboard 关键帧 出:project_dir/cover.jpg(含封面主文案的封面图) -封面硬约束:必含封面主文案。主文案由甲方在 Brief 中给出(有平台标题时用标题,视频号用短标题);Brief 未给时回退核心传达。siliconflow-img-gen 不一定能把中文封面主文案烤进图, +封面硬约束:必含封面主文案。主文案由甲方在 Brief 中给出(有平台标题时用标题,视频号用短标题);Brief 未给时回退核心传达。awk-img-gen 不一定能把中文封面主文案烤进图, agent 生成后用 image 工具看,确认封面主文案可见——不可见就用 ImageMagick/Pillow 烧字上去。 """ @@ -52,14 +52,14 @@ def main() -> None: stub = { "cover_copy": title, "instruction": ( - "agent 调公共 siliconflow-img-gen 生成封面图,prompt 必含封面主文案指令。" + "agent 调公共 awk-img-gen 生成封面图,prompt 必含封面主文案指令。" "生成后用 image 工具看,确认封面主文案可见——不可见就用 ImageMagick/Pillow 烧字上去。" "落 cover.jpg 到项目根。" ), "cover_path": str(cover), } print(f"[plan] cover.jpg 封面主文案:{title}") - print(f"[next] agent 调 siliconflow-img-gen 生成 → 确认封面主文案可见 → 落 {cover}") + print(f"[next] agent 调 awk-img-gen 生成 → 确认封面主文案可见 → 落 {cover}") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py index fe8f05e0..72d0e8b1 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/render-shot.py @@ -6,7 +6,7 @@ 入:project_dir/storyboard/shot_decompose.json + characters/ + slots/asset-resolve.json 出:project_dir/render/shot-NN/ 下产物: - first-frame.png(首帧静照,调 siliconflow-img-gen 生成或素材裁切) + first-frame.png(首帧静照,调 awk-img-gen 生成或素材裁切) last-frame.png(尾帧静照) gen*.mp4(aigc-video-gen i2v 产物,首尾帧插值;实际产出名不固定,gen.mp4 / gen-run-v01.mp4 / gen-v2.mp4 等,assemble 自动识别取最新) settings.log @@ -80,8 +80,8 @@ def main() -> None: "variation_type": variation, "reference_images": 1 if variation == "static" else 2, "calls": [ - "siliconflow-img-gen → first-frame.png", - "siliconflow-img-gen → last-frame.png" if variation != "static" else "(skip, same as first)", + "awk-img-gen → first-frame.png", + "awk-img-gen → last-frame.png" if variation != "static" else "(skip, same as first)", "aigc-video-gen i2v --first first-frame.png --last last-frame.png → gen-run-v01.mp4", ], } @@ -92,7 +92,7 @@ def main() -> None: if args.dry_run: print(f" [dry-run] 不真调") else: - print(f" [next] agent 调 siliconflow-img-gen + aigc-video-gen 落产物到 {shot_dir}/") + print(f" [next] agent 调 awk-img-gen + aigc-video-gen 落产物到 {shot_dir}/") if __name__ == "__main__": diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py index 5ece21ac..4e8db158 100644 --- a/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py +++ b/crews/content-producer/skills/expert-video/tools/video-producer/scripts/slot-plan.py @@ -87,7 +87,7 @@ def main() -> None: "query": "(API 搜索关键词,英文给 pexels/pixabay)", "tone_params": {"slot_duration": tone_cfg["shot_duration"]}, "hero_slot": False, - "fallback": "静图(siliconflow-img-gen)", + "fallback": "静图(awk-img-gen)", }, } plan_path.write_text(json.dumps(stub, ensure_ascii=False, indent=2), encoding="utf-8") diff --git a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh index f9a55f3a..4d283a28 100755 --- a/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh +++ b/crews/content-producer/skills/expert-video/tools/video-producer/video-producer.sh @@ -52,7 +52,7 @@ video-producer — 视频制作原子能力(wrapper,expert-video 包内工 make-outro Stage 12 片尾制作(形象图+黑边+烧字幕+静音轨 → 标准比例片尾) motion-audit Stage 13b motion_led 抽查(补公共 video-review) normalize Stage 13c 响度归一化到 -14 LUFS(**必跑**) - make-cover Stage 14a 封面(siliconflow-img-gen,必含封面主文案) + make-cover Stage 14a 封面(awk-img-gen,必含封面主文案) 后期处理(可选,全部干湿分离:输出落 _<处理名>.mp4,不覆盖输入): burn-srt libass 把 SRT 硬烧进画面(甲方要字幕时) diff --git a/crews/content-producer/skills/expert-video/workflows/collage-broll.md b/crews/content-producer/skills/expert-video/workflows/collage-broll.md index 11f0cb8c..3e8346e8 100644 --- a/crews/content-producer/skills/expert-video/workflows/collage-broll.md +++ b/crews/content-producer/skills/expert-video/workflows/collage-broll.md @@ -116,13 +116,15 @@ Brief 里写 `workflow: collage-broll`,或甲方要"把这句口播做成拼 主体以黑白半调为主,局部彩色纸张必须服务信息层级,不为彩色而彩色。 -### imagegen prompt(Seedream / siliconflow-img-gen) +### imagegen prompt(awk-img-gen / 百炼) ```bash -siliconflow-img-gen --prompt "<下面整段>" --image-size 1600x2848 --out-dir /render//frames/ +awk-img-gen --prompt "<下面整段>" --image-size 1536x2688 --out-dir /render//frames/ ``` -Prompt 用英文(Seedream 对英文响应更好),整段落 `script/imagegen-prompts.md` 留档: +> 尺寸注意:百炼上限总像素 2048×2048,旧火山 9:16 预设 `1600x2848` 超限会被脚本拒绝;9:16 一律用 `1536x2688`。 + +Prompt 语言:下面模板是英文可照用;qwen-image / wan2.7 中文同样好,**要渲染的文字必须原句完整写入**(见 awk-img-gen 封面最佳实践)。整段落 `script/imagegen-prompts.md` 留档: ```text Use case: ads-marketing @@ -136,7 +138,7 @@ Constraints: [本条隐喻必须一眼看懂的关系]. Avoid: no typography, no readable letters, no numerals, no logos, no watermark, no UI, no subtitles, no glossy 3D, no photoreal environment, no clutter. ``` -Seedream 不支持参考图锁风格,"同设计语言"靠同一批复用同一 `style_signature` 字串 + 同一 `color_field` 范围。 +同设计语言:优先用 awk-img-gen 参考图编辑(`--image` 传同批已过 QA 的静帧,1–3 张)锁风格;不用参考图时靠同一批复用同一 `style_signature` 字串 + 同一 `color_field` 范围。 ### 静帧 QA @@ -263,7 +265,7 @@ video-review render//gen-runs/run-v01/final-5s-noaudio.mp4 ├── script/ │ ├── script.md # Phase 1 隐喻清单(本类型的"分场剧本") │ ├── visual-spec.json # Phase 2 视觉规格 -│ ├── imagegen-prompts.md # Seedream prompt 留档 +│ ├── imagegen-prompts.md # imagegen prompt 留档 │ └── decisions.json # 决策审计链 ├── gates/ │ ├── gate-a.md / gate-b.md # 闸门批准记录(含批准人与批准范围) diff --git a/crews/main/TOOLS.md b/crews/main/TOOLS.md index 972cd1b8..6f0c00ba 100644 --- a/crews/main/TOOLS.md +++ b/crews/main/TOOLS.md @@ -6,9 +6,9 @@ ### 📝 视频封面/海报制作经验 -`siliconflow-img-gen` 可以很好的直接出带文字的海报,完全不必要先生成图,然后自己再编写脚本拼字。 +`awk-img-gen` 可以很好的直接出带文字的海报,完全不必要先生成图,然后自己再编写脚本拼字。 -具体见 `siliconflow-img-gen` 技能中 `视频封面/海报最佳实践`。 +具体见 `awk-img-gen` 技能中 `视频封面/海报最佳实践`。 ### 数据库查询一定走 published-track wrapper diff --git a/crews/main/skills/expert-douyin/SKILL.md b/crews/main/skills/expert-douyin/SKILL.md index 741ca161..1cab3109 100644 --- a/crews/main/skills/expert-douyin/SKILL.md +++ b/crews/main/skills/expert-douyin/SKILL.md @@ -43,7 +43,7 @@ metadata: 跨领域通用技能:`viral-chaser`(抖音 / B站 / 小红书视频下载拆解,DNA 采样与仿写参考的取数主力)、`smart-search`(跨平台搜索,选题调研优先走社交平台,不用通用搜索引擎)、`content-calibrator`(DNA 表现评估)、`published-track`(发布记录与指标库)、`login-manager`(抖音登录态维护)。 -素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 +素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`awk-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 **视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;**口播**(真人出镜 / 数字人 / 真人录音)的口播稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写;**旁白**(剪辑配的解说)完全由 CP 写,main 不出旁白稿。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 diff --git a/crews/main/skills/expert-douyin/workflows/editing.md b/crews/main/skills/expert-douyin/workflows/editing.md index 312f1395..2e6a745b 100644 --- a/crews/main/skills/expert-douyin/workflows/editing.md +++ b/crews/main/skills/expert-douyin/workflows/editing.md @@ -14,7 +14,7 @@ | "剪紧一点 / 去口气词 / 把高光剪出来" | 轻剪层 | `talking-head-cut`(去口气词、结巴、静音,按发言内容剪高光) | | "加 BGM / 加字幕 / 补片头片尾 / 插一段素材" | 加工层 | `video-edit`(audio-mix / subtitles / extract / assemble) | | "结构调一下 / 把第二段提前 / 换个讲法" | 结构层 | 先出调整方案(新的段落顺序与时点)-> 确认 -> `video-edit` 重剪 | -| "换个封面" | 封面层 | 成片抽帧或 `siliconflow-img-gen` 按 DNA `production-spec`(画面风格)重做 | +| "换个封面" | 封面层 | 成片抽帧或 `awk-img-gen` 按 DNA `production-spec`(画面风格)重做 | | "换个风格 / 方向不对" | 方向层 | 回到 `content-production.md` 从选题重新走 | ## 文案层 @@ -44,7 +44,7 @@ 1. 用户直接指定封面图 -> 直接使用。 2. 用户说"从片子里选一帧" -> 抽帧候选给用户挑。 -3. 用户说"重做一张" -> 按 DNA `production-spec`(画面风格)与本次标题/核心收益,用 `siliconflow-img-gen` 生成,用户确认后替换 `cover.jpg`。 +3. 用户说"重做一张" -> 按 DNA `production-spec`(画面风格)与本次标题/核心收益,用 `awk-img-gen` 生成,用户确认后替换 `cover.jpg`。 ## 改完必做 diff --git a/crews/main/skills/expert-ir/workflows/investor-materials.md b/crews/main/skills/expert-ir/workflows/investor-materials.md index 1adf6e08..1fd7a890 100644 --- a/crews/main/skills/expert-ir/workflows/investor-materials.md +++ b/crews/main/skills/expert-ir/workflows/investor-materials.md @@ -59,7 +59,7 @@ - 现场路演/拜访场景 → 用演示文稿工具生成 PPTX - 用户未指定时,简要介绍两种方式让用户选择 -**配图**:优先使用 `siliconflow-img-gen`(16:9),不可用时尝试 `pexels-footage` 或 `pixabay-footage` +**配图**:优先使用 `awk-img-gen`(16:9),不可用时尝试 `pexels-footage` 或 `pixabay-footage` ### One-Pager / 投资人备忘录 diff --git a/crews/main/skills/expert-wx-channel/SKILL.md b/crews/main/skills/expert-wx-channel/SKILL.md index a74901f5..a7ea48cb 100644 --- a/crews/main/skills/expert-wx-channel/SKILL.md +++ b/crews/main/skills/expert-wx-channel/SKILL.md @@ -41,9 +41,9 @@ metadata: | `wechat-channels-publish` | 发布视频到视频号创作者中心(camoufox-cli 持久化 session `wechat-channel`) | 无 wrapper,按工具说明驱动 `camoufox-cli` | | `wx-channel-engagement` | 视频号助手后台作品数据抓取,写入 published-track 的 `pub_wx_channel` 表 | `wx-channel-engagement` | -跨领域通用技能:`published-track`(发布记录与指标库)、`content-calibrator`(DNA 表现评估)、`smart-search`(跨平台搜索,选题调研优先社交平台)、`council`(定位决策辅助)、`siliconflow-img-gen`(封面图生成)。 +跨领域通用技能:`published-track`(发布记录与指标库)、`content-calibrator`(DNA 表现评估)、`smart-search`(跨平台搜索,选题调研优先社交平台)、`council`(定位决策辅助)、`awk-img-gen`(封面图生成)。 -素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`siliconflow-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 +素材加工相关技能:`video-edit`(素材加工拼接)、`talking-head-cut`(口播轻剪辑)、`ui-demo`(产品操作录屏)、`video-review`(成片质检闸门,仅用于 main 自做轻加工成品的自检;CP 成片质检在 CP 流程内完成)、`awk-img-gen`(封面图)、`pexels-footage` / `pixabay-footage`(免版权素材)。 **视频全案分工硬边界**:除非是基于已有素材轻加工,否则视频全案的制作均应委托 `content-producer`。main 负责选题策划、按 DNA 出 **Brief**、拟定标题与简介、准备素材(用户素材预处理 / `ui-demo` 录屏 / 从 `campaign_assets/` 挑选,绝对路径写进 Brief)、监督推动 CP、成片后的发布与运营;**口播**(真人出镜 / 数字人 / 真人录音)的口播稿一律由 main 写好并随 Brief 交付(`narration-script` 子模块启用时按其结构写,未启用时按用户要求与 Brief 核心传达写;真人口播时,指导用户录音并取得录音文件),CP 不重写;**旁白**(剪辑配的解说)完全由 CP 写,main 不出旁白稿。Brief 指定 `workflow` 时 CP 必须采用;未指定时 CP 按其通用制作流程做;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。Brief **不含 DNA 信息**,main 也不替 CP 建工作区(双方 T3 权限可互访取文件)。 diff --git a/crews/main/skills/expert-wx-channel/workflows/editing.md b/crews/main/skills/expert-wx-channel/workflows/editing.md index 86ae59ea..108f2fb5 100644 --- a/crews/main/skills/expert-wx-channel/workflows/editing.md +++ b/crews/main/skills/expert-wx-channel/workflows/editing.md @@ -49,7 +49,7 @@ ## 换封面 1. 读目标 DNA 的 `title-cover` 与 `production-spec`,保留封面三要素:身份 + 痛点 + 解决方案。 -2. 按新主题出 2-3 个候选:素材截帧优先,无合适素材走 `siliconflow-img-gen` 生成。 +2. 按新主题出 2-3 个候选:素材截帧优先,无合适素材走 `awk-img-gen` 生成。 3. 用户确认后替换 `wx_channel/outputs//cover.jpg`。 ## 压缩 / 展开时长 diff --git a/crews/main/skills/expert-wx-mp/workflows/content-production.md b/crews/main/skills/expert-wx-mp/workflows/content-production.md index d227f958..fb24324e 100644 --- a/crews/main/skills/expert-wx-mp/workflows/content-production.md +++ b/crews/main/skills/expert-wx-mp/workflows/content-production.md @@ -169,7 +169,7 @@ DNA 约束的是选题角度、标题、结构、句式、语气和表达方式 1. 读取 DNA template 的封面图风格和封面 AIGC 提示词要素。 2. 结合最终标题、目标读者和本文核心收益,补齐本次封面的主体、场景、构图、色彩、光线、质感、文字视觉和负向约束。 -3. 使用 `siliconflow-img-gen` 生成封面图。 +3. 使用 `awk-img-gen` 生成封面图。 4. 用户已提供封面或封面素材时,可以基于用户已提供的封面或封面素材做编辑,补充必要要素。如果用户提供的不足以成为封面图,比如说尺寸偏小,元素不全等,则作为生成的参考。如果用户明确说:就按照他提供的,那就直接使用。 5. 生成的封面图发给用户确认,确认后保存到 `wx_mp/outputs//cover.jpg`。 diff --git a/crews/main/skills/expert-xhs/workflows/content-production.md b/crews/main/skills/expert-xhs/workflows/content-production.md index 5d154cf2..b1074526 100644 --- a/crews/main/skills/expert-xhs/workflows/content-production.md +++ b/crews/main/skills/expert-xhs/workflows/content-production.md @@ -205,7 +205,7 @@ Brief 硬性规则: ### 图文图组 1. 读取 DNA 的图组视觉与标题封面结论,只取风格边界。 -2. 图片来源优先级:用户素材 → `campaign_assets/` → `siliconflow-img-gen` → `pexels-footage` / `pixabay-footage`。 +2. 图片来源优先级:用户素材 → `campaign_assets/` → `awk-img-gen` → `pexels-footage` / `pixabay-footage`。 3. 封面必须存在,兑现标题承诺;生成图发用户确认。 4. 图文建议 3:4 竖版,图片 ≤ 18 张,顺序按信息推进。 5. 图片与正文分工:图承载证据、过程、对比或清单;文承载判断、细节与行动。 @@ -218,7 +218,7 @@ Brief 硬性规则: | 已有素材需简单加工 | main 用 `video-edit` / `talking-head-cut` 处理 | | 全案制作 | 委托 `content-producer`:只交 Brief 一份(素材、口播文案 / 录音以绝对路径写在 Brief 内),不指定 CP 工作区;指定 `workflow` 必须采用;未指定时 CP 按其通用制作流程做,叙事 / 动效 / 蒙太奇手法由 CP 据创意自定;Brief 缺失或创意不足以直接写剧本时,CP 会走其 story-develop intake workflow 与 Brief owner 收敛 Brief。成片与封面按 CP 回报的绝对路径取回作品目录 | -视频封面优先从成片选帧;需要更强视觉冲击时用 `siliconflow-img-gen`。 +视频封面优先从成片选帧;需要更强视觉冲击时用 `awk-img-gen`。 ## 【确认】正文与图组 / 成片与封面 diff --git a/crews/main/skills/expert-xhs/workflows/editing.md b/crews/main/skills/expert-xhs/workflows/editing.md index 74dffd76..6008cc51 100644 --- a/crews/main/skills/expert-xhs/workflows/editing.md +++ b/crews/main/skills/expert-xhs/workflows/editing.md @@ -13,7 +13,7 @@ | "改下标题" | 标题层 | 按 `title-cover` 与 `search-intent` 重写,给 2-3 个候选 | | "正文改改 / 精简 / 扩写 / 换开头" | 正文层 | 按 `topic-angle`、`content-idea`、`search-intent` 与互动目标局部调整 | | "标签换一下 / 加标签" | 标签层 | 按 template 话题标签策略重组(≤10 个硬限制) | -| "换封面 / 换图 / 加图" | 图组层 | 用户指定 / `siliconflow-img-gen` 按 `imageset-visual` 重做 | +| "换封面 / 换图 / 加图" | 图组层 | 用户指定 / `awk-img-gen` 按 `imageset-visual` 重做 | | "结构调一下 / 改成清单体" | 结构层 | 先出调整方案(新的段落组织与信息顺序)-> 确认 -> 重写正文 | | "换个风格 / 用另一个 DNA 写" | 方向层 | 明确目标 `dna-id`,按目标 template 重写;等于一次小型再生产 | | "方向不对 / 选题要换" | 选题层 | 回到 `content-production.md` 从选题重新走 | @@ -45,7 +45,7 @@ ## 图组层 1. 用户直接指定图片 -> 直接使用,复制进笔记目录。 -2. 用户说"重做封面" -> 按 DNA `imageset-visual`、标题承诺与核心收益,用 `siliconflow-img-gen` 生成,用户确认后替换。 +2. 用户说"重做封面" -> 按 DNA `imageset-visual`、标题承诺与核心收益,用 `awk-img-gen` 生成,用户确认后替换。 3. 增删配图:总数 ≤ 18 张;顺序按正文信息推进重排;替换后更新 `note.md` 同目录图片与引用。 4. 图文建议 3:4 竖版;封面(首图)必须存在。 diff --git a/crews/main/skills/video-edit/SKILL.md b/crews/main/skills/video-edit/SKILL.md index 775d090d..f9384191 100644 --- a/crews/main/skills/video-edit/SKILL.md +++ b/crews/main/skills/video-edit/SKILL.md @@ -142,7 +142,7 @@ video-edit audio-mix input.mp4 --narration speech.mp3 --bgm music.mp3 --output o 旁白音频的生成: 1. **优先使用 OpenClaw 内置 TTS 工具**(`tts_generate` 或 agent 内置语音合成能力) -2. 内置 TTS 不可用时,使用公共 `awk-tts` 技能(火山方舟豆包语音合成 2.0,要求环境变量已配置 `VOLC_TTS_*` 凭据) +2. 内置 TTS 不可用时,使用公共 `awk-tts` 技能(多供应商:火山豆包 2.0 → 百炼业务空间 → 百炼 agent plan,凭据在哪家走哪家) 3. 旁白时长必须与视频时长匹配(TTS 语速可微调以适配),混音前先核对两者时长 BGM(`--bgm` 文件)的来源: @@ -246,7 +246,7 @@ verdict=pass 才交付;fail 按 critical 项修复后重审;warn 向用户 - 竖屏封面 1080x1920 - 可以使用视频关键画面作为背景,但文字是必须元素 -使用公共 `siliconflow-img-gen` 技能制作封面,保存为 `/cover.jpg`。 +使用公共 `awk-img-gen` 技能制作封面,保存为 `/cover.jpg`。 > 仅对交付成片的项目做封面;中间加工产物(如只是帮用户给一段素材加 BGM)不需要封面。 diff --git a/crews/sales-cs/skills/demo-send/SKILL.md b/crews/sales-cs/skills/demo-send/SKILL.md index 019b3530..ce98e9bd 100644 --- a/crews/sales-cs/skills/demo-send/SKILL.md +++ b/crews/sales-cs/skills/demo-send/SKILL.md @@ -8,39 +8,40 @@ description: > # demo-send -> 技能目前为示例。启用前需根据实际业务调整。 - ## 用途 -当客户属于 `free` 状态,且提出具体使用问题、想先看看产品形态、或需要一个直观参考时,发送 demo 材料。 +当客户属于 `free` 状态,且提出具体使用问题、想先看看产品形态、或需要一个直观参考时,发送 demo 视频。 -## 调用方式 +## ⭐ 唯一调用方式:一键脚本(2026-09-17 起) -使用 `message` 工具发送预存在微信网盘中的 demo 文件: +直接执行脚本发送 demo 视频(**不要**使用 `message` 工具的 `sendAttachment` 手动发送): ``` -message(action="sendAttachment", file_name="<文件名>") +bash /home/ctyun/.openclaw/workspace-sales-cs/skills/demo-send/scripts/send-demo.sh --user-id-external "<客户昵称>" ``` -> **错误示例**(禁止使用): -> ``` -> message(action="sendAttachment", filename="...", filePath="...") -> ``` -> 参数名必须是 `file_name`(带下划线),不得传 `filePath` 或 `filename`。`file_name` 对应微信网盘中已存的文件名,不是本地路径。 +> `<客户昵称>` 必须是客户库 `cs_record.peer` 中完全一致的昵称(含空格), +> 发送前脚本会校验收件人存在性,不存在会拒绝发送并 exit 1。 -**可用文件**: -- `wiseflow5x.mp4` — 小贝(xiaobei)系统演示视频 - -**示例**: -``` -message(action="sendAttachment", file_name="wiseflow5x.mp4") -``` +**脚本行为**: +- 固定发送微信网盘预存文件 `wiseflow5x.mp4`(唯一 demo 文件,已写死) +- 参数已全部封装,脚本自动构造正确的 file 消息,**无需手动拼参数** +- 成功打印 `✅ demo 视频(...)已发送给「...」: `,exit 0 +- 失败打印错误到 stderr,exit 1 ## 完整发送流程 -1. 直接调用 `message(action="sendAttachment", file_name="...")` 发送文件(**本 turn 不输出任何文字**) -2. 工具返回后,在最后一个 turn 统一输出完整回复:说明已发送 demo + 追问客户的具体需求或应用场景 + 提醒官网/GitHub 主页获取最新信息 +1. 执行上面的脚本(**本 turn 不输出任何文字**) +2. 工具返回成功(exit 0)后,在最后一个 turn 统一输出完整回复: + - 说明已发送 demo 视频 + - 追问客户的具体需求或应用场景 + - 提醒官网/GitHub 主页获取最新信息 -> **重要**:不要在调用工具前生成任何文字(包括"我先给您发一份..."之类的介绍语),否则客户会收到多条内容相近的消息。 +> **重要**:不要在调用脚本前生成任何文字(包括"我先给您发一份..."之类的介绍语),否则客户会收到多条内容相近的消息。 ## 调用后必须做的事 发送 demo 后,**必须立刻追问客户的具体需求或应用场景**,不得只发完就结束。 + +## 如果脚本失败 +- 若是 `收件人校验失败`:说明客户昵称拼写有误,检查客户库中的准确昵称(可用 `sqlite3` 查询 `cs_record.peer`),用完全一致的昵称重试一次。 +- 若是 Redis 连接失败:报告给用户/上级,不要反复重试刷屏。 +- 最多重试 1 次;仍失败则停止,如实告知客户"稍后为您补发",并汇报给上级。 diff --git a/docs/d21-symlink-skill.md b/docs/d21-symlink-skill.md index 06c5fdb1..3b98a7df 100644 --- a/docs/d21-symlink-skill.md +++ b/docs/d21-symlink-skill.md @@ -132,15 +132,15 @@ login-manager check douyin # wrapper 在 PATH 中 | email-ops | scripts/send_email.py | wrapper → py | | pexels-footage | scripts/pexels_search.py | wrapper → py | | pixabay-footage | scripts/pixabay_search.py | wrapper → py | -| siliconflow-img-gen | scripts/gen.py | wrapper → py | +| awk-img-gen | scripts/gen.py | wrapper → py | | wxwork-drive | scripts/drive.py | wrapper → py | | youtube-publish | scripts/publish_youtube.py | wrapper → py | | bilibili-publish | scripts/publish_bilibili.py | wrapper → py | | design-system-picker | scripts/pick.sh | wrapper → sh | | init-workspace | scripts/init.sh | wrapper → sh | | ~~manim-explainer~~ | scripts/render-manim.sh | wrapper → sh(**2026-09-10 技能删除**,wrapper 与 bin 软链由 `expose_skill_wrappers` 的悬挂清理回收) | -| siliconflow-tts | scripts/tts.py | wrapper → py | -| siliconflow-video-gen | scripts/gen.py | wrapper → py | +| awk-tts | scripts/tts.py | wrapper → py | +| ~~siliconflow-video-gen~~ | scripts/gen.py | wrapper → py(**已删除**,CP 视频生成统一走公共 `aigc-video-gen`) | | awada-channel-setup | scripts/apply-awada-config.py | wrapper → py | | icp-exemption | scripts/generate_pdf.py | wrapper → py | | icp-filing | scripts/icp.sh | wrapper → sh | @@ -282,7 +282,7 @@ dev plan §Phase 7 续 写"验收": 5. `skills/email-ops` 6. `skills/pexels-footage` 7. `skills/pixabay-footage` -8. `skills/siliconflow-img-gen` +8. `skills/awk-img-gen` 9. `skills/wxwork-drive` 10. `crews/main/skills/xhs-publish` 11. `crews/main/skills/xhs-content-ops` diff --git a/package.json b/package.json index 54ff191c..ee567e9f 100644 --- a/package.json +++ b/package.json @@ -1,3 +1,6 @@ { - "packageManager": "pnpm@11.2.2+sha512.36e6621fad506178936455e70247b8808ef4ec25797a9f437a93281a020484e2607f6a469a22e982987c3dbb8866e3071514ab10a4a1749e06edcd1ec118436f" + "packageManager": "pnpm@11.2.2+sha512.36e6621fad506178936455e70247b8808ef4ec25797a9f437a93281a020484e2607f6a469a22e982987c3dbb8866e3071514ab10a4a1749e06edcd1ec118436f", + "dependencies": { + "ioredis": "^6.0.0" + } } diff --git a/skills/README.md b/skills/README.md index ff483c83..eee5783c 100644 --- a/skills/README.md +++ b/skills/README.md @@ -18,5 +18,5 @@ | `pexels-footage` | Pexels 免费素材搜索下载 | main + content-producer 继承 | | `pixabay-footage` | Pixabay 免费素材搜索下载 | main + content-producer 继承 | | `wxwork-drive` | 企业微信微盘 | main + content-producer 继承 | -| `siliconflow-img-gen` | 硅基流动生图(Phase 5 改火山) | main + content-producer 继承 | +| `awk-img-gen` | 阿里云百炼生图/编辑(业务空间 qwen-image / agent plan wan2.7-image) | main + content-producer 继承 | | `youtube-publish` | YouTube 视频发布(Data API v3 + OAuth2) | main + content-producer 继承 | diff --git a/skills/aigc-video-gen/SKILL.md b/skills/aigc-video-gen/SKILL.md index c70a9675..1ac10bdc 100644 --- a/skills/aigc-video-gen/SKILL.md +++ b/skills/aigc-video-gen/SKILL.md @@ -20,12 +20,12 @@ metadata: | 平台 | 环境变量 | 视频模型 | 音乐模型 | |------|---------|---------|---------| -| 阿里云百炼(优先) | `MODELSTUDIO_API_KEY`(或 `DASHSCOPE_API_KEY`) | `happyhorse-1.1-i2v`、`happyhorse-1.1-t2v`、`happyhorse-1.1-r2v` | — | +| 阿里云百炼(优先) | 业务空间:`WORKSPACE_ID` + `MODELSTUDIO_API_KEY`(或 `DASHSCOPE_API_KEY`);agent plan:`AWK_API_KEY` | `happyhorse-1.1-i2v`、`happyhorse-1.1-t2v`、`happyhorse-1.1-r2v` | — | | 火山引擎方舟 | `AWK_GEN_KEY` | `doubao-seedance-2-0-fast-260128`、`doubao-seedance-2-0-260128`、`doubao-seedance-2-0-mini-260615` | — | | MiniMax Hailuo | `MINIMAX_API_KEY` | `MiniMax-H3` | `music-3.0` | - 三个平台的上述视频模型**均支持声画同出**(t2v / i2v / r2v 三种模式)。 -- **平台自动判断写在 `aigc-video-gen.sh` 里**:argv 含 `--platform ` 时转发到对应供应商脚本(剔除 `--platform` 参数);无 `--platform` 时按 env 自动判——有 `MINIMAX_API_KEY` 走 MiniMax,否则有 `AWK_GEN_KEY` 走火山,否则有 `MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY` 走百炼,三者皆无则输出提示让 Agent 改用 `pexels-footage` / `pixabay-footage`(退出码 2)。 +- **平台自动判断写在 `aigc-video-gen.sh` 里**:argv 含 `--platform ` 时转发到对应供应商脚本(剔除 `--platform` 参数);无 `--platform` 时按 env 自动判——有 `MINIMAX_API_KEY` 走 MiniMax,否则有 `AWK_GEN_KEY` 走火山,否则有 `MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY`/`WORKSPACE_ID` 走百炼,否则有 `AWK_API_KEY` 走百炼(agent plan 兜底,排最后:它是主模型 key,只在无显式视频平台凭据时触发),皆无则输出提示让 Agent 改用 `pexels-footage` / `pixabay-footage`(退出码 2)。 - **三供应商脚本拆分**(共享逻辑在 `scripts/aigc_common.py`,与三脚本同目录): - `scripts/gen_minimax.py` — MiniMax Hailuo 视频生成(含 `--ref-audio` 多模态参考)+ `music` 子命令 - `scripts/gen_volc.py` — 火山引擎 Seedance 视频生成 @@ -59,11 +59,15 @@ Agent 读到此报错后的处理流程: - 候选链(每模式一条):`happyhorse-1.1-{mode}` → `happyhorse-1.0-{mode}` → `wan2.7-{mode}`。首选模型不可用或任务失败时 `gen.py` 自动沿链降级,无需人工干预。 - **`--model ` 可显式覆盖**(关闭候选链 fallback,只用该模型);非必要不覆盖。 -### WORKSPACE_ID 端点规则 +### 百炼端点双模式规则 -配了 `WORKSPACE_ID` 时,happyhorse 走专属端点 `https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1`(华北2,更快);没配则走默认 `https://dashscope.aliyuncs.com/api/v1`。 +| 模式 | 触发条件 | 端点 | +|------|---------|------| +| 业务空间(优先) | `WORKSPACE_ID` + `MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY` | `https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1` | +| agent plan | 无业务空间凭据时用 `AWK_API_KEY` | `https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1` | +| legacy 兼容 | 都没有但 `MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY` 在 | `https://dashscope.aliyuncs.com/api/v1`(老部署) | -这个设置对于火山(doubao-seedance 系列模型)和 MiniMax 无效。 +> `WORKSPACE_ID` 配了但业务空间 key 缺失时打 warning 落 agent plan。端点模式对火山(doubao-seedance 系列)和 MiniMax 无效。 ### 火山候选链 @@ -188,11 +192,12 @@ aigc-video-gen music \ | Variable | Description | |----------|-------------| -| `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | 阿里云百炼 API key(优先平台) | +| `WORKSPACE_ID` + `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | 百炼业务空间(优先模式) | +| `AWK_API_KEY` | 百炼 agent plan key(token-plan 端点;无业务空间凭据时启用) | | `AWK_GEN_KEY` | 火山方舟视频生成专用 key(不可与 `ARK_API_KEY` 混用) | | `MINIMAX_API_KEY` | MiniMax API key(Hailuo-H3 视频生成 + 背景音乐生成共用) | | `WORKSPACE_ID` | 可选,百炼专属端点加速 | ## Fallback 路径 -`MODELSTUDIO_API_KEY`、`AWK_GEN_KEY`、`MINIMAX_API_KEY` 均未配 → `gen.py` 退出码 2,Agent 应改用 `pexels-footage` / `pixabay-footage` 走 Stock Footage 模式兜底。 +`MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY`/`WORKSPACE_ID`/`AWK_API_KEY`、`AWK_GEN_KEY`、`MINIMAX_API_KEY` 均未配 → `gen.py` 退出码 2,Agent 应改用 `pexels-footage` / `pixabay-footage` 走 Stock Footage 模式兜底。 diff --git a/skills/aigc-video-gen/aigc-video-gen.sh b/skills/aigc-video-gen/aigc-video-gen.sh index 89e2a82a..4e08df2d 100755 --- a/skills/aigc-video-gen/aigc-video-gen.sh +++ b/skills/aigc-video-gen/aigc-video-gen.sh @@ -10,8 +10,10 @@ # Dispatch 顺序: # 1. argv 含 --platform → 转发到对应 gen_*.py(剔除 --platform 参数) # 2. 否则按 env 自动判:MINIMAX_API_KEY → minimax;AWK_GEN_KEY → volcengine; -# MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY → dashscope -# 3. 三者皆无 → 输出提示让 Agent 改用 pexels-footage / pixabay-footage(退出码 2) +# MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY/WORKSPACE_ID → dashscope; +# AWK_API_KEY → dashscope(agent plan 模式,排最后:它是主模型 key, +# 只在没有任何显式视频平台凭据时才兜底触发百炼) +# 3. 皆无 → 输出提示让 Agent 改用 pexels-footage / pixabay-footage(退出码 2) set -euo pipefail SELF="${BASH_SOURCE[0]}" # Resolve symlink (wrapper is ln -sfn'd into ~/.openclaw/bin) so SCRIPT_DIR points at the real skill dir. @@ -55,11 +57,13 @@ if [ -z "$PLATFORM" ]; then PLATFORM="minimax" elif [ -n "${AWK_GEN_KEY:-}" ]; then PLATFORM="volcengine" - elif [ -n "${MODELSTUDIO_API_KEY:-}" ] || [ -n "${DASHSCOPE_API_KEY:-}" ]; then + elif [ -n "${MODELSTUDIO_API_KEY:-}" ] || [ -n "${DASHSCOPE_API_KEY:-}" ] || [ -n "${WORKSPACE_ID:-}" ]; then + PLATFORM="dashscope" + elif [ -n "${AWK_API_KEY:-}" ]; then PLATFORM="dashscope" else echo "[error] 未检测到任何视频生成平台的环境变量" >&2 - echo " (MODELSTUDIO_API_KEY / DASHSCOPE_API_KEY / AWK_GEN_KEY / MINIMAX_API_KEY 均未设置)。" >&2 + echo " (MODELSTUDIO_API_KEY / DASHSCOPE_API_KEY / WORKSPACE_ID / AWK_API_KEY / AWK_GEN_KEY / MINIMAX_API_KEY 均未设置)。" >&2 echo "[hint] 请改用 pexels-footage 和 pixabay-footage 技能搜集素材:" >&2 echo " 1) pexels-footage 搜索并下载 9:16 竖屏素材" >&2 echo " 2) pexels 无结果时用 pixabay-footage 兜底" >&2 diff --git a/skills/aigc-video-gen/scripts/gen_dashscope.py b/skills/aigc-video-gen/scripts/gen_dashscope.py index 36544343..ecff560a 100644 --- a/skills/aigc-video-gen/scripts/gen_dashscope.py +++ b/skills/aigc-video-gen/scripts/gen_dashscope.py @@ -9,12 +9,11 @@ 模型候选链(每模式一条): happyhorse-1.1-{mode} → happyhorse-1.0-{mode} → wan2.7-{mode} -鉴权:HTTP header `Authorization: Bearer ${MODELSTUDIO_API_KEY}`(或 DASHSCOPE_API_KEY)。 - -端点规则: - - 配了 WORKSPACE_ID 时,happyhorse 走专属端点 {WorkspaceId}.cn-beijing.maas.aliyuncs.com(更快) - - 没配则走默认 dashscope.aliyuncs.com - - wan2.7 始终走默认端点 +端点/key 双模式(2026-09 provider 收敛,ds_resolve): + - 业务空间(优先):WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY + → https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1 + - agent plan:否则 AWK_API_KEY → https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1 + - legacy 兼容:都没有但 MODELSTUDIO/DASHSCOPE 在 → 默认 dashscope.aliyuncs.com """ from __future__ import annotations @@ -44,8 +43,9 @@ # ---- 百炼端点与常量 ----------------------------------------------------------- -DS_DEFAULT_BASE = "https://dashscope.aliyuncs.com/api/v1" +DS_DEFAULT_BASE = "https://dashscope.aliyuncs.com/api/v1" # legacy 兼容端点 DS_WS_BASE_TEMPLATE = "https://{wsid}.cn-beijing.maas.aliyuncs.com/api/v1" +DS_AGENT_PLAN_BASE = "https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1" DS_CREATE_PATH = "/services/aigc/video-generation/video-synthesis" DS_QUERY_PATH = "/tasks/{task_id}" @@ -62,17 +62,30 @@ DS_TIMEOUT = 900 -def ds_base_for_model(model: str) -> str: - """Resolve the DashScope base URL for a given model. +def ds_resolve() -> tuple[str, str, str]: + """解析百炼端点模式。返回 (base, api_key, mode)。 - happyhorse-1.1 / 1.0 在默认 dashscope.aliyuncs.com 端点可正常调用(WorkspaceId 端点 - 只是华北2的性能优化,非必需)。WORKSPACE_ID 设置时走专属端点更快,否则走默认。 - wan2.7 始终走默认端点。 + - 业务空间(优先):WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY + - agent plan:否则 AWK_API_KEY(token-plan 端点) + - legacy 兼容:都没有但 MODELSTUDIO/DASHSCOPE 在 → 默认端点(老部署无 AWK_API_KEY 时) + - 都不可用 → die """ wsid = (os.environ.get("WORKSPACE_ID") or "").strip() - if model.startswith("happyhorse") and wsid: - return DS_WS_BASE_TEMPLATE.format(wsid=wsid) - return DS_DEFAULT_BASE + ws_key = ( + os.environ.get("MODELSTUDIO_API_KEY") + or os.environ.get("DASHSCOPE_API_KEY") + or "" + ).strip() + if wsid and ws_key: + return DS_WS_BASE_TEMPLATE.format(wsid=wsid), ws_key, "workspace" + if wsid: + log("WORKSPACE_ID 已配置但 MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY 缺失,尝试 agent plan") + awk_key = (os.environ.get("AWK_API_KEY") or "").strip() + if awk_key: + return DS_AGENT_PLAN_BASE, awk_key, "agent-plan" + if ws_key: + return DS_DEFAULT_BASE, ws_key, "legacy" + die("百炼视频生成凭据未配置:需 WORKSPACE_ID+MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY(业务空间)或 AWK_API_KEY(agent plan)") # ---- 百炼视频生成 ------------------------------------------------------------- @@ -166,7 +179,7 @@ def ds_candidates(args: argparse.Namespace, mode: str) -> list[str]: def run_one(platform: str, model: str, args: argparse.Namespace, api_key: str) -> str: """Submit + poll for a single DashScope model. Returns video URL or raises.""" - base = ds_base_for_model(model) + base, _key, _mode = ds_resolve() task_id = ds_submit(model, args, api_key, base) log(f"dashscope task submitted: {task_id} (model={model} base={base})") return ds_poll(task_id, api_key, base) @@ -179,13 +192,7 @@ def cmd_video(args: argparse.Namespace) -> None: # --prev-segment: 抽取上一段末帧作为本段首帧(人物故事首尾帧对齐) resolve_prev_segment(args) - api_key = ( - os.environ.get("MODELSTUDIO_API_KEY") - or os.environ.get("DASHSCOPE_API_KEY") - or "" - ).strip() - if not api_key: - die("MODELSTUDIO_API_KEY / DASHSCOPE_API_KEY 未设置") + base, api_key, provider_mode = ds_resolve() has_ref = bool(args.ref_image or args.ref_video) has_i2v = bool(args.image or args.last_frame) @@ -197,7 +204,7 @@ def cmd_video(args: argparse.Namespace) -> None: output_path.parent.mkdir(parents=True, exist_ok=True) log( - f"platform=dashscope mode={mode} candidates={candidates} " + f"platform=dashscope provider={provider_mode} mode={mode} candidates={candidates} " f"duration={args.duration}s ratio={args.ratio} resolution={args.resolution}" ) video_url = generate("dashscope", candidates, args, api_key, run_one) @@ -208,6 +215,7 @@ def cmd_video(args: argparse.Namespace) -> None: json.dumps( { "platform": "dashscope", + "provider_mode": provider_mode, "mode": mode, "model_candidates": candidates, "duration": args.duration, diff --git a/skills/awk-img-gen/SKILL.md b/skills/awk-img-gen/SKILL.md index b7edff35..9b88d213 100644 --- a/skills/awk-img-gen/SKILL.md +++ b/skills/awk-img-gen/SKILL.md @@ -1,141 +1,88 @@ --- -name: siliconflow-img-gen -description: Generate or edit images via 火山方舟 (Volcengine Ark) Seedream API. - Text-to-image default model doubao-seedream-4.5(fallback doubao-seedream-5.0-lite); - image-edit supported via 1-3 reference images. Uses user's AWK_GEN_KEY - (client-side only, never sent to server). +name: awk-img-gen +description: 阿里云百炼图像生成/编辑。业务空间(WORKSPACE_ID)走 qwen-image-3.0 系候选链,agent plan 走 wan2.7-image;文生图默认,1-3 张参考图触发编辑/多图融合。封面海报直接渲染文字,不要后期拼字。 metadata: openclaw: emoji: 🖼️ requires: bins: - - python3 - env: - - AWK_GEN_KEY - primaryEnv: AWK_GEN_KEY - homepage: https://www.volcengine.com/docs/82379/1541523 + - python3 + homepage: https://docs.bailian.console.aliyun.com/zh/model-studio/qwen-image-generation-and-editing-api-reference --- -# 火山方舟 Seedream 图像生成 +# 阿里云百炼图像生成(awk-img-gen) -> **凭据**:用户自带 `AWK_GEN_KEY`(纯客户端,不入 server)。 +走百炼 DashScope 同步 `multimodal-generation` 接口生成/编辑图片,落盘 PNG + `prompts.json` 索引 + `index.html` 缩略图 gallery。 -Generate or edit images using 火山方舟 (Volcengine Ark) Seedream API. - -Endpoint: `POST https://ark.cn-beijing.volces.com/api/v3/images/generations` - -Two modes: -- **Text-to-image** — default model `doubao-seedream-4.5`(主力不可用时自动 fallback 到 `doubao-seedream-5.0-lite`) -- **Image-edit** — `--image` 触发;4.5+ 支持多图参考(1-3 张) - -参考文档: - ---- +> **凭据**(双模式,自动判断,无需 `--platform` 类参数): +> +> | 模式 | 触发条件 | 端点 | 模型候选链 | +> |------|---------|------|-----------| +> | 业务空间(优先) | `WORKSPACE_ID` + `MODELSTUDIO_API_KEY`(或 `DASHSCOPE_API_KEY`) | `https://{WORKSPACE_ID}.cn-beijing.maas.aliyuncs.com/api/v1` | `qwen-image-3.0-pro` → `qwen-image-3.0` → `qwen-image-2.0-pro-2026-06-22` | +> | agent plan | 无业务空间凭据时用 `AWK_API_KEY` | `https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1` | `wan2.7-image-pro` → `wan2.7-image` | +> +> 候选链自动 fallback(模型未开通/未找到/无权限时切下一个);`--model` 显式指定时关闭 fallback。 +> 两套凭据都缺 → exit 1 并打印配置指引;实拍图兜底改走 `pexels-footage` / `pixabay-footage`。 +> 应该 spawn IT engineer subagent 配置环境变量,**不要自己写环境变量文件**。 ## Run -Note: Image generation can take 10–60 seconds. Set a higher timeout when invoking via exec (e.g., `exec timeout=120`). +Note: 图像生成可能耗时 10–120 秒(qwen-image-3.0-pro 更慢)。exec 调用时把 timeout 设高(如 `exec timeout=300`)。 -**Do NOT set env vars inline** (e.g., `AWK_GEN_KEY=... python3 ...`). The env var is already in the system environment; inline assignments break the exec permission check. +**Do NOT set env vars inline**(例如 `AWK_API_KEY=... python3 ...`)。env var 已在系统环境里,inline 赋值会破坏 exec 权限检查。 -通过 PATH 调用 wrapper,无需拼接脚本路径。 +通过 PATH 调用 wrapper,无需拼接脚本路径: ```bash -# Text-to-image (default model: doubao-seedream-4.5,失败自动 fallback doubao-seedream-5.0-lite) -siliconflow-img-gen --prompt "your prompt here" +# Text-to-image(模式/模型/候选链全自动,默认 2048x2048) +awk-img-gen --prompt "your prompt here" -# 指定其他 model(显式指定时不 fallback) -siliconflow-img-gen --prompt "your prompt" --model "doubao-seedream-5.0-lite" -siliconflow-img-gen --prompt "your prompt" --model "doubao-seedream-3-0-t2i-250415" - -# Image-edit(1-3 张参考图) -siliconflow-img-gen --prompt "add a lighthouse" --image "https://example.com/source.jpg" -siliconflow-img-gen --prompt "blend" \ - --image "https://example.com/a.jpg" \ - --image2 "https://example.com/b.jpg" \ - --image3 "https://example.com/c.jpg" -``` +# 竖版 9:16(短视频封面) +awk-img-gen --prompt "..." --image-size 1536x2688 -### Text-to-image examples +# 指定模型(显式指定时不 fallback) +awk-img-gen --prompt "..." --model "qwen-image-2.0-pro-2026-06-22" -```bash -# Square 2K (default) -siliconflow-img-gen --prompt "a futuristic city at dusk" - -# Landscape 16:9 -siliconflow-img-gen --prompt "mountain lake" --image-size 2848x1600 - -# Portrait 9:16 -siliconflow-img-gen --prompt "mountain lake" --image-size 1600x2848 - -# Quality preset (火山的 2K / 3K / 4K 方式 1) -siliconflow-img-gen --prompt "4K 超清" --image-size 4K - -# Save to specific directory -siliconflow-img-gen --prompt "sunset" --out-dir ./out/images +# Image-edit(1-3 张参考图;URL / data URI / 本地路径均可) +awk-img-gen --prompt "add a lighthouse" --image "https://example.com/source.jpg" +awk-img-gen --prompt "blend" \ + --image ./tmp/ref-a.png \ + --image2 ./tmp/ref-b.png \ + --image3 ./tmp/ref-c.png ``` -### Image-edit examples - -```bash -# 单图生图 -siliconflow-img-gen --prompt "make it night time" --image "https://example.com/photo.jpg" - -# 多图融合(3 张) -siliconflow-img-gen --prompt "blend these photos" \ - --image "https://example.com/a.jpg" \ - --image2 "https://example.com/b.jpg" \ - --image3 "https://example.com/c.jpg" -``` - ---- - ## Parameters | Flag | Default | Description | |------|---------|-------------| -| `--prompt` | required | 图像描述(≤300 汉字 / 600 英文) | -| `--model` | auto | Model ID;默认 `doubao-seedream-4.5`(主力不可用自动 fallback `doubao-seedream-5.0-lite`);显式指定时不 fallback | -| `--image-size` | `2048x2048` | 文生图尺寸。方式 1: `2K`/`3K`/`4K`;方式 2: `WxH`(如 2048x2048) | -| `--seed` | — | 随机种子 | +| `--prompt` | required | 图像描述;要渲染的文字**直接写完整句子**(≤ 约1300 token,超长静默截断) | +| `--model` | auto | Model ID;缺省按模式走候选链自动 fallback,显式指定时不 fallback | +| `--image-size` | `2048x2048` | `WxH`(总像素 512²~2048²,宽高比 1:8~8:1)或 `auto`;编辑模式缺省跟随输入图 | +| `--seed` | — | 随机种子 [0, 2147483647],需要可复现时设固定值 | | `--watermark` | `false` | 是否加水印(xiaobei 默认不加,避免后续 image 工具处理) | -| `--response-format` | `url` | `url`(链接 24h 有效)/ `b64_json` | -| `--image` | — | 源图 URL 或 Base64(启用 image-edit 模式) | -| `--image2` | — | 第二张参考图(image-edit) | -| `--image3` | — | 第三张参考图(image-edit) | +| `--prompt-extend` | off | 允许百炼自动扩写 prompt(API 默认开;本脚本默认**关**,保证封面文字/布局指令精确;氛围图想要更丰富细节时可开) | +| `--image` | — | 参考图 1(启用编辑模式) | +| `--image2` / `--image3` | — | 参考图 2 / 3(多图融合) | | `--out-dir` | `./tmp/awk-img-` | 输出目录 | -### Valid `--image-size` values - -> **火山方舟图像生成 size 限制**(参考 [API 文档](https://www.volcengine.com/docs/82379/1541523)): -> - 方式 1(quality 预设):`2K` / `3K` / `4K` -> - 方式 2(WxH):总像素 ∈ [2560×1440=3686400, 4096×4096=16777216];宽高比 ∈ [1/16, 16] -> -> 无效值会被脚本拒绝并 exit 1,错误信息列出所有合法选项。 - -**2K 推荐宽高像素值**(默认 quality 档): +### 推荐尺寸(qwen-image 文档推荐值) | Value | Ratio | |-------|-------| -| `2048x2048` | 1:1 (default) | -| `2304x1728` | 4:3 | -| `1728x2304` | 3:4 | -| `2848x1600` | 16:9 | -| `1600x2848` | 9:16 | -| `2496x1664` | 3:2 | -| `1664x2496` | 2:3 | -| `3136x1344` | 21:9 | +| `2048x2048` | 1:1(默认) | +| `2688x1536` | 16:9 | +| `1536x2688` | 9:16 | +| `2368x1728` | 4:3 | +| `1728x2368` | 3:4 | ---- +> 总像素须在 512×512 ~ 2048×2048 之间,宽高比 1:8 ~ 8:1;无效值脚本拒绝并 exit 1 列出推荐值。 ## Output -- `*.jpg` 图像(火山默认 jpeg 输出;URL 24h 有效,按脚本约定下载到本地) -- `prompts.json` 索引 → prompt + URL + file +- `*.png` 图像(百炼输出 PNG;URL 24h 有效,脚本已下载到本地) +- `prompts.json` 索引 → prompt + model + provider_mode + URL + file - `index.html` 缩略图 gallery ---- - ## 视频封面/海报最佳实践 适用于**图文混合素材**(短视频封面、社媒海报、信息图配图等)——需要模型一次性渲染文字与画面,而不是后期合成。 @@ -144,9 +91,9 @@ siliconflow-img-gen --prompt "blend these photos" \ | 参数 | 推荐值 | 原因 | |------|--------|------| -| `--model` | `doubao-seedream-4.5` | 4.5 在文字渲染 / 中文支持上稳定 | -| `--image-size` | `2K` 或 `4K` | 高分辨率给文字留细节空间 | -| 比例 | 9:16 / 16:9 / 1:1 按平台选 | 火山 way 2 任意合法比例 | +| `--model` | 缺省(走候选链主力) | qwen-image-3.0-pro / wan2.7-image-pro 文字渲染与中文支持最好 | +| `--image-size` | 按平台比例选推荐尺寸 | 9:16 用 `1536x2688`,16:9 用 `2688x1536` | +| `--prompt-extend` | 不加(保持默认关) | 扩写会改写你精心排版的文字与布局指令 | ### 2. Prompt 写法(关键) @@ -163,106 +110,18 @@ siliconflow-img-gen --prompt "blend these photos" \ - **明确要求**:"sharp text rendering"、"professional Chinese typography" - 末尾加 "no watermarks" 排除水印(与脚本 `--watermark false` 双保险) -### 3. 可选设置 - -- `--watermark false`:必加(xiaobei 默认) -- `--seed N`:需要可复现时设固定值 -- `--response-format b64_json`:避免依赖 URL 24h 失效(脚本默认 url) - -### 4. 生成后必须验证 +### 3. 生成后必须验证 1. 用 `image` 工具分析图片,**逐项确认**: - ✅ 文字内容是否完全正确(不能错字、漏字、出现乱码字符) - - ✅ 文字位置/对齐/排版是否符合预期 - - ✅ **没有意外的 logo/水印/UI 元素**(如有,加 `--negative-prompt`-like 描述重生成) - - ✅ 主体内容符合 prompt -2. 异常则重新生成(最多 3 次),仍异常则标记失败继续 - -### 5. 输出格式 - -- 脚本默认 **JPG**(火山 4.5 默认 jpeg;5.0 lite 可选 png) -- 如果目标平台需要 **PNG**(如透明背景场景),调 `--response-format b64_json` + 后续转码 -- 任何格式转换都用 PIL,**不做任何像素修改** - -### 6. 完整示例 - -```bash -siliconflow-img-gen \ - --prompt "A dramatic vertical 9:16 short-video cover with bold red and black gradient background, glowing AI chip icons floating in the upper area, and large bold Chinese text '前几周 DeepSeek 还是神一般的存在' in the middle in white and gold gradient with sharp shadows. At the bottom, dramatic glowing red Chinese text '为什么热度消散得这么快?' with lightning-like effects. Visual style: high contrast, modern tech poster, dramatic lighting, professional Chinese typography, sharp text rendering, cinematic, no watermarks" \ - --image-size 1600x2848 \ - --watermark false \ - --out-dir /path/to/output -``` - ---- - -## ⚠️ 生成后必须验证 - -每张图生成后**必须**用 `image` 工具检查(不得跳过): - -- ❌ **异常**:纯色背景 / 文字乱码 / 意外 logo → 重新生成 -- ✅ **正常**:符合 prompt 描述 → 继续下一步 - -最多重试 3 次,仍异常则标记失败并继续后续任务。 - ---- - -## Model 选择速查 - -| Model | 适用场景 | 备注 | -|-------|---------|------| -| `doubao-seedream-4.5` | 默认 / 通用(主力) | 文字渲染稳,多图融合支持;不可用时脚本自动 fallback | -| `doubao-seedream-5.0-lite` | fallback / 组图 / 工具调用 | 主力失败时自动切换;5.0 lite 起支持 tools/web_search | -| `doubao-seedream-3-0-t2i-250415` | 纯文生图(无 image edit) | 3.0 旧版,仅 t2i,需 `--model` 显式指定 | - ---- - -## 🔧 模型开通指引(主力 + fallback 都失败时反馈给用户) - -当 `doubao-seedream-4.5`(主力)与 `doubao-seedream-5.0-lite`(fallback)**都无法使用**时(典型表现:HTTP 400/403/404,或脚本 stderr 输出 `[guide] 请到火山引擎后台开通视觉模型`),说明用户的火山方舟账号尚未开通这两个视觉模型。**Agent 应主动把下面的开通步骤反馈给用户**,不要静默失败或反复重试。 - -### 开通步骤 - -1. 打开火山引擎方舟后台: -2. 左侧「**系统管理**」→「**开通管理**」→「**视觉模型**」 -3. 在列表中找到 **`doubao-seedream-4.5`** 和 **`doubao-seedream-5.0-lite`** 两项,分别点右侧「**开通服务**」 - -### ⚠️ 免费额度与付费提醒(务必告知用户) - -目前火山方舟 CodePlan 有活动:在上述视觉模型开通页面上方可参加**领取免费资源包**活动。 - -| 模型 | 免费额度 | -|------|---------| -| `doubao-seedream-4.5` | 送 200 张图 | -| `doubao-seedream-5.0-lite` | 送 50 张图 | - -**重要提醒(必须告诉用户)**: - -- 免费额度用光之后,**除非手动关闭服务**,否则将**自动进入付费模式**,产生额外费用。 -- 这部分图像生成费用**不在 CodePlan 套餐内**,需额外付费。 -- 具体收费标准参见视觉模型页面上的定价说明。 -- 建议用户按需开通,并在免费额度即将用尽时评估是否继续使用付费模式。 - ---- - -## Pitfalls - -### pitfall: API key 错误(401) - -- **症状**:`HTTPError 401` -- **workaround**:检查 `AWK_GEN_KEY` 环境变量是否设置;火山 Ark 控制台 (`console.volcengine.com/ark`) 验证 key 有效 - -### pitfall: size 太小被拒 - -- **症状**:`HTTPError 400` + 提示 size invalid -- **workaround**:用 `2K` / `3K` / `4K` 预设,或按 [API 文档](https://www.volcengine.com/docs/82379/1541523) 调整 WxH(总像素 ≥ 2560x1440) - -### pitfall: URL 24h 失效 - -- **症状**:生成成功后未及时下载图片,URL 过期 -- **workaround**:脚本默认 `response_format=url` 且立刻下载到 `--out-dir`;如需长期保留,用 `--response-format b64_json` + - ✅ 文字是否清晰可读(无模糊、无变形) + - ✅ 布局是否符合预期 +2. 文字渲染错误 → 调整 prompt 重新生成(可加 `--seed` 复现好的构图再微调文字),**不要**交付错字封面 -### pitfall: watermark 默认 true(火山端) +## Environment Variables -- **症状**:图右下角出现"AI 生成"水印 -- **workaround**:脚本默认 `--watermark false`;不要漏写 +| Variable | Description | +|----------|-------------| +| `WORKSPACE_ID` | 百炼业务空间 ID;配置后优先走业务空间端点 | +| `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | 业务空间 API key(与 `WORKSPACE_ID` 配对) | +| `AWK_API_KEY` | 百炼 agent plan key(token-plan 端点;无业务空间凭据时使用) | diff --git a/skills/awk-img-gen/awk-img-gen.sh b/skills/awk-img-gen/awk-img-gen.sh index 714acd3a..da55aa30 100755 --- a/skills/awk-img-gen/awk-img-gen.sh +++ b/skills/awk-img-gen/awk-img-gen.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# siliconflow-img-gen.sh — siliconflow-img-gen 顶层 wrapper(薄转发) -# 让 agent 用 `siliconflow-img-gen ` 走 PATH,零路径拼接。 +# awk-img-gen.sh — awk-img-gen 顶层 wrapper(薄转发) +# 让 agent 用 `awk-img-gen ` 走 PATH,零路径拼接。 # 内部转发到 scripts/gen.py;wrapper 自身只是 exec 转发,不改语义。 set -euo pipefail SELF="${BASH_SOURCE[0]}" diff --git a/skills/awk-img-gen/scripts/gen.py b/skills/awk-img-gen/scripts/gen.py index 19660fa1..e421cd35 100755 --- a/skills/awk-img-gen/scripts/gen.py +++ b/skills/awk-img-gen/scripts/gen.py @@ -1,21 +1,30 @@ #!/usr/bin/env python3 -"""火山方舟 Seedream 图像生成(Phase 5 — D13 决策:img-gen Key 用户自带) - -替代原 SiliconFlow (Qwen) 生图路径。改调火山方舟 Ark 平台 -`/api/v3/images/generations` 端点(不是 `/api/coding/v3`,后者是 LLM 编码 -路径;图像生成走标准推理 `/v3`)。 - -API key 走用户自带 `AWK_GEN_KEY` 环境变量(纯客户端,不入 server)。 - -支持模型: - - doubao-seedream-4.5(默认,主力) - - doubao-seedream-5.0-lite(fallback:主力不可用时自动切换) - - doubao-seedream-3-0-t2i-250415(3.0 旧版,纯文生图,需 --model 显式指定) - -参考:https://www.volcengine.com/docs/82379/1541523 +"""阿里云百炼图像生成/编辑(awk-img-gen)— stdlib only. + +Provider 收敛(2026-09 拍板):SiliconFlow(skill 旧名残留)→ 火山 Seedream(Phase 5)→ 阿里云百炼(现在)。 + +双模式(resolve_mode): + - 业务空间:WORKSPACE_ID 配置时优先 → https://{wsid}.cn-beijing.maas.aliyuncs.com/api/v1 + key = MODELSTUDIO_API_KEY / DASHSCOPE_API_KEY + 模型候选链:qwen-image-3.0-pro → qwen-image-3.0 → qwen-image-2.0-pro-2026-06-22 + - agent plan:否则 → https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1 + key = AWK_API_KEY + 模型候选链:wan2.7-image-pro → wan2.7-image + +同步接口:POST {base}/services/aigc/multimodal-generation/generation +请求体(DashScope messages 风格,单轮,input_audio 家族同款端点): + {model, input:{messages:[{role:"user", + content:[{image:}×1-3(编辑模式), {text:}]}]}, + parameters:{size?, seed?, watermark, prompt_extend}} +响应:output.choices[0].message.content[*].image 为图片 URL(24h 有效),下载落盘。 + +参考:docs.bailian.console.aliyun.com「千问-图像生成与编辑 3.0 / qwen-image 2.0」 +与 modelstudioai/cli packages/core/src/client/image-routes.ts(sync-multimodal 家族)。 """ import argparse +import base64 import json +import mimetypes import os import re import sys @@ -23,137 +32,172 @@ import urllib.error import urllib.request from pathlib import Path -from typing import Optional - -# ── 常量 ───────────────────────────────────────────────────────────────────── - -API_URL = "https://ark.cn-beijing.volces.com/api/v3/images/generations" - -# 火山方舟支持的 model 列表 -# 主力 doubao-seedream-4.5;fallback doubao-seedream-5.0-lite(主力不可用时自动切换) -DEFAULT_GEN_MODEL = "doubao-seedream-4.5" -DEFAULT_EDIT_MODEL = "doubao-seedream-4.5" # 4.5 支持 image edit -FALLBACK_MODEL = "doubao-seedream-5.0-lite" -# 触发 fallback 的 HTTP 状态码(模型未开通 / 未找到 / 配额不足等模型层错误) -MODEL_UNAVAILABLE_CODES = {400, 403, 404} -DEFAULT_SIZE = "2048x2048" # 火山默认 1:1 - -# 火山 size 校验(方式 2:宽x高) -# - 总像素范围 [2560x1440=3686400, 4096x4096=16777216] -# - 宽高比范围 [1/16, 16] -MIN_TOTAL_PIXELS = 2560 * 1440 # 3686400 -MAX_TOTAL_PIXELS = 4096 * 4096 # 16777216 -MIN_ASPECT_RATIO = 1 / 16 -MAX_ASPECT_RATIO = 16 - -# 火山方式 1:固定 2K/3K/4K -SIZE_PRESETS_QUALITY = {"2K", "3K", "4K"} - -# 推荐的 2K 分辨率(用户用得最多的) -SIZE_PRESETS_2K = { - "2048x2048": "1:1", - "2304x1728": "4:3", - "1728x2304": "3:4", - "2848x1600": "16:9", - "1600x2848": "9:16", - "2496x1664": "3:2", - "1664x2496": "2:3", - "3136x1344": "21:9", + +# ── 端点与模型 ──────────────────────────────────────────────────────────────── + +WS_BASE_TEMPLATE = "https://{wsid}.cn-beijing.maas.aliyuncs.com/api/v1" +AGENT_PLAN_BASE = "https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1" +GEN_PATH = "/services/aigc/multimodal-generation/generation" + +# 业务空间模型候选链(主力 → fallback,用户 --model 显式指定时关闭 fallback) +WS_MODEL_CHAIN = ["qwen-image-3.0-pro", "qwen-image-3.0", "qwen-image-2.0-pro-2026-06-22"] +# agent plan 模型候选链 +PLAN_MODEL_CHAIN = ["wan2.7-image-pro", "wan2.7-image"] + +# 触发候选链 fallback 的 HTTP 状态码(模型未开通 / 未找到 / 无权限) +MODEL_UNAVAILABLE_CODES = {403, 404} +# 400 需结合 body 判断(可能是模型不存在,也可能是参数错——参数错不 fallback) +MODEL_ERROR_BODY_HINTS = ("modelnotfound", "model not found", "not exist", "unsupported model", "access denied") + +REQUEST_TIMEOUT = 300 + +# ── size 校验(qwen-image 系文档:总像素 [512*512, 2048*2048],宽高比 [1/8, 8])── + +DEFAULT_SIZE = "2048*2048" # 1:1 +MIN_TOTAL_PIXELS = 512 * 512 # 262144 +MAX_TOTAL_PIXELS = 2048 * 2048 # 4194304 +MIN_ASPECT_RATIO = 1 / 8 +MAX_ASPECT_RATIO = 8 + +# 推荐尺寸(qwen-image 2.0/3.0 文档推荐值,DashScope 风格 * 分隔) +SIZE_PRESETS = { + "2048*2048": "1:1 (默认)", + "2688*1536": "16:9", + "1536*2688": "9:16", + "2368*1728": "4:3", + "1728*2368": "3:4", +} + +IMAGE_MIME_BY_EXT = { + ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", + ".bmp": "image/bmp", ".tiff": "image/tiff", ".webp": "image/webp", } -RETRYABLE_STATUS_CODES = {403, 404, 429, 500, 503, 504} +# ── 模式解析 ───────────────────────────────────────────────────────────────── -# ── 校验 ───────────────────────────────────────────────────────────────────── +def resolve_mode() -> tuple[str, str, list[str], str]: + """解析百炼端点模式。返回 (base, api_key, model_chain, mode)。 + + 优先业务空间(WORKSPACE_ID + MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY); + WORKSPACE_ID 配了但 key 缺失时打 warning 落到 agent plan; + 否则 agent plan(AWK_API_KEY)。都不可用时报错退出。 + """ + wsid = (os.environ.get("WORKSPACE_ID") or "").strip() + if wsid: + key = ( + os.environ.get("MODELSTUDIO_API_KEY") + or os.environ.get("DASHSCOPE_API_KEY") + or "" + ).strip() + if key: + return WS_BASE_TEMPLATE.format(wsid=wsid), key, WS_MODEL_CHAIN, "workspace" + print("[warn] WORKSPACE_ID 已配置但 MODELSTUDIO_API_KEY/DASHSCOPE_API_KEY 缺失,尝试 agent plan", file=sys.stderr) + key = (os.environ.get("AWK_API_KEY") or "").strip() + if key: + return AGENT_PLAN_BASE, key, PLAN_MODEL_CHAIN, "agent-plan" + print("[error] 百炼生图凭据未配置:", file=sys.stderr) + print(" - 业务空间:WORKSPACE_ID + MODELSTUDIO_API_KEY(或 DASHSCOPE_API_KEY)", file=sys.stderr) + print(" - agent plan:AWK_API_KEY(token-plan 端点)", file=sys.stderr) + sys.exit(1) + + +# ── size 处理 ──────────────────────────────────────────────────────────────── def _parse_size(size_str: str) -> tuple[int, int] | None: - """解析 WxH 字符串。失败返回 None。""" - m = re.match(r"^\s*(\d+)\s*[xX×]\s*(\d+)\s*$", size_str) + """解析 WxH / W*H / W×H 字符串。失败返回 None。""" + m = re.match(r"^\s*(\d+)\s*[xX×*]\s*(\d+)\s*$", size_str) if not m: return None return int(m.group(1)), int(m.group(2)) -def validate_size(size_str: str) -> str: - """校验火山 size 参数;无效报错退出。""" - if size_str in SIZE_PRESETS_QUALITY: - return size_str +def normalize_size(size_str: str) -> str: + """校验并规范化 size 为 DashScope 风格 'W*H';'auto' 原样放行;无效报错退出。""" + if size_str.strip().lower() == "auto": + return "auto" parsed = _parse_size(size_str) if parsed is None: - _print_size_error(size_str, "格式必须是 'WxH' 或 '2K/3K/4K'") + _print_size_error(size_str, "格式必须是 'WxH'(或 W*H)或 'auto'") sys.exit(1) w, h = parsed total = w * h ratio = w / h if h != 0 else 0 if total < MIN_TOTAL_PIXELS: - _print_size_error( - size_str, - f"总像素 {total} 低于火山最小值 {MIN_TOTAL_PIXELS}(2560x1440)", - ) + _print_size_error(size_str, f"总像素 {total} 低于百炼最小值 {MIN_TOTAL_PIXELS}(512x512)") sys.exit(1) if total > MAX_TOTAL_PIXELS: - _print_size_error( - size_str, - f"总像素 {total} 高于火山最大值 {MAX_TOTAL_PIXELS}(4096x4096)", - ) + _print_size_error(size_str, f"总像素 {total} 高于百炼最大值 {MAX_TOTAL_PIXELS}(2048x2048)") sys.exit(1) if ratio < MIN_ASPECT_RATIO or ratio > MAX_ASPECT_RATIO: - _print_size_error( - size_str, - f"宽高比 {ratio:.4f} 超出火山范围 [{MIN_ASPECT_RATIO:.4f}, {MAX_ASPECT_RATIO}]", - ) + _print_size_error(size_str, f"宽高比 {ratio:.4f} 超出百炼范围 [1/8, 8]") sys.exit(1) - return size_str + return f"{w}*{h}" def _print_size_error(size_str: str, reason: str) -> None: print(f"[error] --image-size '{size_str}' 无效:{reason}", file=sys.stderr) - print("[info] 合法选项:", file=sys.stderr) - print(" 方式 1(quality 预设):2K / 3K / 4K", file=sys.stderr) - print(" 方式 2(2K 1:1 / 4:3 / 16:9 / 9:16 等):", file=sys.stderr) - for s, r in SIZE_PRESETS_2K.items(): + print("[info] 推荐尺寸(总像素 512x512 ~ 2048x2048,宽高比 1:8 ~ 8:1):", file=sys.stderr) + for s, r in SIZE_PRESETS.items(): print(f" {s} ({r})", file=sys.stderr) +# ── 图像引用解析 ────────────────────────────────────────────────────────────── + +def resolve_image_ref(value: str) -> str: + """把 --image 入参解析为百炼可接受的引用:URL / data URI 原样,本地文件转 data URI。""" + if value.startswith(("http://", "https://", "data:")): + return value + path = Path(value) + if not path.is_file(): + print(f"[error] 参考图不存在: {value}", file=sys.stderr) + sys.exit(1) + mime = IMAGE_MIME_BY_EXT.get(path.suffix.lower()) or mimetypes.guess_type(str(path))[0] or "image/png" + b64 = base64.b64encode(path.read_bytes()).decode("ascii") + return f"data:{mime};base64,{b64}" + + # ── Payload 构造 ───────────────────────────────────────────────────────────── def build_payload(args: argparse.Namespace, model: str) -> dict: - """构造火山方舟 images/generations 请求体(model 由调用方传入,便于 fallback)。""" + """构造百炼 multimodal-generation 请求体(model 由调用方传入,便于候选链 fallback)。""" + content: list[dict] = [] + + # 编辑模式:1-3 张参考图在前,text 在后(qwen-image 3.0/2.0 与 wan2.7-image 同构) is_edit_mode = bool(args.image) - payload: dict = { - "model": model, - "prompt": args.prompt, - } + if is_edit_mode: + for ref in (args.image, args.image2, args.image3): + if ref: + content.append({"image": resolve_image_ref(ref)}) - # 图生图(image / image2 / image3) + content.append({"text": args.prompt}) + + parameters: dict = { + "watermark": bool(args.watermark), + # 默认关 prompt 改写:封面/海报场景要精确渲染指定文字,扩写会破坏布局指令。 + # 需要模型自动扩写润色时显式 --prompt-extend。 + "prompt_extend": bool(args.prompt_extend), + } + if args.seed is not None: + parameters["seed"] = args.seed if is_edit_mode: - # 火山支持单图或多图(URL / Base64 数组) - images = [args.image] - if args.image2: - images.append(args.image2) - if args.image3: - images.append(args.image3) - payload["image"] = images if len(images) > 1 else images[0] + # 编辑模式默认不指定 size(跟随输入图);显式传了才带上 + if args.image_size: + parameters["size"] = normalize_size(args.image_size) else: - # 文生图:size 必填(火山默认 2048x2048) - size = args.image_size or DEFAULT_SIZE - payload["size"] = validate_size(size) - - # 可选参数 - if args.watermark is not None: - payload["watermark"] = args.watermark - if args.response_format: - payload["response_format"] = args.response_format - if args.seed is not None: - payload["seed"] = args.seed + parameters["size"] = normalize_size(args.image_size or DEFAULT_SIZE) - return payload + return { + "model": model, + "input": {"messages": [{"role": "user", "content": content}]}, + "parameters": parameters, + } # ── API 调用 ──────────────────────────────────────────────────────────────── class ImgGenHTTPError(Exception): - """火山端 HTTP 错误(携带状态码与响应体,供 main 做 fallback 决策)。""" + """百炼端 HTTP 错误(携带状态码与响应体,供 main 做候选链 fallback 决策)。""" def __init__(self, code: int, body: str) -> None: super().__init__(f"HTTP {code}: {body}") @@ -161,14 +205,25 @@ def __init__(self, code: int, body: str) -> None: self.body = body -def api_request(payload: dict, api_key: str) -> dict: - """调火山方舟 images/generations;返回解析后的 JSON。 +def is_model_unavailable(exc: ImgGenHTTPError) -> bool: + """判断 HTTP 错误是否属于"模型层不可用"(可沿候选链 fallback)。 - 失败时抛 ImgGenHTTPError(由 main 决定是否 fallback 到备用模型)。 + 403/404 直接算;400 需 body 命中模型类错误关键词(参数错的 400 不 fallback, + 换模型也一样错,快速失败暴露真实原因)。 """ - data = json.dumps(payload).encode("utf-8") + if exc.code in MODEL_UNAVAILABLE_CODES: + return True + if exc.code == 400: + lowered = exc.body.lower() + return any(hint in lowered for hint in MODEL_ERROR_BODY_HINTS) + return False + + +def api_request(url: str, payload: dict, api_key: str) -> dict: + """调百炼 multimodal-generation;返回解析后的 JSON。失败抛 ImgGenHTTPError。""" + data = json.dumps(payload, ensure_ascii=False).encode("utf-8") req = urllib.request.Request( - API_URL, + url, data=data, headers={ "Authorization": f"Bearer {api_key}", @@ -177,165 +232,154 @@ def api_request(payload: dict, api_key: str) -> dict: method="POST", ) try: - with urllib.request.urlopen(req, timeout=120) as resp: + with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp: return json.loads(resp.read()) except urllib.error.HTTPError as e: body = e.read().decode(errors="replace") - print(f"[error] HTTP {e.code}: {body}", file=sys.stderr) - if e.code in RETRYABLE_STATUS_CODES: - print(f"[hint] 火山端暂时不可用 (HTTP {e.code}),稍后重试", file=sys.stderr) raise ImgGenHTTPError(e.code, body) +def extract_image_urls(resp: dict) -> list[str]: + """从响应提取图片 URL:output.choices[*].message.content[*].image。""" + urls: list[str] = [] + output = resp.get("output") or {} + for choice in output.get("choices") or []: + message = choice.get("message") or {} + content = message.get("content") + if isinstance(content, list): + for item in content: + if isinstance(item, dict) and item.get("image"): + urls.append(item["image"]) + elif isinstance(content, str) and content.startswith("http"): + urls.append(content) + return urls + + # ── 图像下载 ──────────────────────────────────────────────────────────────── def download_image(url: str, dest_path: Path) -> None: - """下载图片到本地。链接 24h 内有效(按火山文档)。""" - req = urllib.request.Request(url, headers={"User-Agent": "wiseflow-img-gen/2.0"}) - with urllib.request.urlopen(req, timeout=60) as resp: + """下载图片到本地。链接 24h 内有效(按百炼文档)。""" + req = urllib.request.Request(url, headers={"User-Agent": "wiseflow-awk-img-gen/3.0"}) + with urllib.request.urlopen(req, timeout=120) as resp: dest_path.write_bytes(resp.read()) -def _print_enable_guide(failed_model: str) -> None: - """主力 + fallback 模型都不可用时,输出火山后台开通指引(供 Agent 转告用户)。""" +def _print_enable_guide(mode: str, failed_model: str) -> None: + """候选链全部不可用时,输出开通指引(供 Agent 转告用户)。""" print("", file=sys.stderr) - print(f"[error] 图像生成模型 {failed_model} 不可用。已尝试主力 + fallback 均失败。", file=sys.stderr) - print("[guide] 请到火山引擎后台开通视觉模型:", file=sys.stderr) - print(" 1. 打开 https://console.volcengine.com/ark/", file=sys.stderr) - print(" 2. 左侧「系统管理」→「开通管理」→「视觉模型」", file=sys.stderr) - print(" 3. 列表中找到 doubao-seedream-4.5 和 doubao-seedream-5.0-lite,点右侧「开通服务」", file=sys.stderr) - print("[guide] 开通页面上方有 CodePlan 免费资源包活动:", file=sys.stderr) - print(" doubao-seedream-4.5 送 200 张图,doubao-seedream-5.0-lite 送 50 张图。", file=sys.stderr) - print(" ⚠️ 免费额度用光后,除非手动关闭服务,否则进入付费模式(额外付费)。", file=sys.stderr) - print(" 收费标准参见视觉模型页面。请提醒用户知悉。", file=sys.stderr) + print(f"[error] 图像生成模型 {failed_model} 不可用(模式={mode}),候选链已全部尝试。", file=sys.stderr) + if mode == "workspace": + print("[guide] 请到阿里云百炼控制台检查业务空间模型授权:", file=sys.stderr) + print(" 1. 打开 https://bailian.console.aliyun.com/", file=sys.stderr) + print(f" 2. 确认业务空间(WORKSPACE_ID)已授权模型:{'、'.join(WS_MODEL_CHAIN)}", file=sys.stderr) + print(" 3. 确认 MODELSTUDIO_API_KEY 属于该业务空间", file=sys.stderr) + else: + print("[guide] 请检查 Agent Plan(token-plan)订阅:", file=sys.stderr) + print(f" 1. 确认订阅包含图像生成能力,模型:{'、'.join(PLAN_MODEL_CHAIN)}", file=sys.stderr) + print(" 2. 确认 AWK_API_KEY 为百炼 agent plan 的 key(sk-sp-* 形态)", file=sys.stderr) + print("[hint] 免 key 实拍图片可退公共技能 pexels-footage / pixabay-footage(非 AI 生成)。", file=sys.stderr) # ── main ───────────────────────────────────────────────────────────────────── def main() -> None: parser = argparse.ArgumentParser( - description="火山方舟 Seedream 图像生成(text-to-image / image-edit)" + description="阿里云百炼图像生成/编辑(业务空间 qwen-image / agent plan wan2.7-image)" ) - parser.add_argument("--prompt", required=True, help="图像描述(≤300 汉字 / 600 英文)") + parser.add_argument("--prompt", required=True, help="图像描述(要渲染的文字直接写完整句子)") parser.add_argument( "--model", default=None, - help="Model ID(默认 doubao-seedream-4.5,主力不可用时自动 fallback 到 doubao-seedream-5.0-lite;" - "显式指定时不 fallback)", + help="Model ID(缺省按模式走候选链自动 fallback;显式指定时不 fallback)", ) parser.add_argument( "--image-size", default=None, dest="image_size", - help="文生图尺寸。方式 1: 2K/3K/4K;方式 2: WxH(如 2048x2048)", + help="尺寸:'WxH'(如 2048x2048,总像素 512²~2048²)或 'auto';缺省 2048x2048", ) - parser.add_argument("--seed", type=int, default=None, help="随机种子 (0-?)") + parser.add_argument("--seed", type=int, default=None, help="随机种子 [0, 2147483647]") parser.add_argument( "--watermark", choices=["true", "false"], default="false", - help="是否加水印(火山默认 true;wiseflow 默认 false 避免后续 image 工具处理)", + help="是否加水印(百炼默认 false;xiaobei 保持 false 避免后续 image 工具处理)", ) parser.add_argument( - "--response-format", default="url", dest="response_format", - choices=["url", "b64_json"], - help="返回格式:url(火山默认,链接 24h 有效)/ b64_json", + "--prompt-extend", action="store_true", dest="prompt_extend", + help="允许百炼自动扩写 prompt(API 默认开;本脚本默认关,保证封面文字/布局指令精确)", ) - # image-edit inputs(火山 image 字段支持 URL 或 Base64) - parser.add_argument("--image", default=None, help="源图 URL 或 Base64(启用图生图)") - parser.add_argument("--image2", default=None, help="第二张参考图(image-edit)") - parser.add_argument("--image3", default=None, help="第三张参考图(image-edit)") + # image-edit inputs(URL / data URI / 本地文件路径均可) + parser.add_argument("--image", default=None, help="参考图 1:URL 或本地路径(启用编辑模式)") + parser.add_argument("--image2", default=None, help="参考图 2(编辑模式,多图融合)") + parser.add_argument("--image3", default=None, help="参考图 3(编辑模式,多图融合)") parser.add_argument("--out-dir", default=None, dest="out_dir", help="输出目录") args = parser.parse_args() - api_key = os.environ.get("AWK_GEN_KEY") - if not api_key: - print("[error] AWK_GEN_KEY not set(火山方舟 API Key)", file=sys.stderr) - sys.exit(1) + base, api_key, chain, mode = resolve_mode() - # watermark 字段火山期望 bool(JSON),从字符串转 + # watermark 字段百炼期望 bool(JSON),从字符串转 args.watermark = args.watermark == "true" ts = int(time.time()) out_dir = Path(args.out_dir) if args.out_dir else Path(f"./tmp/awk-img-{ts}") out_dir.mkdir(parents=True, exist_ok=True) - # 候选模型:用户显式 --model 时不 fallback;否则主力 → fallback + # 候选模型:用户显式 --model 时不 fallback;否则按模式走候选链 + candidates = [args.model] if args.model else list(chain) is_edit_mode = bool(args.image) - if args.model: - candidates = [args.model] - else: - default_model = DEFAULT_EDIT_MODEL if is_edit_mode else DEFAULT_GEN_MODEL - candidates = [default_model, FALLBACK_MODEL] if default_model != FALLBACK_MODEL else [default_model] - - mode = "image-edit" if is_edit_mode else "text-to-image" + gen_mode = "image-edit" if is_edit_mode else "text-to-image" - result: Optional[dict] = None - last_code: Optional[int] = None + url = f"{base}{GEN_PATH}" + result: dict | None = None for idx, cand_model in enumerate(candidates): payload = build_payload(args, cand_model) - print(f"[info] Mode={mode} model={cand_model} size={payload.get('size', '-')}", file=sys.stderr) + size = (payload.get("parameters") or {}).get("size", "-") + print(f"[info] Mode={gen_mode} provider={mode} model={cand_model} size={size}", file=sys.stderr) try: - result = api_request(payload, api_key) + result = api_request(url, payload, api_key) break except ImgGenHTTPError as e: - last_code = e.code + print(f"[error] HTTP {e.code}: {e.body[:500]}", file=sys.stderr) is_last = idx == len(candidates) - 1 - if e.code in MODEL_UNAVAILABLE_CODES and not is_last: - print(f"[warn] model {cand_model} 不可用 (HTTP {e.code}),切换 fallback...", file=sys.stderr) + if is_model_unavailable(e) and not is_last: + print(f"[warn] model {cand_model} 不可用 (HTTP {e.code}),切换候选链下一个...", file=sys.stderr) continue - # 非模型层错误(5xx/429)或已是最后一个候选:直接失败 - if e.code in MODEL_UNAVAILABLE_CODES: - _print_enable_guide(cand_model) + if is_model_unavailable(e): + _print_enable_guide(mode, cand_model) sys.exit(1) if result is None: - # 所有候选都失败(理论上上面 sys.exit 已退出,保险) - _print_enable_guide(candidates[-1]) + _print_enable_guide(mode, candidates[-1]) sys.exit(1) - # 火山响应:{ created, data: [{url, b64_json, size}], usage, ... } - data = result.get("data", []) - if not data: - print(f"[error] No images in response: {result}", file=sys.stderr) - sys.exit(1) - - # 检查每张图 - images: list[tuple[int, dict]] = [] - for i, item in enumerate(data): - if "error" in item: - print(f"[error] image[{i}] 生成失败: {item['error']}", file=sys.stderr) - continue - images.append((i, item)) - - if not images: - print("[error] 所有图片都生成失败,response 见上", file=sys.stderr) + # 百炼响应:output.choices[0].message.content[*].image → URL(24h 有效) + image_urls = extract_image_urls(result) + if not image_urls: + print(f"[error] 响应中无图片 URL: {json.dumps(result, ensure_ascii=False)[:800]}", file=sys.stderr) sys.exit(1) - # 下载 prompts_map: dict = {} - for i, item in images: - url = item.get("url", "") - if not url: - print(f"[error] image[{i}] 无 url 字段: {item}", file=sys.stderr) - continue - # 火山默认 jpeg;可通过 output_format 改 png - ext = "jpg" - dest = out_dir / f"{i:02d}.{ext}" + for i, image_url in enumerate(image_urls): + dest = out_dir / f"{i:02d}.png" print(f"[info] Downloading image {i} → {dest}", file=sys.stderr) - download_image(url, dest) - prompts_map[str(i)] = {"prompt": args.prompt, "url": url, "file": str(dest), "size": item.get("size", "")} + download_image(image_url, dest) + prompts_map[str(i)] = { + "prompt": args.prompt, + "model": result.get("model", candidates[0] if not args.model else args.model), + "provider_mode": mode, + "url": image_url, + "file": str(dest), + } (out_dir / "prompts.json").write_text(json.dumps(prompts_map, ensure_ascii=False, indent=2)) # 简单 HTML gallery gallery_html = [""] - for i, _ in images: - gallery_html.append(f'') + for i in range(len(image_urls)): + gallery_html.append(f'') gallery_html.append("") (out_dir / "index.html").write_text("\n".join(gallery_html)) - # 用量 - usage = result.get("usage", {}) - generated = usage.get("generated_images", len(images)) - print(f"[done] {len(images)} image(s) saved to {out_dir}/ (usage: generated={generated})", file=sys.stderr) + usage = result.get("usage") or {} + print(f"[done] {len(image_urls)} image(s) saved to {out_dir}/ (usage: {json.dumps(usage, ensure_ascii=False)})", file=sys.stderr) for k, v in prompts_map.items(): - print(f" [{k}] {v['file']} ({v['size']})", file=sys.stderr) + print(f" [{k}] {v['file']}", file=sys.stderr) if __name__ == "__main__": diff --git a/skills/awk-img-gen/scripts/tests/test_gen.py b/skills/awk-img-gen/scripts/tests/test_gen.py index 99c1d047..940c7450 100644 --- a/skills/awk-img-gen/scripts/tests/test_gen.py +++ b/skills/awk-img-gen/scripts/tests/test_gen.py @@ -1,24 +1,25 @@ #!/usr/bin/env python3 -"""Unit tests for gen.py (Phase 5 火山方舟 Seedream image generation). +"""Unit tests for gen.py (阿里云百炼图像生成/编辑,双模式). Covers: -- API key env var: AWK_GEN_KEY (not SILICONFLOW_API_KEY) -- Endpoint: https://ark.cn-beijing.volces.com/api/v3/images/generations -- Default model: doubao-seedream-4.5 (fallback doubao-seedream-5.0-lite) -- size validation (方式 1: 2K/3K/4K; 方式 2: WxH with total pixels + aspect ratio constraints) -- Payload construction (text-to-image vs image-edit) -- API request shape (Bearer token, JSON body, response_format, watermark) -- Image download (mocked) +- 模式解析:WORKSPACE_ID+MODELSTUDIO_API_KEY → 业务空间;否则 AWK_API_KEY → agent plan +- 端点常量:业务空间 maas.aliyuncs.com / agent plan token-plan +- 模型候选链:qwen-image-3.0-pro 链 / wan2.7-image-pro 链 +- size 校验(总像素 [512², 2048²],宽高比 [1/8, 8],规范化为 W*H,auto 放行) +- Payload 构造(text-to-image vs image-edit,content 顺序 image 在前 text 在后) +- 本地图转 data URI +- API request shape(Bearer token、multimodal-generation 端点) +- 响应解析 output.choices[*].message.content[*].image +- 候选链 fallback 判定(403/404 直接 fallback;400 仅模型类错误 fallback) All HTTP calls are mocked — these are unit tests. -Integration tests are deferred to the post-deployment phase. """ +import base64 import json import os +import subprocess import sys -import tempfile import unittest -from io import StringIO from pathlib import Path from unittest import mock @@ -28,254 +29,270 @@ import gen # noqa: E402 -class TestApiConstants(unittest.TestCase): - def test_endpoint_is_ark_v3_images(self): - # 不是 /coding/v3(LLM 编码路径),是 /v3(标准推理) +def _env(**overrides): + """构造受控 env dict(清空所有相关变量后按需注入)。""" + base = { + k: v for k, v in os.environ.items() + if k not in ("WORKSPACE_ID", "MODELSTUDIO_API_KEY", "DASHSCOPE_API_KEY", "AWK_API_KEY") + } + base.update({k: v for k, v in overrides.items() if v is not None}) + return base + + +class TestConstants(unittest.TestCase): + def test_agent_plan_base(self): + self.assertEqual(gen.AGENT_PLAN_BASE, "https://token-plan.cn-beijing.maas.aliyuncs.com/api/v1") + + def test_ws_base_template(self): self.assertEqual( - gen.API_URL, - "https://ark.cn-beijing.volces.com/api/v3/images/generations", + gen.WS_BASE_TEMPLATE.format(wsid="llm-abc"), + "https://llm-abc.cn-beijing.maas.aliyuncs.com/api/v1", ) - def test_default_models_are_seedream(self): - # 4.0 是 dev plan 推荐的稳定版 - self.assertIn("seedream", gen.DEFAULT_GEN_MODEL) - self.assertIn("seedream", gen.DEFAULT_EDIT_MODEL) + def test_gen_path(self): + self.assertEqual(gen.GEN_PATH, "/services/aigc/multimodal-generation/generation") + + def test_model_chains(self): + self.assertEqual(gen.WS_MODEL_CHAIN[0], "qwen-image-3.0-pro") + self.assertIn("qwen-image-3.0", gen.WS_MODEL_CHAIN) + self.assertIn("qwen-image-2.0-pro-2026-06-22", gen.WS_MODEL_CHAIN) + self.assertEqual(gen.PLAN_MODEL_CHAIN[0], "wan2.7-image-pro") + self.assertIn("wan2.7-image", gen.PLAN_MODEL_CHAIN) + + def test_size_constraints_match_bailian_docs(self): + self.assertEqual(gen.MIN_TOTAL_PIXELS, 512 * 512) + self.assertEqual(gen.MAX_TOTAL_PIXELS, 2048 * 2048) + self.assertEqual(gen.MIN_ASPECT_RATIO, 1 / 8) + self.assertEqual(gen.MAX_ASPECT_RATIO, 8) + + +class TestResolveMode(unittest.TestCase): + def test_workspace_mode_preferred(self): + with mock.patch.dict(os.environ, _env( + WORKSPACE_ID="llm-abc", MODELSTUDIO_API_KEY="sk-ws", AWK_API_KEY="sk-sp-x" + ), clear=True): + base, key, chain, mode = gen.resolve_mode() + self.assertEqual(mode, "workspace") + self.assertEqual(base, "https://llm-abc.cn-beijing.maas.aliyuncs.com/api/v1") + self.assertEqual(key, "sk-ws") + self.assertEqual(chain, gen.WS_MODEL_CHAIN) + + def test_workspace_dashscope_key_alias(self): + with mock.patch.dict(os.environ, _env( + WORKSPACE_ID="llm-abc", DASHSCOPE_API_KEY="sk-ds" + ), clear=True): + base, key, chain, mode = gen.resolve_mode() + self.assertEqual(mode, "workspace") + self.assertEqual(key, "sk-ds") + + def test_agent_plan_when_no_workspace(self): + with mock.patch.dict(os.environ, _env(AWK_API_KEY="sk-sp-x"), clear=True): + base, key, chain, mode = gen.resolve_mode() + self.assertEqual(mode, "agent-plan") + self.assertEqual(base, gen.AGENT_PLAN_BASE) + self.assertEqual(chain, gen.PLAN_MODEL_CHAIN) + + def test_workspace_id_without_key_falls_to_agent_plan(self): + with mock.patch.dict(os.environ, _env( + WORKSPACE_ID="llm-abc", AWK_API_KEY="sk-sp-x" + ), clear=True): + _base, _key, _chain, mode = gen.resolve_mode() + self.assertEqual(mode, "agent-plan") + + def test_no_credentials_exits_1(self): + with mock.patch.dict(os.environ, _env(), clear=True): + with self.assertRaises(SystemExit) as ctx: + gen.resolve_mode() + self.assertEqual(ctx.exception.code, 1) + - def test_size_constraints_match_volcengine_docs(self): - # 火山 API 文档:总像素 [2560x1440, 4096x4096],宽高比 [1/16, 16] - self.assertEqual(gen.MIN_TOTAL_PIXELS, 2560 * 1440) - self.assertEqual(gen.MAX_TOTAL_PIXELS, 4096 * 4096) - self.assertEqual(gen.MIN_ASPECT_RATIO, 1 / 16) - self.assertEqual(gen.MAX_ASPECT_RATIO, 16) +class TestSizeNormalization(unittest.TestCase): + def test_presets_accepted_and_normalized(self): + for size in ("2048x2048", "2688x1536", "1536x2688", "2368x1728", "1728x2368"): + self.assertEqual(gen.normalize_size(size), size.replace("x", "*")) + def test_star_separator_accepted(self): + self.assertEqual(gen.normalize_size("2048*2048"), "2048*2048") -class TestSizeValidation(unittest.TestCase): - def test_quality_presets_accepted(self): - for preset in ("2K", "3K", "4K"): - self.assertEqual(gen.validate_size(preset), preset) + def test_chinese_x_accepted(self): + self.assertEqual(gen.normalize_size("2048×2048"), "2048*2048") - def test_2k_recommended_sizes_accepted(self): - for size in ( - "2048x2048", "2304x1728", "1728x2304", - "2848x1600", "1600x2848", "2496x1664", - "1664x2496", "3136x1344", - ): - self.assertEqual(gen.validate_size(size), size) + def test_auto_passthrough(self): + self.assertEqual(gen.normalize_size("auto"), "auto") + self.assertEqual(gen.normalize_size("AUTO"), "auto") def test_total_pixels_below_min_rejected(self): - # 1500x1500 = 2250000 < 3686400 - with self.assertRaises(SystemExit) as ctx: - gen.validate_size("1500x1500") - self.assertEqual(ctx.exception.code, 1) + # 500x500 = 250000 < 262144 + with self.assertRaises(SystemExit): + gen.normalize_size("500x500") def test_total_pixels_above_max_rejected(self): - # 5000x5000 = 25000000 > 16777216 - with self.assertRaises(SystemExit) as ctx: - gen.validate_size("5000x5000") - self.assertEqual(ctx.exception.code, 1) + # 2848x1600 = 4556800 > 4194304(旧火山 2K 预设,百炼超限) + with self.assertRaises(SystemExit): + gen.normalize_size("2848x1600") - def test_aspect_ratio_below_min_rejected(self): - # 100x5000 = 500000 (above min total), but ratio = 0.02 < 1/16 - with self.assertRaises(SystemExit) as ctx: - gen.validate_size("100x5000") - self.assertEqual(ctx.exception.code, 1) - - def test_aspect_ratio_above_max_rejected(self): - # 5000x100 = 500000, ratio = 50 > 16 - with self.assertRaises(SystemExit) as ctx: - gen.validate_size("5000x100") - self.assertEqual(ctx.exception.code, 1) + def test_aspect_ratio_out_of_range_rejected(self): + # 4096x400: ratio 10.24 > 8(面积 1638400 合法但比例超限) + with self.assertRaises(SystemExit): + gen.normalize_size("4096x400") def test_invalid_format_rejected(self): - for bad in ("abc", "1024", "1024x", "x1024", ""): - with self.assertRaises(SystemExit) as ctx: - gen.validate_size(bad) - self.assertEqual(ctx.exception.code, 1) + for bad in ("abc", "1024", "1024x", "x1024", "", "2K", "4K"): + with self.assertRaises(SystemExit): + gen.normalize_size(bad) - def test_lowercase_x_accepted(self): - # 文档示例用大写 X 但小写也应支持 - self.assertEqual(gen.validate_size("2048x2048"), "2048x2048") +class TestResolveImageRef(unittest.TestCase): + def test_url_passthrough(self): + self.assertEqual(gen.resolve_image_ref("https://a.com/x.jpg"), "https://a.com/x.jpg") -class TestParseSize(unittest.TestCase): - def test_valid_standard(self): - self.assertEqual(gen._parse_size("2048x2048"), (2048, 2048)) + def test_data_uri_passthrough(self): + ref = "data:image/png;base64,AAAA" + self.assertEqual(gen.resolve_image_ref(ref), ref) - def test_valid_lowercase(self): - self.assertEqual(gen._parse_size("1024x768"), (1024, 768)) + def test_local_file_to_data_uri(self): + import tempfile + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f: + f.write(b"\x89PNG\r\n\x1a\nfakebytes") + tmp = f.name + try: + ref = gen.resolve_image_ref(tmp) + self.assertTrue(ref.startswith("data:image/png;base64,")) + self.assertEqual(base64.b64decode(ref.split(",", 1)[1]), b"\x89PNG\r\n\x1a\nfakebytes") + finally: + os.unlink(tmp) - def test_valid_chinese_x(self): - # 火山文档用 ×(中文乘号) - self.assertEqual(gen._parse_size("2048×2048"), (2048, 2048)) - - def test_invalid(self): - self.assertIsNone(gen._parse_size("abc")) - self.assertIsNone(gen._parse_size("1024")) - self.assertIsNone(gen._parse_size("x1024")) + def test_missing_file_exits_1(self): + with self.assertRaises(SystemExit): + gen.resolve_image_ref("/nonexistent/x.jpg") class TestPayloadConstruction(unittest.TestCase): - def test_text_to_image_default(self): - args = mock.Mock( + def _args(self, **overrides): + defaults = dict( prompt="a cat", model=None, image=None, image2=None, image3=None, - image_size=None, seed=None, watermark=False, response_format="url", - ) - payload = gen.build_payload(args, gen.DEFAULT_GEN_MODEL) - self.assertEqual(payload["model"], gen.DEFAULT_GEN_MODEL) - self.assertEqual(payload["prompt"], "a cat") - # Default size is 2048x2048 - self.assertEqual(payload["size"], "2048x2048") - self.assertFalse(payload["watermark"]) - # image field not present in text-to-image mode - self.assertNotIn("image", payload) - - def test_text_to_image_explicit_size(self): - args = mock.Mock( - prompt="mountains", model=None, image=None, image2=None, image3=None, - image_size="1664x2496", seed=None, watermark=False, response_format="url", - ) - payload = gen.build_payload(args, gen.DEFAULT_GEN_MODEL) - # 1664x2496 = 4153344, ratio 0.667 — within 2K - self.assertEqual(payload["size"], "1664x2496") - - def test_text_to_image_quality_preset(self): - args = mock.Mock( - prompt="x", model=None, image=None, image2=None, image3=None, - image_size="4K", seed=None, watermark=False, response_format="url", + image_size=None, seed=None, watermark=False, prompt_extend=False, ) - payload = gen.build_payload(args, gen.DEFAULT_GEN_MODEL) - self.assertEqual(payload["size"], "4K") - - def test_image_edit_single_source(self): - args = mock.Mock( - prompt="make it night", model=None, - image="https://example.com/a.jpg", image2=None, image3=None, - image_size=None, seed=None, watermark=False, response_format="url", - ) - payload = gen.build_payload(args, gen.DEFAULT_EDIT_MODEL) - self.assertEqual(payload["model"], gen.DEFAULT_EDIT_MODEL) - # 单图是字符串(不是数组) - self.assertEqual(payload["image"], "https://example.com/a.jpg") - # image-edit 模式不传 size - self.assertNotIn("size", payload) - - def test_image_edit_multi_source_uses_array(self): - args = mock.Mock( - prompt="blend", model=None, - image="https://example.com/a.jpg", - image2="https://example.com/b.jpg", - image3="https://example.com/c.jpg", - image_size=None, seed=None, watermark=False, response_format="url", + defaults.update(overrides) + return mock.Mock(**defaults) + + def test_text_to_image_default(self): + payload = gen.build_payload(self._args(), "qwen-image-3.0-pro") + self.assertEqual(payload["model"], "qwen-image-3.0-pro") + msgs = payload["input"]["messages"] + self.assertEqual(len(msgs), 1) + self.assertEqual(msgs[0]["role"], "user") + # 文生图 content 只有一个 text 项 + self.assertEqual(msgs[0]["content"], [{"text": "a cat"}]) + self.assertEqual(payload["parameters"]["size"], "2048*2048") + self.assertFalse(payload["parameters"]["watermark"]) + self.assertFalse(payload["parameters"]["prompt_extend"]) + + def test_explicit_size_normalized(self): + payload = gen.build_payload(self._args(image_size="1536x2688"), "m") + self.assertEqual(payload["parameters"]["size"], "1536*2688") + + def test_image_edit_content_order(self): + # 编辑模式:image 项在前,text 在后(百炼文档要求) + payload = gen.build_payload( + self._args(image="https://a.com/x.jpg", image2="https://a.com/y.jpg"), "m" ) - payload = gen.build_payload(args, gen.DEFAULT_EDIT_MODEL) - # 多图用数组(火山 image 字段支持 array) - self.assertIsInstance(payload["image"], list) - self.assertEqual(len(payload["image"]), 3) - - def test_explicit_model_overrides_default(self): - args = mock.Mock( - prompt="x", model=gen.FALLBACK_MODEL, - image=None, image2=None, image3=None, - image_size=None, seed=None, watermark=False, response_format="url", + content = payload["input"]["messages"][0]["content"] + self.assertEqual(content[0], {"image": "https://a.com/x.jpg"}) + self.assertEqual(content[1], {"image": "https://a.com/y.jpg"}) + self.assertEqual(content[2], {"text": "a cat"}) + # 编辑模式缺省不带 size + self.assertNotIn("size", payload["parameters"]) + + def test_image_edit_explicit_size_included(self): + payload = gen.build_payload( + self._args(image="https://a.com/x.jpg", image_size="2048x2048"), "m" ) - payload = gen.build_payload(args, gen.FALLBACK_MODEL) - self.assertEqual(payload["model"], gen.FALLBACK_MODEL) + self.assertEqual(payload["parameters"]["size"], "2048*2048") - def test_seed_included_when_provided(self): - args = mock.Mock( - prompt="x", model=None, image=None, image2=None, image3=None, - image_size=None, seed=42, watermark=False, response_format="url", - ) - payload = gen.build_payload(args, gen.DEFAULT_GEN_MODEL) - self.assertEqual(payload["seed"], 42) + def test_seed_and_flags(self): + payload = gen.build_payload(self._args(seed=42, watermark=True, prompt_extend=True), "m") + self.assertEqual(payload["parameters"]["seed"], 42) + self.assertTrue(payload["parameters"]["watermark"]) + self.assertTrue(payload["parameters"]["prompt_extend"]) class TestApiRequest(unittest.TestCase): @mock.patch("gen.urllib.request.urlopen") - def test_request_uses_bearer_token(self, mock_urlopen): + def test_request_shape(self, mock_urlopen): mock_urlopen.return_value.__enter__.return_value.read.return_value = json.dumps({ - "data": [{"url": "https://x.com/a.jpg"}], - "usage": {"generated_images": 1}, + "output": {"choices": [{"message": {"content": [{"image": "https://x/a.png"}]}}]}, }).encode("utf-8") - - payload = {"model": "doubao-seedream-4.5", "prompt": "x", "size": "2048x2048"} - gen.api_request(payload, "test-key-abc") - + url = f"{gen.AGENT_PLAN_BASE}{gen.GEN_PATH}" + resp = gen.api_request(url, {"model": "m"}, "sk-sp-test") args, _ = mock_urlopen.call_args req = args[0] - self.assertEqual(req.headers["Authorization"], "Bearer test-key-abc") + self.assertEqual(req.headers["Authorization"], "Bearer sk-sp-test") self.assertEqual(req.headers["Content-type"], "application/json") - self.assertEqual(req.method, "POST") - # Verify URL - self.assertEqual(req.full_url, gen.API_URL) + self.assertEqual(req.full_url, url) + self.assertEqual(gen.extract_image_urls(resp), ["https://x/a.png"]) @mock.patch("gen.urllib.request.urlopen") - def test_response_parsed(self, mock_urlopen): - mock_urlopen.return_value.__enter__.return_value.read.return_value = json.dumps({ - "created": 1234, - "data": [{"url": "https://x.com/a.jpg", "size": "2048x2048"}], - "usage": {"generated_images": 1, "output_tokens": 16384, "total_tokens": 16384}, - }).encode("utf-8") + def test_http_error_raises_with_body(self, mock_urlopen): + import urllib.error + from io import BytesIO + mock_urlopen.side_effect = urllib.error.HTTPError( + url="https://x/api", code=404, msg="Not Found", hdrs=None, fp=BytesIO(b'{"code":"ModelNotFound"}'), + ) + with self.assertRaises(gen.ImgGenHTTPError) as ctx: + gen.api_request("https://x/api", {}, "k") + self.assertEqual(ctx.exception.code, 404) - result = gen.api_request({"model": "x", "prompt": "y", "size": "2048x2048"}, "k") - self.assertEqual(len(result["data"]), 1) - self.assertEqual(result["data"][0]["url"], "https://x.com/a.jpg") - self.assertEqual(result["usage"]["generated_images"], 1) - - -class TestApiKeyEnv(unittest.TestCase): - def test_awk_gen_key_is_required_env(self): - """SKILL.md frontmatter 的 `requires.env` 必须声明 AWK_GEN_KEY,不再含 SILICONFLOW_API_KEY。""" - skill_md = (SCRIPTS_DIR.parent / "SKILL.md").read_text() - # 抽 frontmatter YAML 块 - import re as _re - m = _re.search(r"^---\n(.*?)\n---", skill_md, _re.DOTALL | _re.MULTILINE) - self.assertIsNotNone(m, "SKILL.md 必须以 YAML frontmatter 开头") - frontmatter = m.group(1) - self.assertIn("AWK_GEN_KEY", frontmatter) - # requires.env 段(缩进 2 空格的 env 子段)不含 SILICONFLOW_API_KEY - env_block = _re.search(r"^\s+env:\n((?:\s+-\s+\S+\n)+)", frontmatter, _re.MULTILINE) - self.assertIsNotNone(env_block, "frontmatter 应包含 env: 段") - env_text = env_block.group(1) - self.assertNotIn("SILICONFLOW_API_KEY", env_text) - - def test_awk_gen_key_required_by_script(self): - """脚本会显式校验 AWK_GEN_KEY 不存在时 exit 1。""" - with mock.patch.dict(os.environ, {"AWK_GEN_KEY": ""}, clear=False): - # 强制 AWK_GEN_KEY 为空,看脚本逻辑 - args = mock.Mock(prompt="x", model=None, image=None, image2=None, image3=None, - image_size=None, seed=None, watermark="false", response_format="url", - out_dir=None) - with self.assertRaises(SystemExit) as ctx: - # 模拟 main() 前半段(API key 校验) - api_key = os.environ.get("AWK_GEN_KEY") - if not api_key: - sys.exit(1) - self.assertEqual(ctx.exception.code, 1) + +class TestExtractImageUrls(unittest.TestCase): + def test_multi_choices_multi_content(self): + resp = {"output": {"choices": [ + {"message": {"content": [{"image": "https://x/a.png"}, {"text": "ignored"}]}}, + {"message": {"content": [{"image": "https://x/b.png"}]}}, + ]}} + self.assertEqual(gen.extract_image_urls(resp), ["https://x/a.png", "https://x/b.png"]) + + def test_empty_output(self): + self.assertEqual(gen.extract_image_urls({}), []) + self.assertEqual(gen.extract_image_urls({"output": {}}), []) + + +class TestModelUnavailable(unittest.TestCase): + def test_403_404_always(self): + self.assertTrue(gen.is_model_unavailable(gen.ImgGenHTTPError(403, ""))) + self.assertTrue(gen.is_model_unavailable(gen.ImgGenHTTPError(404, ""))) + + def test_400_model_hints_only(self): + self.assertTrue(gen.is_model_unavailable(gen.ImgGenHTTPError(400, '{"code":"ModelNotFound"}'))) + self.assertTrue(gen.is_model_unavailable(gen.ImgGenHTTPError(400, "model not found: x"))) + self.assertFalse(gen.is_model_unavailable(gen.ImgGenHTTPError(400, '{"code":"InvalidParameter","message":"size invalid"}'))) + + def test_5xx_not_model_unavailable(self): + self.assertFalse(gen.is_model_unavailable(gen.ImgGenHTTPError(500, ""))) + self.assertFalse(gen.is_model_unavailable(gen.ImgGenHTTPError(429, ""))) class TestIntegrationDryRun(unittest.TestCase): - """Subprocess-level smoke test: gen.py --help 应该可执行且不依赖 env。""" + """Subprocess-level smoke tests.""" def test_help_runs_without_env(self): - import subprocess result = subprocess.run( [sys.executable, str(SCRIPTS_DIR / "gen.py"), "--help"], capture_output=True, text=True, timeout=10, check=False, + env=_env(), ) self.assertEqual(result.returncode, 0) self.assertIn("image", result.stdout.lower()) - def test_missing_api_key_exits_1(self): - import subprocess - env = {k: v for k, v in os.environ.items() if k != "AWK_GEN_KEY"} + def test_missing_credentials_exits_1(self): result = subprocess.run( [sys.executable, str(SCRIPTS_DIR / "gen.py"), "--prompt", "x"], capture_output=True, text=True, timeout=10, check=False, - env=env, + env=_env(), ) self.assertEqual(result.returncode, 1) - self.assertIn("AWK_GEN_KEY", result.stderr) + self.assertIn("AWK_API_KEY", result.stderr) + self.assertIn("WORKSPACE_ID", result.stderr) if __name__ == "__main__": diff --git a/skills/awk-tts/SKILL.md b/skills/awk-tts/SKILL.md index 92fcd65d..17c2e78a 100644 --- a/skills/awk-tts/SKILL.md +++ b/skills/awk-tts/SKILL.md @@ -1,6 +1,6 @@ --- name: awk-tts -description: 火山方舟豆包语音合成 2.0(seed-tts-2.0 字符版)文本转语音。生成 MP3/PCM/WAV/OGG_OPUS 旁白音频,凭据走 VOLC_TTS_*(旧控制台双头)或 VOLC_TTS_APP_KEY(新控制台单头),与 viral-chaser 火山 ASR 同控制台开通。 +description: 多供应商旁白 TTS——火山豆包语音合成 2.0(seed-tts-2.0)+ 阿里云百炼(qwen-audio-3.0-tts),凭据在哪家走哪家(火山 → 百炼业务空间 → 百炼 agent plan)。生成 MP3/PCM/WAV/OPUS 旁白,可选字级时间戳,合成后自动 ASR 自检。 metadata: openclaw: emoji: 🔊 @@ -8,44 +8,35 @@ metadata: bins: - python3 - ffprobe - env: - - VOLC_TTS_APP_ID - - VOLC_TTS_ACCESS_KEY primaryEnv: VOLC_TTS_ACCESS_KEY homepage: https://www.volcengine.com/docs/6561/1598757 --- -# 火山方舟豆包语音合成(awk-tts) +# 多供应商旁白 TTS(awk-tts) -走火山 openspeech v3 单向流式接口合成旁白音频。 +火山 openspeech v3 单向流式 + 百炼 SpeechSynthesizer 双后端,凭据自动路由。 ## 优先级约定(强制) **优先使用 OpenClaw 内置 TTS 工具**(`tts_generate` 或 agent 内置语音合成能力)。 -OpenClaw 内置 TTS 不可用时,回退到本 skill 的本地脚本(要求环境变量已经配置火山 TTS 凭据)。 +OpenClaw 内置 TTS 不可用时,回退到本 skill 的本地脚本。 > 即:本 skill 的 `scripts/tts.py` 是 **fallback 路径**,不应首选。调用方在 SKILL.md 里描述 TTS 环节时,须照此优先级写明,不许把本脚本写成第一选择。 -## 首次使用:开通火山语音合成 +## 供应商路由(脚本内部,凭据在哪家走哪家) -火山豆包语音合成 2.0 与本仓 `viral-chaser`(火山 ASR)同控制台、同范式开通: +| 优先级 | 供应商 | 触发凭据 | 模型/资源 | +|--------|--------|---------|-----------| +| 1 | 火山豆包语音合成 2.0 | `VOLC_TTS_APP_ID`+`VOLC_TTS_ACCESS_KEY`(旧双头)或 `VOLC_TTS_APP_KEY`(新单头) | `seed-tts-2.0`(克隆音色 `S_xxx` 自动路由 `seed-icl-2.0`) | +| 2 | 百炼业务空间 | `WORKSPACE_ID` + `MODELSTUDIO_API_KEY`/`DASHSCOPE_API_KEY` | `qwen-audio-3.0-tts-plus` → `qwen-audio-3.0-tts-flash` 候选链 | +| 3 | 百炼 agent plan | `AWK_API_KEY`(token-plan 端点) | 同上 | -1. 登火山引擎控制台 → 左侧「开通管理」→ **「语音模型」**选项卡 -2. 拉到最下找 **「Doubao-语音合成-2.0」** → 点击「立即使用」 -3. 在跳转页选 **「试用」**(一开始送 2 万字符,可先用,后续再点开通付费) -4. 在该页面拿三项凭据:**APP ID**(数字)、**Access Token**、**Secret Key** - -**给小贝的凭据**(按控制台版本二选一): - -| 控制台版本 | 给小贝的变量 | 备注 | -|---------|------------|------| -| 旧控制台双头 | `VOLC_TTS_APP_ID`(数字 APP ID)+ `VOLC_TTS_ACCESS_KEY`(Access Token) | 与 viral-chaser ASR 同对范式,**Secret Key 不给** | -| 新控制台单头 | `VOLC_TTS_APP_KEY`(APP Key) | 旧控制台账号不要把 Secret Key 填到这里 | - -> 鉴权二选一(脚本优先旧控制台双头)。脚本自动判:同时给出 `VOLC_TTS_APP_ID`+`VOLC_TTS_ACCESS_KEY` 走旧双头;否则用 `VOLC_TTS_APP_KEY` 走新单头。 - -> 应该 spawn IT engineer subagent 写入实例环境变量,**不要自己写环境变量文件**,it-engineer具有相关背景知识。 +- 百炼候选链自动 fallback(模型未开通/未找到切下一个);`--model` 显式指定关闭 fallback(仅百炼生效)。 +- 百炼默认音色 `longanhuan_v3.6`;传了火山系音色 ID 会警告并换默认音色。 +- 格式映射:火山 `ogg_opus` 在百炼自动转 `opus`;百炼不支持 32000 采样率。 +- 语速 `--speech-rate [-50,100]` 在百炼线性映射为 rate [0.5,2.0];响度 `--loudness` 映射为 volume [0,100]。 +- 三组凭据都缺 → exit 1 并列出配置方式。应该 spawn IT engineer subagent 写入实例环境变量,**不要自己写环境变量文件**。 ## Run @@ -69,6 +60,9 @@ awk-tts \ --format wav \ --sample-rate 44100 \ --output ./assets/audio/demo.wav + +# 字级时间戳(旁白对齐用):火山走流式原生返回,百炼走 SSE + word_timestamp_enabled +awk-tts --text "..." --enable-subtitle --output ./assets/audio/narration.mp3 ``` ## Parameters @@ -78,22 +72,26 @@ awk-tts \ | `fragment_dir` | — | Optional fragment directory under `fragments/`; when set, reads `tts_requirement.md` and defaults output to `artifacts/speech.` | | `--text` | — | Text to synthesize. Required unless `--text-file` or `fragment_dir` is set | | `--text-file` | — | UTF-8 text file to synthesize. Must be relative and under `scripts`, `assets`, `tmp`, `output_videos`, `fragments`, or `/outputs/` | -| `--voice` | `zh_female_shuangkuaisisi_uranus_bigtts` | Speaker ID(火山音色,见下表) | -| `--format` | `mp3` | Audio format: `mp3`, `pcm`, `ogg_opus`, `wav` | -| `--sample-rate` | — | Optional sample rate: 8000/16000/22050/24000/32000/44100/48000 | -| `--speech-rate` | — | Optional speech rate, range `-50`–`100`(0=默认, 100=2x, -50=0.5x) | -| `--loudness` | — | Optional loudness, range `-50`–`100`(0=默认) | -| `--context-text` | — | Optional 情感控制上下文(如 '用撒娇甜蜜的语气',仅 2.0/克隆音色支持) | +| `--voice` | 火山 `zh_female_shuangkuaisisi_uranus_bigtts` / 百炼 `longanhuan_v3.6` | 音色 ID(按路由到的供应商选默认) | +| `--model` | — | 百炼模型 ID(显式指定关闭候选链 fallback;火山模式忽略) | +| `--format` | `mp3` | Audio format: `mp3`, `pcm`, `wav`, `opus`(火山另收 `ogg_opus`,百炼自动映射为 `opus`) | +| `--sample-rate` | 火山不设 / 百炼 24000 | 8000/16000/22050/24000/44100/48000(火山另收 32000) | +| `--speech-rate` | — | Speech rate `-50`–`100`(0=默认, 100=2x, -50=0.5x;百炼线性映射 rate) | +| `--loudness` | — | Loudness `-50`–`100`(0=默认;百炼映射 volume) | +| `--context-text` | — | 情感/风格控制上下文(火山 `context_texts`;百炼 `instruction`) | | `--output` | — | Exact output file path under `assets/audio`, `tmp`, `output_videos`, `fragments`, or `/outputs/` | | `--out-dir` | `./tmp/awk-tts-` | Output directory under `assets/audio`, `tmp`, `output_videos`, `fragments`, or `/outputs/` when `--output` is not set | | `--overwrite` | off | Overwrite existing output audio/metadata files | | `--no-asr-check` | off | Skip ASR self-check after TTS generation | +| `--enable-subtitle` | off | 产出 `