Selaa lähdekoodia

同步 wan3 输入参数规范与合规/唇形参考文档

- 按官方公式重组 wan3 prompt(总体描述+Shot序号+时间戳+分镜内容),SKILL.md Step8、script-template.md、wan3-workflow.md 同步
- wan3-workflow.md 增补红线(禁 Image N/Audio N 标记)与实测坑(BUG B/padding/多音字/参考音频语义前置)+ ASR 自查流程
- 新增 scripts/compliance_scan.py 与 references/compliance-check.md(文案合规扫雷)
- 新增 references/lipsync-fal.md(fal 唇形同步参考)
- elements.md 插画约束收紧为局部特写;README 同步更新
zhengqi 1 viikko sitten
vanhempi
sitoutus
662e293479

+ 5 - 4
README.md

@@ -30,7 +30,7 @@
 
 对 WorkBuddy 说:「用美业口播科普视频脚本技能,做一条关于 *[选题]* 的口播视频」。技能按 `SKILL.md` 的 11 步流程执行:
 
-> 文字风格问卷 → 锁定固定素材 → 生成并确认物料(人物形象照 / 门店内景含 LOGO / 插画)→ 确认口播文案 → 确定插画插入点 → 组装编导分镜 Prompt → 逐步确认 → 调 wan3 生成 → 成片交付。
+> 文字风格问卷 → 锁定固定素材 → 生成并确认物料(人物形象照 / 门店内景含 LOGO / 插画)→ 确认口播文案 → **敏感词/合规检测(必跑,用户按建议改或自改后复检)** → 确定插画插入点 → 组装编导分镜 Prompt → 逐步确认 → 调 wan3 生成 → 成片交付。
 
 也可手动驱动生成脚本:`python scripts/generate_video.py --help`。
 
@@ -43,8 +43,9 @@ graph TD
     A[用户触发<br/>选题 / 选题+素材] --> B[文字风格问卷<br/>无要求则自动生成物料]
     B --> C[锁定固定素材<br/>4项OSS URL 已写死]
     C --> D[生成并确认物料<br/>人物形象照 / 门店内景含LOGO / 插画]
-    D --> E[确认口播文案<br/>转口语化 + 字数约120-150]
-    E --> F[确定插画插入点<br/>指定从哪句开始融入]
+    D --> E[确认口播文案<br/>转口语化 + 字数约90-110]
+    E --> E2[敏感词/合规检测<br/>必跑 · 风险等级+替换建议]
+    E2 --> F[确定插画插入点<br/>指定从哪句开始融入]
     F --> G[组装编导分镜 Prompt<br/>动线 + 运镜 + 插画展示 + 门店元素]
     G --> H[逐步确认]
     H --> I[调百炼 wan3 生成<br/>建任务 → 轮询 → 下载]
@@ -60,7 +61,7 @@ graph TD
 
 - **人物行为动线**:30 秒单段 ≤2 个动作、方向不反向(「走动→落座」则落座后不再起身;「坐姿→起身走动」则走动后不再落座);口播时不设计整理产品等手部摆弄动作,手部只用自然手势 / 点头。
 - **运镜组合**:2–3 种随机穿插(跟随 / 推近 / 拉远 / 横移 / 手持微晃)。
-- **插画展示方式**:全屏铺盖 / 圆形画中画 / 顶部或底部条状画中画 / 侧边画中画 / 人物画中画式全屏,随机混用不固化。
+- **插画展示方式**:仅限局部呈现——圆形画中画 / 顶部或底部条状画中画 / 侧边画中画,**禁止全屏铺盖与人物画中画式全屏**(用户 2026-09-17 规则)。
 - **门店丰富元素**:前台 / 美容床 / 沙发 / 桌椅 / 其他店员虚化走过,随机点缀 2–3 个增加景深。
 
 详细元素、wan3 工程要点、物料收集流程分别见 `references/elements.md`、`references/wan3-workflow.md`、`references/asset-collection.md`;单段脚本骨架见 `assets/script-template.md`。

+ 15 - 8
SKILL.md

@@ -109,12 +109,19 @@ agent_created: true
 
 - **用户自带内容/口播** → 转换为口语化、适合口播的文案(自然亲和、带 `/` `//` 停顿符号、科普准确)。
 - **用户只给选题/知识点** → 技能生成口语化口播文案。
-- **30 秒硬卡循环**:按"30 秒 ≈ 120–150 字"估算字数;若 >150 字 → **反馈用户精简,等用户回精简版后重新估算**,循环直到 ≤150 字(对应 ≤30 秒)才继续。绝不为凑时长拆分片段。
+- **30 秒硬卡循环**:按"30 秒 ≈ 90–110 字"估算字数;若 >110 字 → **反馈用户精简,等用户回精简版后重新估算**,循环直到 ≤110 字(对应 ≤30 秒,预留停顿余量、避免语速赶)才继续。绝不为凑时长拆分片段。
 
-### Step 6 — 确认口播文案(用户确认后才进下一步)
+### Step 6 — 确认口播文案 + 敏感词/合规检测(定稿后必跑)
 
 - 把 Step 5 产出的口语化口播文案(含字数校验)**单独呈现**给用户,请用户确认或修订。
-- 用户未明确确认(或提出修改)→ 回到 Step 5 重做,循环直到用户确认。
+- **用户确认后,自动跑敏感词/合规检测(每次确定文案都必须执行)**:
+  - 先用 `scripts/compliance_scan.py` 对口播全文做机器初筛;
+  - 再对照 `references/compliance-check.md` 人工复核(否定语境同样违规、灰色地带标「建议咨询法律顾问」),生成「敏感词/合规检测报告」(风险等级 🔴🟠🟡🟢 + 法规依据 + 合规替换 + 结论)。
+- **把报告呈现给用户,用户二选一**:
+  ① **按建议改** → 采纳合规替换,回到 Step 5 重出口播;
+  ② **自己改** → 用户手改后再次检测;
+  循环直到结论为「通过 / 修改后通过(中·低)」且**无 🔴 极高、无 🟠 高**违规,才进入 Step 7。
+- **存在 🔴/🟠 不得进入后续组装与生成**(合规优先级最高,与第七节硬性禁令协同);检测结论写入脚本「四-补、合规检测」字段留痕。
 - 此步确保「口播文案已确定」,是 Step 7 插画插入点确认的两大前置之一(另一前置是 Step 4 已确认插画)。
 
 ### Step 7 — 确定插画插入点(插画 + 口播都确认后)
@@ -130,15 +137,15 @@ agent_created: true
 ### Step 8 — 组装单段 30 秒脚本(编导分镜级)
 
 用 `assets/script-template.md` 填充,产出**单份结构化 Markdown**:
