diff --git a/config/media/doubao.yaml b/config/media/doubao.yaml index 99644bd..29955ba 100644 --- a/config/media/doubao.yaml +++ b/config/media/doubao.yaml @@ -52,6 +52,10 @@ video: default_ratio: "16:9" # 16:9 / 9:16 / 1:1 / 4:3 / 3:4 / 21:9 / adaptive default_duration: 5 # 4-15s default_watermark: false + # generate_audio: Seedance 2.0 旗舰特性 —— 原生 AI 生成背景音效 / 角色对白(无需后期配音)。 + # 开启会增加 token 消耗(模型还要算音轨),cost 比纯视频高;默认关闭让 cost 可预测, + # 用户要带音的视频(广告 / 角色对白)时显式传 true。 + default_generate_audio: false # 轮询参数 request_timeout_s: 60 # submit POST 超时(异步,只是提交) diff --git a/skills/videogen/SKILL.md b/skills/videogen/SKILL.md index 4b359fd..8141669 100644 --- a/skills/videogen/SKILL.md +++ b/skills/videogen/SKILL.md @@ -115,11 +115,12 @@ description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何 > ratio: 16:9(ppt 横版) > duration: 5 秒 > watermark: false +> generate_audio: false(静音视频,后期 ppt 加 BGM) > 预计花费: ¥4.00 > 预计等待: 30-90 秒 > ```` > -> 这样开烧?要改什么?(改 prompt 文字 / 改时长 / 换比例 / 降到 480p 省钱) +> 这样开烧?要改什么?(改 prompt 文字 / 改时长 / 换比例 / 降到 480p 省钱 / 开 audio 加 AI 配音) 然后 ⛔ **BLOCKING:等用户明确回复**。 @@ -169,6 +170,21 @@ description: 用豆包 Seedance 2.0 Fast 生视频(`seedance` tool)。**任何 |---|---| | `false` | 默认无水印(申报 / ppt / 客户交付都不该带);仅当用户明确说"加水印"才传 `true` | +### `generate_audio`(Seedance 2.0 旗舰特性) + +| 默认 | 何时开 | +|---|---| +| `false` | 默认关 —— 控成本 + 大多数 ppt / 申报场景用静音视频(后期配音 / 当背景视频)。**模型还要算音轨,开启 cost 会高于纯视频估算**(具体增幅未实测,首次开时盯一下返回的 tokens)。 | +| `true` | 用户明确要**带声音的视频**:广告 / 短剧 / 角色对白 / 配乐场景。**调用前必须告诉用户**"开启 audio,cost 会高于估算 ¥X"并等确认。 | + +prompt 写法变化:开 `generate_audio=true` 时,prompt 里要描述**声音是什么**(背景音 / 音效 / 对白台词具体说啥 / 音色),否则模型随机配,效果难控: + +``` +... 背景音「鲜切现摇」,女生音色,轻快鼓点卡点 +``` + +参考火山方舟官方 r2v 例子(`r2v_tea_*`):每个镜头段都明示"背景音 XX / 卡点 XX / 音效 XX"。**不写 = 不可预期**。 + ## 调用范式 **前置条件**:用户已经看过最终 prompt + 所有参数,明确回复"可以" / "OK" / "出片吧" 之类。**没看到这个确认就不要调**。 @@ -232,6 +248,7 @@ seedance( - ❌ prompt 里写否定 "no shaking, not blurry" —— Seedance 不支持 negative,反向起效 - ❌ 让用户在 seedream / seedance 之间默默替他决定 —— 模糊就一句话问明白 - ❌ phase 1 拿用户已生成的图试图 i2v —— **当前不支持**,明确告诉用户 +- ❌ 用户没说要声音就把 `generate_audio` 设 true —— 多花钱且大概率配出不合用户预期的随机音效;开它前必须问"要 AI 配音吗?cost 会高于估算" - ❌ 用 `run_python` 调 `requests` 裸打豆包 API —— 走 `seedance` tool(已封装异步轮询 + 计费 + 落盘 + meta + cancel) ## 输出 diff --git a/tools/seedance.py b/tools/seedance.py index 6456192..0270f03 100644 --- a/tools/seedance.py +++ b/tools/seedance.py @@ -102,6 +102,13 @@ class SeedanceTool(Tool): "type": "boolean", "description": "是否打豆包水印。默认 false(申报/PPT 场景反需求)。", }, + "generate_audio": { + "type": "boolean", + "description": ( + "是否同步生成 AI 背景音 / 对白(Seedance 2.0 旗舰特性)。默认 false 控成本;" + "广告 / 短剧 / 角色对白等场景传 true,模型会一并算音轨,cost 高于纯视频。" + ), + }, }, "required": ["prompt"], } @@ -135,6 +142,7 @@ class SeedanceTool(Tool): ratio: Optional[str] = None, duration: Optional[int] = None, watermark: Optional[bool] = None, + generate_audio: Optional[bool] = None, ) -> str: if not (prompt or "").strip(): return "[Error] prompt 不能为空" @@ -145,6 +153,9 @@ class SeedanceTool(Tool): chosen_ratio = ratio or cfg.get("default_ratio", "16:9") chosen_duration = int(duration) if duration is not None else int(cfg.get("default_duration", 5)) chosen_watermark = bool(cfg.get("default_watermark", False)) if watermark is None else bool(watermark) + chosen_generate_audio = ( + bool(cfg.get("default_generate_audio", False)) if generate_audio is None else bool(generate_audio) + ) fps = int(cfg.get("fps", 24)) submit_timeout = float(cfg.get("request_timeout_s", 60)) @@ -162,6 +173,7 @@ class SeedanceTool(Tool): "resolution": chosen_resolution, "duration": chosen_duration, "watermark": chosen_watermark, + "generate_audio": chosen_generate_audio, } t0 = time.monotonic() @@ -232,6 +244,7 @@ class SeedanceTool(Tool): "duration_s": chosen_duration, "fps": fps, "watermark": chosen_watermark, + "generate_audio": chosen_generate_audio, "tokens": tokens_actual, "tokens_estimated": tokens_estimated, "price_cny_per_mtoken": price_t2v, @@ -258,7 +271,11 @@ class SeedanceTool(Tool): price_cny_per_mtoken=price_t2v, has_video_input=False, # phase 1 仅 t2v;i2v 接入后这里读 body 判断 watermark=chosen_watermark, - extra_units={"cgt_id": cgt_id, "elapsed_s": round(elapsed, 1)}, + extra_units={ + "cgt_id": cgt_id, + "elapsed_s": round(elapsed, 1), + "generate_audio": chosen_generate_audio, + }, ) except Exception as e: print(f"[seedance] record_video_usage failed: {type(e).__name__}: {e}", flush=True) @@ -268,7 +285,8 @@ class SeedanceTool(Tool): # 前端 extractMediaBanner 已 whitelist seedance,正则抓 key=value 挂徽章 return ( f"[seedance] model={model_id} · resolution={chosen_resolution} · ratio={chosen_ratio} · " - f"duration={chosen_duration}s · cost=¥{cost_cny:.2f} · elapsed={elapsed:.1f}s\n" + f"duration={chosen_duration}s · audio={chosen_generate_audio} · " + f"cost=¥{cost_cny:.2f} · elapsed={elapsed:.1f}s\n" f"saved: {disp}\n" f"prompt={prompt!r}\n" f"watermark={chosen_watermark} cgt_id={cgt_id}"