-一、角色定义 → 二、固定品牌素材 → 三、自动生成素材 → 四、口播文案(口语化,字数校验)→ **四-补、插画插入点** → 五、单段视频 Prompt(整段 30 秒,**编导分镜版**,含 Image 1–5 / Audio 1 引用)→ 六、保留分析 → 七、硬性禁令 → 八、AI 口令(wan3 请求:命令 + JSON)。
+一、角色定义 → 二、固定品牌素材 → 三、自动生成素材 → 四、口播文案(口语化,字数校验)→ **四-补、插画插入点** → 五、单段视频 Prompt(整段 ≤30 秒,**官方公式版**:总体描述 + Shot序号 + 时间戳 + 分镜内容,时间戳英文 `Shot N [x-y s]`、台词 `{}` 圈出;**prompt 中禁止写 Image N/Audio N**)→ 六、保留分析 → 七、硬性禁令 → 八、AI 口令(wan3 请求:命令 + JSON)。
 - **落盘**:组装完成后,将脚本写入**本任务文件夹** `脚本_<主题>.md`;并把第五节整段 prompt 单独写出到本任务文件夹 `prompt.txt`(供 Step 10 直接 `--prompt "$(cat .../prompt.txt)"` 读取)。
 - 第五节单段 Prompt **必须按 `wan3-workflow.md` 第四节「编导分镜版」范式写**,像专业编导一样覆盖四块:
   1. **人物行为动线**(元素 25):30 秒单段内 **≤2 个动作切换**,且方向不反向("走动→落座"则落座后不再起身;"坐姿→起身走动"则走动后不再落座);**口播时不设计"整理产品"等手部摆弄动作**,手部只用自然手势 / 点头;全程在门店场景里与场景互动,**绝不全程正对镜头站桩**。
   2. **运镜组合**(元素 12):每条约 2–3 种手法按时间轴穿插(跟随 / 推近 / 拉远 / 横移 / 手持微晃),随机组合、不全程单一机位。
-  3. **插画展示方式**(元素 26):在「插画插入点」处触发,随机选用全屏铺盖 / 圆形画中画 / 顶部或底部条状画中画 / 侧边 PiP,**同一视频多次出现可轮换方式,不固化一种**。
+  3. **插画展示方式**(元素 26):在「插画插入点」处触发,仅用**局部**方式(圆形画中画 / 顶部或底部条状画中画 / 侧边 PiP)随机选用,**禁止全屏铺盖 / 人物画中画式全屏**(用户 2026-09-17 硬性规则);同一视频多次出现可轮换局部方式,不固化一种。
   4. **门店丰富元素**(元素 27):在门店场景里随机点缀 2–3 个道具 / 人物(前台 / 美容床 / 沙发 / 桌椅 / 其他店员虚化走过),增加景深与真实感。
-- 口播全文直接写进 prompt 作对白;在「插画插入点」指定句处明确写"讲到[第 N 句:原文]时,画面以[某展示方式]融入 Image 3 / Image 4 风格的抽象可视化";音色用 Audio 1 引用。
-- 引用的是**确认后的生成物料**:Image 1 = 人物完整形象照、Image 2 = 门店内景照片(含 LOGO)、Image 3/4 = 插画;LOGO 已在 Step 4 生成门店内景时落位,无需单独合成。
+- 口播台词用 `{}` 圈出、逐句写进各 Shot 的「分镜内容」;在讲对口白前**加入语气/节奏指令**(见 `wan3-workflow.md` 第四节:疑问/转折处语调上扬、核心点重音与停顿、整体松弛有起伏、禁止机械平读),缓解 wan3 原生 TTS 语气偏平的问题;在「插画插入点」指定句处明确写"讲到[第 N 句:原文]时,画面以[某展示方式]融入插画风格的抽象可视化";音色用自然语言描述"随附音频作音色参考",**不写 Audio 1**。**⚠️ prompt 文本里绝不出现 `Image N`/`Audio N`**(会被 wan3 当对白念出,详见 `wan3-workflow.md` 第一节/第七节)。
+- 引用的是**确认后的生成物料**(仅内部对应 media 数组顺序,prompt 中不写 Image N/Audio N):人物完整形象照、门店内景照片(含 LOGO)、插画;LOGO 已在 Step 4 生成门店内景时落位,无需单独合成。
 
 ### Step 9 — 逐步确认后交付(贴合"逐步确认"习惯)
 
@@ -163,7 +170,7 @@ agent_created: true
 - **固定 4 项永远锁定(作生成输入)**:人物五视角/制服细节/LOGO/音色来自素材包(`assets/brand/`)或公网 URL(`config.brand_assets`),不可被自动生成覆盖;制服胸牌样式绝对不可变。
 - **提交的是确认后的生成物料**:wan3 实际接收 Image 1=人物完整形象照、Image 2=门店内景照片(含 LOGO)、Image 3/4=插画、Audio 1=音色;原始 4 项仅用于生成这些物料,不直接提交。
 - **风格先确认后出图**:发起任务先文字版问风格,再生成「人物完整形象照 + 门店内景(含 LOGO)+ 插画」示例图供确认。
-- **单片段 ≤30 秒**:口播超 150 字必须反馈精简、循环直到满足;不拆分片段。
+- **单片段 ≤30 秒**:口播超 110 字必须反馈精简、循环直到满足;不拆分片段。
 - **LOGO 必现**:在生成门店内景照片时以 `logo_3d` 为参考落位(Image 2),确保成片前台有怡呵美立体 LOGO。
 - **原生音视频**:口播写进 prompt 作对白,音色作 reference_audio,wan3 一次性生成画面+对白+口型+环境音。
 - **插画插入点确认**:插画与口播文案都确认后,必须请用户指定插画从口播哪一句开始融入,再组装 prompt(见 Step 7);不静默默认。

+ 60 - 27
assets/script-template.md

@@ -28,6 +28,8 @@
 
 ## 三、生成并确认的最终物料(用户确认后才提交)
 
+> ⚠️ 以下物料名里的 `Image 1 / Image 2 / Image 3 / Image 4 / Audio 1` **只是内部命名与 media 数组顺序对应**,**绝不能原样写进 prompt 文本**(wan3 会把 `Image N`/`Audio N` 当对白念出)。prompt 里用自然语言描述画面与音色即可。
+
 - **人物完整形象照(Image 1)**:由 character_5view + uniform_detail 生成,`人物_完整形象照.jpg`(胸牌与 uniform_detail 一致、不可变更)。
   - 确认状态:[用户挑选 / 已自动随机指定:人物_形象_X]
 - **门店内景照片(Image 2,含怡呵美 LOGO)**:由 logo_3d 生成含 LOGO,`门店_内景_采用.jpg`(确认采用的位置)。
@@ -38,7 +40,7 @@
 
 ---
 
-## 四、口播文案(口语化,≤150 字 / 30 秒)
+## 四、口播文案(口语化,≤110 字 / 30 秒,预留停顿余量)
 
 > 由用户内容转换而来,自然亲和、适合口播;`/` 短停、`//` 长停不读出。
 
@@ -48,7 +50,21 @@
 
 ---
 
-## 四-补、插画插入点(用户确认,提交前必填)
+## 四-补、敏感词/合规检测(口播定稿后必跑)
+
+> 口播文案(第四节)经用户确认后,按技能 Step 6 自动跑 `scripts/compliance_scan.py` + 对照 `references/compliance-check.md` 复核,结论写入此处留痕。
+
+- **检测触发**:每次口播定稿确认后必跑;用户按建议改 / 自改后再次检测,循环直到无 🔴 极高、无 🟠 高违规。
+- **检测报告结论**:[通过 / 修改后通过(中·低) / 不予通过]
+- **命中条目与处置**:
+  | 违规点 | 原表述 | 风险等级 | 法规依据 | 合规替换 | 处置 |
+  |--------|--------|----------|----------|----------|------|
+  | [类别] | 「[词]」 | [🔴/🟠/🟡/🟢] | [法规] | [替换] | [已采纳 / 用户自改 / 保留待核] |
+- 确认状态:[用户已确认通过 / 已按建议修改后复检通过]
+
+---
+
+## 四-补2、插画插入点(用户确认,提交前必填)
 
 > 在口播文案(第四节)与插画(第三节 Image 3/4)都确认后,由用户指定插画从哪一句开始融入画面作为起始点(见技能 Step 7)。
 
@@ -61,33 +77,50 @@
 
 ## 五、单段视频 Prompt(整段 30 秒 · 编导分镜版,含参考引用)
 
-> 一段连续 prompt 描述整段画面(不拆分请求),wan3 原生生成画面 + 对白 + 环境音。引用的 Image 1/2/3/4 即第三节**确认后的物料**。写这条 prompt 要像专业编导:**人物必须在门店场景里"活"起来,不站桩;运镜/插画展示/门店丰富元素按下面四块随机组合设计**(对应 `elements.md` 元素 25/26/27/12)。
+> 一段连续 prompt 描述整段画面(不拆分请求),wan3 原生生成画面 + 对白 + 环境音。下方范式按官方「总体描述 + 镜头序号 + 时间戳 + 分镜内容」组织,**时间戳用英文 `Shot N [x-y s]`、台词用 `{}` 圈出**。⚠️ **prompt 文本里绝不写 `Image N`/`Audio N`**(会被 wan3 当对白念出,见 `wan3-workflow.md` 第一节/第七节);第三节物料靠 media 数组顺序间接对应,用自然语言描述画面与音色即可。写这条 prompt 要像专业编导:**人物必须在门店场景里"活"起来,不站桩;运镜/插画展示/门店丰富元素按下面四块随机组合设计**(对应 `elements.md` 元素 25/26/27/12)。
 
 ```
-Image 1 中的女性(即这张已确认的人物完整形象照:与图中面孔、[深栗色半扎长发 + 斜刘海]、身穿[米白]美容师制服完全一致;
-胸牌样式与确认稿一致、不可更改),
-身处 Image 2 的怡呵美门店内(墙面有怡呵美立体 LOGO,背景虚化;
-店内可见[门店丰富元素:前台 / 美容床 / 沙发 / 产品陈列架,可选其他店员从画面边缘虚化走过])。
-
-[人物行为动线 · 按口播长短设计,全程与场景互动、不站桩]
-开场她[从走动中入画 / 端坐于面诊桌前]自然引入[主题];讲到中段[起身缓步走向产品架 / 从走动落座于沙发]边走边说;
-结尾[回到中近景、手势收束]。说话间伴随[整理产品 / 手势比划 / 轻微点头]等真实肢体动作。
-
-[运镜组合 · 随机穿插 2–3 种]
-开场[中近景固定 / 极轻微横摇]→ 人物走动时[跟随拍摄]→ 讲到核心点时[缓慢推近]→ 结尾[缓慢拉远]收束;
-整体[手持微晃]保留真实手机拍摄质感。
-
-她自然亲和地讲解[主题]:
-"[第四节口语化口播全文]"
-
-[插画展示方式 · 随机使用,不固化一种](在[插画插入点:第 N 句「原文」]处触发,对应 Image 3 / Image 4)
-方式示例:[全屏铺盖插画] / [画面右侧圆形画中画嵌入插画] / [顶部条状画中画展示插画] / [侧边圆角区域 PiP] /
-[人物画中画式全屏:插画铺满整屏、口播人物以圆形或正方形 PiP 出现在画面某区域];
-同一视频多次出现插画时可轮换不同方式。
-(插画为抽象皮肤结构可视化:透明表皮层、缓慢流动光点、柔和结构变化,无真人皮肤、无血液、无文字)
-
-原生生成清晰女声普通话对白(音色参考 Audio 1)、轻微环境音、无背景音乐。
-绝对禁止:字幕/任何文字/卡通化/塑料皮肤/变脸/服装漂移/场景跳变/其他清晰正脸人物抢镜。
+【总体描述】
+一位身穿[米白]美容师制服、[深栗色半扎长发 + 斜刘海]、胸牌样式与参考形象照一致的女美容师,
+在怡呵美门店内(墙面有怡呵美立体 LOGO,背景虚化,可见前台、美容床、沙发、产品陈列架,
+一名店员从画面边缘虚化走过)拍摄。画面中的女美容师、门店与插画均依据随附参考图生成,
+须保持人物脸型/发型/制服/胸牌一致,门店须含怡呵美立体 LOGO,插画为卡通动漫风抽象可视化(无真人、无文字)。
+
+整体动线:[开场端坐于面诊桌前自然引入主题,中段起身缓步走向产品陈列架、边走边说,
+结尾保持站立、抬手手势收束];说话间以自然手势比划、轻微点头等真实肢体动作呈现。
+
+镜头语言:[开场中近景极轻微横摇,人物走动时跟随拍摄,讲到核心点时缓慢推近,结尾缓慢拉远收束];
+整体手持微晃保留真实手机拍摄质感。
+
+语气与节奏:她自然亲和地讲解,像真人面对面聊天一样有温度有起伏——问句好奇上扬、肯定句沉稳、
+列举句轻快有层次、收束句温和坚定;整体语速比自然对话放慢约一半、从容舒缓,让内容自然撑满约[30s],
+句与句之间留约[0.5s]自然气口,不赶。
+
+声音:原生清晰女声普通话对白(音色参考随附音频)、轻微环境音、无背景音乐;
+口播须有自然语调起伏与停顿,禁止平淡匀速念稿。
+
+硬性约束:口播须严格限于下列分镜台词,禁止添加、删改或自由发挥任何文案之外的语句、语气词;
+插画仅以局部形式出现(圆形/圆角画中画),禁止全屏覆盖;绝对禁止出现字幕、任何画面文字、
+卡通化人物、塑料皮肤、变脸、服装漂移、场景跳变、或其他清晰正脸人物抢镜。
+
+结构说明:以下「Shot序号 + 时间戳 + 分镜内容」为本片分镜脚本;Shot序号与时间戳是给模型的
+节奏结构指令,不是台词,禁止当作语音读出;也禁止在任意空白时段补加台词或语气词。
+
+【分镜脚本】
+Shot 1 [0-4s]:[开场中近景,女美容师端坐面诊桌前,手持微晃手机质感,镜头极轻微横摇];
+她好奇上扬地提问{[第四节·口播句1]},句末留自然气口。
+
+Shot 2 [4-7.5s]:[仍端坐,中近景];她自然沉稳肯定{[口播句2,含防错替换:定期→订期]},语气笃定、不赶。
+
+Shot 3 [7.5-12.5s]:[她起身缓步走向产品陈列架、边走边说,镜头跟随拍摄];列举句轻快有层次{[口播句3]}。
+
+Shot 4 [12.5-19.5s]:[走到陈列架前站立,讲到核心点时镜头缓慢推近;此处画面右侧嵌入圆形画中画
+展示插画参考1(抽象可视化,无真人无文字,禁全屏)]。该句放慢加重带警示,讲到「[核心重音点]」时
+稍作重音并留自然停顿{[口播句4]}。
+
+Shot 5 [19.5-26s]:[切换为画面侧边圆角区域画中画展示插画参考2(同风格,不铺满整屏)];她自然亲和{[口播句5]}。
+
+Shot 6 [26-30s]:[结尾缓慢拉远收束,她保持站立、抬手手势收束,中近景];收束句温和坚定{[口播句6]},留足停顿感。
 ```
 
 ---

+ 88 - 0
references/compliance-check.md

@@ -0,0 +1,88 @@
+# 敏感词 / 合规检测(口播文案必跑)
+
+> 本文件整合自两个技能并针对「美业护肤科普口播」场景裁剪:
+> - `content-reviewer`(内容审核合规):输出**风险等级 + 具体修改建议**的通用审核方法论。
+> - `medical-content-compliance-auditor`(医疗内容合规审查师):广告法 / 医疗广告管理办法 / 平台审核规则的违禁词清单、输出规范与报告模板。
+>
+> 立场:**合规不是阻碍营销,而是保护品牌。** 每条风险都给能落地的替代说法,不单纯说「不行」。
+
+---
+
+## 一、何时跑(触发条件)
+
+- **每一次口播文案确定(Step 6 用户确认)后,必须自动跑一次检测**,再把结果呈现给用户。
+- 用户可选择:**① 按建议改**(采纳合规替换)→ 回到 Step 5 重出文案;或 **② 自己改** → 用户手改后再次检测,循环直到通过。
+- 检测未通过(存在 🔴 / 🟠)不允许进入 Step 8 组装与生成。
+- 机器初筛脚本:`scripts/compliance_scan.py`(关键词命中底筛),Agent 再对命中项做人工复核与法规时效核对。
+
+## 二、检测流程
+
+1. **确认场景**:内容类型 = 美业护肤科普口播;渠道 = 抖音 / 小红书(默认抖音竖屏);属性 = 化妆品科普(非医疗广告、非药品/器械/医美治疗宣称)。
+2. **机器初筛**:跑 `compliance_scan.py` 对确认后的口播全文做关键词命中。
+3. **人工复核**:对命中项逐条核对——是否真实违规、否定语境是否仍含违禁词、是否属灰色地带(标注「建议咨询法律顾问」)。
+4. **输出报告**:违规条目 + 风险等级 + 法规依据 + 合规替换。
+5. **给结论**:通过 / 修改后通过 / 不予通过。
+
+## 三、高危违禁词清单(默认拦截)
+
+| 类型 | 典型词 | 风险 | 法规依据 | 合规替换 |
+|---|---|---|---|---|
+| 绝对化用语 | 最好 / 最佳 / 第一 / 顶级 / 唯一 / 国家级 / 最安全 / 最有效 / 全球领先 | 🟠 高 | 《广告法》第 9 条 | 适合 / 合适 / 代表性 / 优质 / 较安全 / 行业先进 |
+| 治愈承诺 | 根治 / 治愈 / 根除 / 彻底消除 / 永不复发 / 包治 / 治好 | 🔴 极高 | 《广告法》第 17 条、《医疗广告管理办法》 | 改善 / 减少 / 明显缓解 / 辅助改善 |
+| 保证性承诺 | 保证 / 承诺(疗效)/ 100% / 无效退款 / 签约治疗 | 🟠 高 | 《广告法》第 9、25 条 | 有助于 / 预期 / 多数情况下 |
+| 功效夸大·即时 | 一夜回春 / 秒变 / 瞬间 / 奇效 / 神效 / 立竿见影 / 立马见效 | 🟡 中 | 《广告法》第 28 条(虚假宣传) | 循序渐进 / 逐步改善 / 需坚持 / 逐渐见效 |
+| 医疗功效越界(护肤品禁宣称) | 治疗 / 药用 / 消炎 / 杀菌 / 抗炎 / 抗菌 / 医用 / 药效 | 🟠 高 | 《化妆品监督管理条例》《广告法》 | 护理 / 改善 / 舒缓 / 洁净 / 清洁 |
+| 患者证言·对比 | 术前术后对比图 / 疗效见证 / 未授权患者案例 / 与其他机构疗效对比 | 🔴 极高 | 《医疗广告管理办法》 | 删除,改用抽象示意图 / 动画 |
+| 平台诱导风险(抖音/小红书) | 加微信 / 私信我 / 点击下方链接 / 限时优惠 / 最低价 / 免费领 / 扫码 | 🟡 中 | 平台审核规则 | 活动期间优惠 / 进店了解 / 优惠价 |
+
+⚠️ **否定语境同样违规**——「我们不承诺根治」「不会让你治疗」也含违禁词,改写为「不承诺疗效」「以护理改善为主」。
+
+## 四、通用敏感与平台风险(来自 content-reviewer)
+
+- 涉政 / 色情 / 暴力 / 谣言 / 歧视等通用敏感内容:按各平台安全规范拦截,命中即 🔴。
+- 诱导互动:明确话术诱导「点赞关注」「转发」在部分平台受限,建议改为软性引导或删除。
+- 未经证实的数据 / 统计(「90% 的人…」无来源):按虚假宣传风险 🟡 标注,须补来源或删除。
+
+## 五、输出规范
+
+- 每条违规标注风险等级:🔴 极高 / 🟠 高 / 🟡 中 / 🟢 低。
+- 每条违规附**具体法规条款**(引用须核实时效,已废止法规不得作依据)。
+- 每个违规点**至少给一个合规替代方案**。
+- 结论三选一:**通过 / 修改后通过 / 不予通过**。
+- 灰色地带标注「建议咨询专业法律顾问」;平台规则注明最后已知更新时间。
+
+## 六、输出模板(审查报告)
+
+```markdown
+## 敏感词 / 合规检测报告
+
+### 内容类型
+抖音短视频口播文案(美业护肤科普)
+
+### 检测条目
+| 违规点 | 原表述 | 风险等级 | 法规依据 | 合规替换 |
+|--------|--------|----------|----------|----------|
+| 功效夸大·即时 | 「立竿见影」 | 🟡 中 | 《广告法》第 28 条 | 「循序渐进、需要坚持」 |
+
+### 审查结论
+修改后通过(中)。
+
+### 建议
+1. 将「立竿见影」改为「循序渐进、需要坚持」,规避功效承诺风险。
+2. 发布前可再跑一次合规扫描确认无新增违禁词。
+```
+
+## 七、调用方式
+
+```bash
+# 直接传口播文本
+python scripts/compliance_scan.py --text "为什么用了贵的护肤品,皮肤却没改善?……别指望立竿见影。"
+
+# 或传文件(.txt / .md,扫描全文)
+python scripts/compliance_scan.py --file beauty-video-materials/贵产品无效/prompt_剪字.txt
+
+# 写报告到文件
+python scripts/compliance_scan.py --file <脚本.md> --output <报告.md>
+```
+
+脚本输出即上方模板格式,Agent 据报告向用户呈现并走「采纳建议 / 自改 → 重检」循环。

+ 8 - 7
references/elements.md

@@ -18,7 +18,7 @@
 
 ## 2. 视频时长(Duration,单段)
 - 默认:30 秒
-- 硬约束:**整段 ≤30 秒**(wan3 上限)。技能在口播文案阶段按"30 秒 ≈ 120–150 字"估算,超了**反馈用户精简、循环直到 ≤30 秒**才继续(见 SKILL.md 第 5 步)。
+- 硬约束:**整段 ≤30 秒**(wan3 上限)。技能在口播文案阶段按"30 秒 ≈ 90–110 字"估算(预留停顿余量、避免语速赶),超了**反馈用户精简、循环直到 ≤30 秒**才继续(见 SKILL.md 第 5 步)。
 - 可选项:15 秒 / 30 秒(不提供分片段;若内容过多请精简而非延长)
 
 ## 3. 发布平台(Platform)
@@ -143,15 +143,16 @@
 - 说明:这是导演级分镜设计,由技能在组装单段 Prompt 时按内容自动编排,不进用户风格问卷。
 
 ## 26. 插画展示方式(Illustration Presentation,随机使用不固化)
-- 默认:技能在单次内随机选用,不恒定为一种
-- 可选项(一场视频内可混用):
-  - 全屏铺盖(插画整屏铺开,口播人物退为背景或短暂隐去)
+- **硬性规则(用户 2026-09-17 新增)**:插画**禁止全屏覆盖**,只能以「局部」形式出现在画面中。以下两类全屏方式**永久禁用**:
+  - ❌ 全屏铺盖(插画整屏铺开,口播人物退为背景或短暂隐去)
+  - ❌ 人物画中画式全屏(插画铺满整屏,口播人物以圆形 / 正方形画中画出现在画面某区域内)
+- 默认:技能在单次内随机选用**局部**方式,不恒定为一种
+- 可选项(仅限局部,一场视频内可混用):
   - 圆形画中画(在口播画面的某 1–2 个区域嵌入圆形 PiP 展示插画)
   - 条状画中画(在口播画面底部 / 顶部拿一条区域展示插画 PiP)
   - 侧边画中画(画面一侧矩形 / 圆角区域嵌入插画)
-  - **人物画中画式全屏**(插画铺满整屏,口播人物以圆形 / 正方形画中画出现在画面某区域内,即"插画主、人物 PiP"的反向构图)
-- **随机组合**:同一视频里插画出现多次时,可轮换不同展示方式(如首次全屏、二次圆形 PiP),不要全部按一种方式来;与第三节「插画插入点」配合。
-- 说明:抽象可视化插画(Image 3/4)以这些方式融入单段画面,不切分段。
+- **随机组合**:同一视频里插画出现多次时,可轮换不同局部展示方式(如圆形 PiP、二次条状 PiP),不要全部按一种方式来;与第三节「插画插入点」配合。
+- 说明:抽象可视化插画(Image 3/4)以这些**局部**方式融入单段画面,不切分段。
 
 ## 27. 门店丰富元素(Scene Ambience / 道具与群演,增加画面丰富性)
 - 默认:技能在门店场景里随机点缀 2–3 个丰富元素

+ 72 - 0
references/lipsync-fal.md

@@ -0,0 +1,72 @@
+# 对口型(Lip-Sync)落地路线 — fal.ai
+
+> 适用:C 方案(剥离静音视频 + 外部 TTS 配音)之后的「让口播音频对上人物嘴型」这一步。
+> 结论一句话:**优先用 fal.ai 的 `fal-ai/sync-lipsync`**,它保原时长、保原分辨率、能跳过无脸帧(插画穿插段)。
+
+## 1. 为什么不用 Wan3.0「视频编辑模式」做对口型
+
+- Wan3.0 编辑模式 `reference_audio` 上限 **15s**,30s 视频必须拆片 → 段间场景断裂、某段失败丢音频。
+- 它是「重绘画面」而非「只动嘴」,不适合"上传已有成片 + 外部音频"。
+
+## 2. fal.ai 上可用的「视频+音频 → 对口型」模型对比
+
+| 模型 ID | 输入 | 时长/分辨率表现 | 适用性 |
+|---|---|---|---|
+| **`fal-ai/sync-lipsync`** | `video_url` + `audio_url` + `model`(lipsync-1.9.0-beta) + `sync_mode` | ✅ **原时长保留、原分辨率保留** | ⭐ 首选,最稳 |
+| `fal-ai/latentsync` | `video_url` + `audio_url` (+loop_mode/seed/guidance_scale) | ≤40s($0.20/条) | ⚠️ 有人脸检测门槛,含无脸帧的视频会报 `face_detection_error` |
+| `fal-ai/musetalk` | `source_video_url` + `audio_url` | ❌ 会把 30.6s 裁成 27.4s、分辨率改 606x1080 | ✗ 裁尾句、质量差,弃用 |
+| `fal-ai/wav2lip` | (`video_url` + `audio_url`) | 未实跑 | 备用 |
+| `fal-ai/omnihuman/v1.5`、`fal-ai/veed/fabric/avatar` | 图片 + 音频 | — | ✗ 是"照片生成说话视频",重绘场景,不适用 |
+
+**选型经验(重要)**:视频里若混有**无脸帧**(卡通风插画穿插、产品特写、空镜),
+- LatentSync 采样到无脸帧 → 整体报 `No face detected` 拒绝;
+- MuseTalk 会裁切/改分辨率;
+- **sync-lipsync 逐帧处理、跳过无脸段,且保住原时长与原分辨率** → 这类混剪视频用它。
+
+> 另注:本项目的脸是 **AI 生成数字人**,检测器容易漏(LatentSync 就漏了),sync-lipsync 能识别。
+
+## 3. 调用方式(Python)
+
+```python
+import fal_client, requests, json
+
+# 新版包名是 fal_client(不是旧版 fal):pip install fal-client
+# 凭据走环境变量 FAL_KEY="KEY_ID:SECRET"
+
+video_url = fal_client.upload_file("input.mp4")   # ⚠️ 路径/文件名必须纯 ASCII
+audio_url = fal_client.upload_file("v4_16k.wav")
+
+result = fal_client.subscribe(
+    "fal-ai/sync-lipsync",
+    {"video_url": video_url, "audio_url": audio_url,
+     "model": "lipsync-1.9.0-beta", "sync_mode": "cut_off"},
+    with_logs=True,
+)
+url = result["video"]["url"]
+open("out.mp4", "wb").write(requests.get(url, timeout=600).content)
+```
+
+通用脚本:`去角质频率/fal_lipsync.py <model_id> <out.mp4> [video] [audio]`
+
+## 4. 输入预处理(推荐)
+
+```bash
+# 视频:统一到 25fps(MuseTalk/LatentSync 训练帧率),如需可冻结末帧补齐时长
+ffmpeg -y -i 静音成片.mp4 -vf "fps=25" -c:v libx264 -pix_fmt yuv420p -an input.mp4
+# 音频:转 16kHz 单声道(编码器标准输入)
+ffmpeg -y -i 配音.wav -ar 16000 -ac 1 v4_16k.wav
+```
+
+## 5. 踩坑清单
+
+1. **文件名必须纯 ASCII**:中文名 → `'ascii' codec can't encode ...` → SDK 回退 gcs → `Invalid storage type`。改 `v4_16k.wav` 即可。
+2. **新版 fal-client 导入名是 `fal_client`**,不是 `fal`。
+3. **账号锁定**:`403 {"detail":"User is locked. Reason: TOP_UP."}` = fal 账号需充值/绑卡,非 key 格式问题。
+4. **上传/推理鉴权自检**:`curl -s -o /dev/null -w "%{http_code}" https://fal.run/<model> -H "Authorization: Key $KEY" -H "Content-Type: application/json" -X POST -d '{}'` → 422=端点正常(参数校验失败),403=账号/key 问题。
+5. **查模型参数**:`https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=<url-encoded model id>`(带 `Authorization: Key`)。
+
+## 6. 成本参考
+
+- sync-lipsync:按次(Sync.so 商业口播),单条 30s 量级仍远低于可灵 API 的 ¥25–36。
+- latentsync:≤40s = **$0.20/条**。
+- musetalk:约 **$0.04/次**(但本场景不可用)。

+ 57 - 30
references/wan3-workflow.md

@@ -8,7 +8,7 @@
 - **单段时长上限 30 秒 / 30fps**:直接满足技能"一个片段 ≤30 秒"的硬约束。
 - **原生音视频**:一次调用即生成"画面 + 原生语音对白 + BGM + 音效",且原生对口型。因此口播无需单独 TTS 再对口型——把口播写进 prompt 作为对白,音色文件作为 `reference_audio` 传入即可。
 - **多模态参考(最多 20 个素材)**:图片 ≤10 张(单张 ≤20MB)、音频 ≤5 段(单段 ≤15s、≤15MB)、视频 ≤5 段。本技能只用"图片参考 + 音频参考"。
-- **参考引用语法**:prompt 中用 `Image 1` / `Image 2` / `Audio 1` 按 `media` 数组顺序引用素材。例如 media[0]=人物五视角图 → prompt 里写"Image 1 中的女性"。
+- **参考引用语法(重要坑·2026-09-18 修正)**:`media` 数组顺序在**内部**对应素材即可,**绝不能在 prompt 文本里写 `Image 1` / `Image 2` / `Audio 1` 这类标记**。实测 wan3 会把 prompt 中出现的 `Image N`/`Audio N` 文字当成对白**直接念出来**,形成"开头莫名其妙的一段话"(BUG B)。正确做法:用自然语言描述画面与音色,例如"一位女性…身处怡呵美门店内…""原生清晰女声普通话对白(音色参考随附音频)",**不出现任何 `Image N`/`Audio N` 字样**。素材内容靠 media 数组顺序与画面/音色描述间接对应,不必显式编号。
 - **不支持二次创作**:wan3 虽有视频编辑 / 延长模式,但技能第 8 条要求"每次修改都重新组装完整脚本重发",故**只用文生/参考生视频一种模式,禁用 edit / extend**。
 
 ## 二、接口与鉴权
@@ -33,46 +33,65 @@
 | 音色文件 | 公网 URL `config.brand_assets.voice_ref.url`(优先)或本地 `assets/brand/voice_ref.*`(兜底) | `reference_audio` | `Audio 1` | 单段 ≤15s;超长则裁剪一段代表音色 |
 | (生成输入,不提交)人物五视角图 / 制服细节图 / 立体 LOGO | 固定素材,仅用于生成 Image 1 / Image 2,本身不提交 wan3 | — | — | 锁死、不可被自动生成覆盖 |
 
-> media 数组顺序即 prompt 中 Image/Audio 编号顺序,组装时务必对应。提交的是**确认后的生成物料**,不是原始 4 项文件本身。
+> ⚠️ 表中"prompt 引用"列的 `Image 1`/`Image 2`/`Image 3`/`Image 4`/`Audio 1` **只是素材顺序的内部代号,绝不能原样写进 prompt 文本**(否则会被 wan3 当对白念出,见第一节)。prompt 里用自然语言描述画面与音色即可,素材靠 media 数组顺序间接对应。
 
-> media 数组顺序即 prompt 中 Image/Audio 编号顺序,组装时务必对应。
+> 提交的是**确认后的生成物料**,不是原始 4 项文件本身。
 
 ## 四、单段 30 秒 prompt 范式(编导分镜版)
 
 一个 prompt 描述**整段 30 秒**的连续画面(不要拆成多个 Shot 分开发请求)。要把它当作一条**专业编导分镜**来写——人物必须在门店场景里"活"起来,不能一直正对镜头站桩。必须覆盖四块:**人物行为动线 / 运镜组合 / 插画展示方式 / 门店丰富元素**(对应 `elements.md` 元素 25/26/27/12)。结构范式:
 
+> 范式按官方「总体描述 + 镜头序号 + 时间戳 + 分镜内容」公式组织;时间戳用英文 `Shot N [x-y s]`,台词用花括号 `{}` 圈出。⚠️ **绝不在 prompt 里写 `Image N`/`Audio N`**(会被当对白念出);用自然语言描述画面与音色。
+
 ```
-Image 1 中的女性(即这张已确认的人物完整形象照:与图中面孔、深栗色半扎长发、斜刘海一致,
-身穿[米白]美容师制服,胸牌样式与确认稿一致、不可更改),
-身处 Image 2 的怡呵美门店内(墙面有怡呵美立体 LOGO,背景虚化;店内可见[门店丰富元素:
-前台 / 美容床 / 沙发 / 产品陈列架,可选其他店员从画面边缘虚化走过])。
-
-[人物行为动线 · 按口播长短设计,全程与场景互动、不站桩]:
-开场她[从走动中入画 / 端坐于面诊桌前]自然引入[主题];讲到中段时[起身缓步走向产品架 /
-从走动落座于沙发]边走边说;结尾[回到中近景、手势收束]。说话间伴随[整理产品 / 手势比划 /
-轻微点头]等真实肢体动作。
-
-[运镜组合 · 随机穿插 2–3 种]:
-开场[中近景固定 / 极轻微横摇]→ 人物走动时[跟随拍摄]→ 讲到核心点时[缓慢推近]→
-结尾[缓慢拉远]收束。整体[手持微晃]保留真实手机拍摄质感。
-
-她自然亲和地讲解[主题]:
-"[口语化口播全文,含自然停顿]"
-
-[插画展示方式 · 随机使用,不固化一种](在[插画插入点:第 N 句]处触发,对应 Image 3 / Image 4):
-- 方式示例:[全屏铺盖插画] / [画面右侧圆形画中画嵌入插画] / [顶部条状画中画展示插画] /
-  [侧边圆角区域 PiP] / [人物画中画式全屏:插画铺满整屏、口播人物以圆形或正方形 PiP 出现在画面某区域];
-  同一视频多次出现插画时可轮换不同方式。
-(插画为抽象皮肤结构可视化:透明表皮层、缓慢流动光点、柔和结构变化,无真人皮肤、无血液、无文字)
-
-原生生成清晰女声普通话对白(音色参考 Audio 1)、轻微环境音、无背景音乐。
-绝对禁止:字幕/任何文字/卡通化/塑料皮肤/变脸/服装漂移/场景跳变/其他清晰正脸人物抢镜。
+【总体描述】
+一位身穿[米白]美容师制服、[深栗色半扎长发 + 斜刘海]、胸牌样式与参考形象照一致的女美容师,
+在怡呵美门店内(墙面有怡呵美立体 LOGO,背景虚化,可见前台、美容床、沙发、产品陈列架,
+一名店员从画面边缘虚化走过)拍摄。画面中的女美容师、门店与插画均依据随附参考图生成,
+须保持人物脸型/发型/制服/胸牌一致,门店须含怡呵美立体 LOGO,插画为卡通动漫风抽象可视化(无真人、无文字)。
+
+整体动线:[开场端坐于面诊桌前自然引入话题,中段起身缓步走向产品陈列架、边走边说,
+结尾保持站立、抬手手势收束];说话间以自然手势比划、轻微点头等真实肢体动作呈现。
+
+镜头语言:[开场中近景极轻微横摇,人物走动时跟随拍摄,讲到核心点时缓慢推近,结尾缓慢拉远收束];
+整体手持微晃保留真实手机拍摄质感。
+
+语气与节奏:她自然亲和地讲解,像真人面对面聊天一样有温度有起伏——问句好奇上扬、肯定句沉稳、
+列举句轻快有层次、收束句温和坚定;整体语速比自然对话放慢约一半、从容舒缓,让内容自然撑满约[30s],
+句与句之间留约[0.5s]自然气口,不赶。
+
+声音:原生清晰女声普通话对白(音色参考随附音频)、轻微环境音、无背景音乐;
+口播须有自然语调起伏与停顿,禁止平淡匀速念稿。
+
+硬性约束:口播须严格限于下列分镜台词,禁止添加、删改或自由发挥任何文案之外的语句、语气词;
+插画仅以局部形式出现(圆形/圆角画中画),禁止全屏覆盖;绝对禁止出现字幕、任何画面文字、
+卡通化人物、塑料皮肤、变脸、服装漂移、场景跳变、或其他清晰正脸人物抢镜。
+
+结构说明:以下「Shot序号 + 时间戳 + 分镜内容」为本片分镜脚本;Shot序号与时间戳是给模型的
+节奏结构指令,不是台词,禁止当作语音读出;也禁止在任意空白时段补加台词或语气词。
+
+【分镜脚本】
+Shot 1 [0-4s]:[开场中近景,女美容师端坐面诊桌前,手持微晃手机质感,镜头极轻微横摇];
+她好奇上扬地提问{[口播句1]},句末留自然气口。
+
+Shot 2 [4-7.5s]:[仍端坐,中近景];她自然沉稳肯定{[[订期]去角质的→按用户文案填,见下方多音字提示]},语气笃定、不赶。
+
+Shot 3 [7.5-12.5s]:[她起身缓步走向产品陈列架、边走边说,镜头跟随拍摄];列举句轻快有层次{[口播句3]}。
+
+Shot 4 [12.5-19.5s]:[走到陈列架前站立,讲到核心点时镜头缓慢推近;此处画面右侧嵌入圆形画中画
+展示[插画1](抽象可视化,无真人无文字,禁全屏)]。该句放慢加重带警示,讲到「[核心重音点]」时
+稍作重音并留自然停顿{[口播句4]}。
+
+Shot 5 [19.5-26s]:[切换为画面侧边圆角区域画中画展示[插画2](同风格,不铺满整屏)];她自然亲和{[口播句5]}。
+
+Shot 6 [26-30s]:[结尾缓慢拉远收束,她保持站立、抬手手势收束,中近景];收束句温和坚定{[口播句6]},留足停顿感。
 ```
 
-- 口播全文直接写进 prompt(作为对白台词),wan3 原生生成语音并对口型。
+- 口播台词用花括号 `{}` 圈出、逐句写进各 Shot 的「分镜内容」,wan3 原生生成语音并对口型;`{}` 之外的是画面/运镜描述,不会被当台词。
 - **人物行为动线**按口播时长设计:≤15 秒用 1–2 个动作切换,30 秒用 3–4 个动线(走动/坐/起/落座/整理)。
 - **运镜**每条约 2–3 种按时间轴穿插,不全程单一机位;**插画展示方式**一场内可混用多种,不恒定为一种;**门店丰富元素**随机点缀 2–3 个,增加景深与真实感。
-- 时长参数 `duration=30`,`ratio=9:16`,`resolution` 按 config。
+- **时间戳即控速锚点(关键)**:`Shot N [x-y s]` 连续铺满目标时长(如 0–30s),既是分镜结构也是节奏约束。wan3 实测在「强制时长 > 口播实际长度」的空白时段会**自动编造废话填充**(padding 幻觉),故内容必须真实撑满时间戳区间——靠放慢语速 + 句间留足气口,而非空撑时长。宁可 `duration` 用 `−1` 智能时长(按内容自动定长,上限 30s)也不要硬撑。
+- 时长参数 `duration` 默认 `30`,`ratio=9:16`,`resolution` 按 config;若口播偏短或要规避 padding,可显式缩短 `duration` 或设 `−1`。
 - 反向约束(硬性禁令)也写进 prompt 负向描述:无字幕/无文字/无卡通化/无塑料皮肤/无变脸/无服装漂移/无场景跳变。
 
 ## 五、LOGO 落位(保证成片一定出现 LOGO)
@@ -112,3 +131,11 @@ python scripts/generate_video.py \
 - `reference_audio` 单段 ≤15s、≤15MB;音色样本过长先裁剪。
 - `duration` 必须 ≤30,超了先在技能侧精简口播(见 SKILL.md 第 5 步硬卡循环)。
 - 不传 `X-DashScope-Async: enable` 会报"current user api does not support synchronous calls"。
+
+### 实测新增坑(2026-09-17 ~ 09-18,去角质频率项目验证)
+
+- **坑1 · `Image N`/`Audio N` 被当对白念出(BUG B)**:在 prompt 文本里出现的 `Image 1`/`Audio 1` 等素材编号会被 wan3 当成台词念出来,表现为"片头莫名其妙的一段话"。**解法:prompt 中绝不写 `Image N`/`Audio N`,改用自然语言描述画面与音色**(如"一位女性…门店内…""音色参考随附音频")。已验证删掉编号后片头干净。
+- **坑2 · 强制时长 > 口播长度 → padding 幻听**:当 `duration` 大于口播真实时长,wan3 会在空白段**自动编造语句填充**(如结尾冒出"也沒衣服看管的")。**解法:让口播内容真实撑满时间戳区间(放慢语速 + 句间气口),或改用 `duration=-1` 智能时长**;不要硬撑空时长。
+- **坑3 · 多音字误读(如"定期"→"顶期")**:wan3 端到端 TTS 对多音字消歧不稳定,纯文本时好时坏;**拼音标注(如"定(dìng)期")反而会被整段念错(实测读成"定定妻"),不可用**。可用近义替换规避:把"定期"换成"订期"(同音 dìng、无歧义、语义贴),或"按周期"/"有规律地"。**提交前不要回改用户文案**,该替换本身就是防错手段。
+- **推荐流程 · 生成后 ASR 自查**:成片下载后用本地 ASR(funasr / faster-whisper,不外传素材)提取音频转文字,逐字核对:①片头是否乱读 ②时间数字/Shot序号是否被念出 ③多音字读音 ④片尾是否 padding 废话。先自查再交付,避免来回试错。详见技能 `scripts/` 下的 `extract_audio.py` + `asr_wordlevel.py` 用法(从 mp4 抽音频→逐词时间戳)。
+- **参考音频语义前置**:`reference_audio` 本是提取音色,但若样本本身带口播语义,可能被前置到片头。建议从音色源音频的**中段**裁一段(避开句首)作 ≤15s 样本。

+ 160 - 0
scripts/compliance_scan.py

@@ -0,0 +1,160 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+美业护肤科普口播 · 敏感词 / 合规机器初筛
+整合自 content-reviewer 与 medical-content-compliance-auditor 两技能,按美业场景裁剪。
+输出 markdown 审查报告(风险等级 + 法规依据 + 合规替换 + 结论三选一)。
+
+用法:
+  python compliance_scan.py --text "口播全文"
+  python compliance_scan.py --file path/to/script.md
+  python compliance_scan.py --file path/to/script.md --output report.md
+"""
+import argparse
+import sys
+from pathlib import Path
+
+# 词库: (词, 类别, 风险符号, 法规依据, 合规替换)
+WORD_LIST = [
+    # 绝对化用语
+    ("最好", "绝对化用语", "🟠", "《广告法》第9条", "适合 / 值得考虑"),
+    ("最佳", "绝对化用语", "🟠", "《广告法》第9条", "合适"),
+    ("第一", "绝对化用语", "🟠", "《广告法》第9条", "代表性 / 较早"),
+    ("顶级", "绝对化用语", "🟠", "《广告法》第9条", "优质"),
+    ("唯一", "绝对化用语", "🟠", "《广告法》第9条", "特色"),
+    ("国家级", "绝对化用语", "🟠", "《广告法》第9条", "(删除)"),
+    ("最安全", "绝对化用语", "🟠", "《广告法》第9条", "较安全"),
+    ("最有效", "绝对化用语", "🟠", "《广告法》第9条", "较有效"),
+    ("全球领先", "绝对化用语", "🟠", "《广告法》第9条", "行业先进"),
+    # 治愈承诺
+    ("根治", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "改善"),
+    ("治愈", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "改善"),
+    ("根除", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "减少"),
+    ("彻底消除", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "明显缓解"),
+    ("永不复发", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "减少反复"),
+    ("包治", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "辅助改善"),
+    ("治好", "治愈承诺", "🔴", "《广告法》第17条 / 《医疗广告管理办法》", "改善"),
+    # 保证性承诺
+    ("保证", "保证性承诺", "🟠", "《广告法》第9、25条", "有助于"),
+    ("承诺", "保证性承诺", "🟠", "《广告法》第9、25条", "预期(疗效类承诺删除)"),
+    ("100%", "保证性承诺", "🟠", "《广告法》第9、25条", "多数情况下"),
+    ("无效退款", "保证性承诺", "🟠", "《广告法》第25条", "(删除)"),
+    ("签约治疗", "保证性承诺", "🟠", "《广告法》第25条", "(删除)"),
+    # 功效夸大·即时
+    ("一夜回春", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐步改善"),
+    ("秒变", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐渐"),
+    ("瞬间", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐渐"),
+    ("奇效", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "效果"),
+    ("神效", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "效果"),
+    ("立竿见影", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "循序渐进、需要坚持"),
+    ("立马见效", "功效夸大·即时", "🟡", "《广告法》第28条(虚假宣传)", "逐渐见效"),
+    # 医疗功效越界(护肤品禁宣称)
+    ("治疗", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "护理 / 改善"),
+    ("药用", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "(删除)"),
+    ("消炎", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "舒缓"),
+    ("杀菌", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "洁净"),
+    ("抗炎", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "舒缓"),
+    ("抗菌", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "清洁"),
+    ("医用", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "(删除)"),
+    ("药效", "医疗功效越界", "🟠", "《化妆品监督管理条例》/《广告法》", "(删除)"),
+    # 患者证言·对比
+    ("术前术后", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除,改用示意图)"),
+    ("对比图", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除,改用示意图)"),
+    ("疗效见证", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除)"),
+    ("患者案例", "患者证言·对比", "🔴", "《医疗广告管理办法》", "(删除 / 脱敏)"),
+    # 平台诱导风险
+    ("加微信", "平台诱导风险", "🟡", "抖音/小红书审核规则", "进店了解"),
+    ("私信我", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
+    ("点击下方链接", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
+    ("限时优惠", "平台诱导风险", "🟡", "抖音/小红书审核规则", "活动期间优惠"),
+    ("最低价", "平台诱导风险", "🟡", "抖音/小红书审核规则", "优惠价"),
+    ("免费领", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
+    ("扫码", "平台诱导风险", "🟡", "抖音/小红书审核规则", "(删除)"),
+]
+
+RISK_RANK = {"🔴": 4, "🟠": 3, "🟡": 2, "🟢": 1}
+CONCLUSION = {
+    4: "不予通过(存在极高违规,必须删除/替换后重检)",
+    3: "修改后通过(高)—— 须替换 🟠 项方可进入生成",
+    2: "修改后通过(中)—— 建议替换 🟡 项",
+    1: "修改后通过(低)—— 建议酌情优化",
+    0: "通过(未检出违禁词)",
+}
+
+
+def scan(text: str):
+    hits = []
+    seen = set()
+    for word, cat, risk, law, repl in WORD_LIST:
+        if word in text and word not in seen:
+            seen.add(word)
+            hits.append({
+                "word": word, "cat": cat, "risk": risk,
+                "law": law, "repl": repl,
+                "count": text.count(word),
+            })
+    hits.sort(key=lambda h: -RISK_RANK[h["risk"]])
+    return hits
+
+
+def build_report(text: str, hits: list) -> str:
+    lines = []
+    lines.append("## 敏感词 / 合规检测报告\n")
+    lines.append("### 内容类型")
+    lines.append("抖音短视频口播文案(美业护肤科普)\n")
+    lines.append("### 检测条目")
+    if not hits:
+        lines.append("\n> ✅ 未检出高危违禁词。\n")
+    else:
+        lines.append("")
+        lines.append("| 违规点 | 原表述 | 风险等级 | 法规依据 | 合规替换 |")
+        lines.append("|--------|--------|----------|----------|----------|")
+        for h in hits:
+            cnt = f"(×{h['count']})" if h["count"] > 1 else ""
+            lines.append(
+                f"| {h['cat']} | 「{h['word']}」{cnt} | {h['risk']} {('极高' if h['risk']=='🔴' else '高' if h['risk']=='🟠' else '中' if h['risk']=='🟡' else '低')} | {h['law']} | {h['repl']} |"
+            )
+        lines.append("")
+    max_rank = max((RISK_RANK[h["risk"]] for h in hits), default=0)
+    lines.append("### 审查结论")
+    lines.append(CONCLUSION[max_rank] + "\n")
+    if hits:
+        lines.append("### 建议")
+        for i, h in enumerate(hits, 1):
+            lines.append(f"{i}. 「{h['word']}」({h['cat']})→ 替换为「{h['repl']}」,规避对应风险。")
+        lines.append(f"{len(hits)+1}. 修改后建议再跑一次本脚本确认无新增违禁词。")
+    return "\n".join(lines)
+
+
+def main():
+    ap = argparse.ArgumentParser(description="美业口播敏感词/合规机器初筛")
+    g = ap.add_mutually_exclusive_group(required=True)
+    g.add_argument("--text", help="直接传入口播文本")
+    g.add_argument("--file", help="传入文本/脚本文件路径(.txt/.md)")
+    ap.add_argument("--output", help="将报告写入该文件(可选)")
+    args = ap.parse_args()
+
+    if args.text:
+        text = args.text
+    else:
+        p = Path(args.file)
+        if not p.exists():
+            print(f"[错误] 文件不存在: {args.file}", file=sys.stderr)
+            sys.exit(2)
+        text = p.read_text(encoding="utf-8")
+
+    hits = scan(text)
+    report = build_report(text, hits)
+    print(report)
+
+    if args.output:
+        Path(args.output).write_text(report, encoding="utf-8")
+        print(f"\n[已写入报告] {args.output}", file=sys.stderr)
+
+    # 退出码:极高/高违规 → 非0,便于流程判断
+    max_rank = max((RISK_RANK[h["risk"]] for h in hits), default=0)
+    sys.exit(0 if max_rank <= 2 else 1)
+
+
+if __name__ == "__main__":
+    main()

+ 3 - 3
scripts/generate_video.py

@@ -202,7 +202,7 @@ def main():
              "如 /abs/char.jpg:reference_image 或 https://cdn.x/char.jpg:reference_image;"
              "公网 URL 默认直接透传;本地文件编码 base64 直传;详见 config.reupload_external_urls。",
     )
-    ap.add_argument("--duration", type=int, default=30, help="时长(秒),≤30")
+    ap.add_argument("--duration", type=int, default=30, help="时长(秒),≤30;传 -1 启用智能时长模式(按实际口播内容自动定长,上限 30s)")
     ap.add_argument("--ratio", default=None, help="画幅,如 9:16")
     ap.add_argument("--resolution", default=None, help="分辨率,如 720P")
     ap.add_argument("--model", default=None)
@@ -213,8 +213,8 @@ def main():
 
     if args.duration > 30:
         sys.exit("wan3 单次生成上限 30 秒,duration 必须 ≤30。请先在技能侧精简口播。")
-    if args.duration < 2:
-        sys.exit("duration 必须 ≥2 秒。")
+    if args.duration != -1 and args.duration < 2:
+        sys.exit("duration 必须 ≥2 秒,或传 -1 启用智能时长模式(模型按实际内容自动定长,上限 30s)。")
 
     key = resolve_key(here)