{
  "timestamp": "2026-05-13T01:42:49+0800",
  "status": "ok",
  "summary": "completed",
  "mode": "claude_api_multi_turn",
  "multi_turn": true,
  "payload_text": "请根据以下 SKILL.md 规则和 step1 分析数据，对歌词进行改写。\n\n## 🎯 创作纲要（Turn 1 已确认）\n\n**情感领地（compass）**：沉默里还没说完的质问\n  ↑ 这是指南针，不是终点站。**先写具体场景**，让那句最重要的话从画面里自己长出来——不要预先计划\"第几段说什么结论\"。\n\n**结构引擎**：C\n  ↑ 用此引擎的骨架逻辑，但场景、意象、hook 都在写作过程中自然发现。\n\n⚠️ 行长：Verse 每行 8-13字；Chorus 每行 ≤10字\n⚠️ 副歌词汇：日常用语，小学生能理解。文学词/技术词 → 移到 verse。\n⚠️ 押韵：写词时让内容自然带出押韵；写完每段后逐行检查末字，同段内保持一致韵部即可。\n\n---\n## SKILL.md 规则\n\n# music-pipeline SKILL.md\n\n## 用途\nPGC → AI 翻唱生成完整工作链路。接收用户给出的歌曲信息、PGC 歌词、音频或平台链接，自动串联 **音乐获取 → 音频预处理 → Gemini 分析 → Claude 改写（含封面方案）→ Suno 生成 → 封面生成** 六个阶段，输出可试听的 AI 生成歌曲及专辑封面。\n\n这个 skill 的目标不是\"随便跑出一首歌\"，而是把整个工作流固定下来：\n- 上游素材怎么取\n- 中间 JSON 怎么长\n- 每一步的职责边界是什么\n- 遇到错误时怎么回退\n\n---\n\n## 触发场景\n- 用户说\"跑一下音乐生成链路\"\n- 用户说\"帮我生成这首歌的 AI 版\"\n- 用户给出歌名/歌手，希望自动找音频和歌词\n- 用户给出 PGC 歌词 + 音频，希望生成可直接喂给 Suno 的版本\n- 用户在开发模式下调试某一步（Step 0 / Step 1 / Step 2 / Step 3 / Step 4）\n\n---\n\n## ⚠️ 强制规则：禁止手动 curl，必须用脚本\n\n**禁止手动 curl 调汽水/网易云/QQ 音乐 API。所有操作必须通过脚本执行。**\n\n工作目录：`cd /home/debian/.openclaw/workspace/skills/music-pipeline/scripts`\n\n### 默认：用 pipeline 跑完整链路（三阶段）\n\nStep 2（歌词改写）通过 OpenClaw subagent 执行，不是纯 Python 脚本，所以 pipeline 分三段跑：\n\n```bash\ncd /home/debian/.openclaw/workspace/skills/music-pipeline/scripts\n\n# 准备歌单 CSV（歌名,歌手[,平台]）\necho \"稻香,周杰伦\" > /tmp/songs.csv\n\n# ===== 阶段 1：Step 0 → 1（全自动，约 2-3 分钟/批）=====\npython3 pipeline.py /tmp/songs.csv -o /srv/music-files/pipeline/batch-主题-YYYYMMDD-HHMM --step1-only\n\n# ===== 阶段 2：Step 2 歌词改写（spawn subagent）=====\n# 见下方【阶段 2 流水线编排规范】\n\n# ===== 阶段 3：Step 3 → 5（全自动，--resume 继续）=====\npython3 pipeline.py /tmp/songs.csv --resume /srv/music-files/pipeline/batch-主题-YYYYMMDD-HHMM\n```\n\n> ⚠️ **批次目录命名规范（必须遵守）**：\n> - `-o` 必须指向**最终的、有意义的批次目录**，格式：`/srv/music-files/pipeline/batch-{主题}-{YYYYMMDD}-{HHMM}`\n> - **禁止**先建父目录再传 `-o 父目录/output`——`batch_name` 取的是目录名，这样会导致批次名变成 `output`\n> - **禁止**在同一个父目录下建多个 `output2`/`output3`——失败了应该换新的 batch 目录重跑\n> - 歌单文件放在 `/tmp/songs.csv` 即可，不要放在批次目录里\n\n阶段 1 和 3 全自动（搜歌、下载、Gemini 分析、Suno 生成、混音、封面、飞书导出）。\n阶段 2 通过 spawn subagent 改写歌词。\npipeline 含 checkpoint，任意阶段中断可 `--resume` 续跑。\n\n### 阶段 2 流水线编排规范（必须严格遵守）\n\n**subagent 参数（固定，不要改）：**\n```\nruntime: subagent\nmode: run\nmodel: anthropic/claude-sonnet-4-6\nthinking: low\nrunTimeoutSeconds: 600\n```\n\n**为什么这样设：**\n- Sonnet（不是 Opus）：创作任务不需要深度推理，Opus 的 extended thinking 会在 5 分钟内超时被 abort\n- thinking=low：避免 thinking 阶段消耗过多时间\n- 600s timeout：Sonnet 通常 2-4 分钟完成，600s 留足余量\n\n**subagent task 模板：**\n```\n你是歌词改写 agent。先 read {skill_path}。\n然后读取 {song_dir}/step1_json_a.json\n改写后输出到 {song_dir}/step2_json_b.json\nprompt 不改、[section] 和 (annotation) 保留、唱出来的英文替换为全新内容、英文≥5%时保持中英比例。完成后输出摘要。\n```\n> `{skill_path}` 由 orchestrator 调用 `get_step2_skill_path()` 动态填入：\n> - base profile → `/home/debian/.openclaw/workspace/skills/lyrics-rewriter/SKILL.md`\n> - xiachen profile → `profiles/xiachen/lyrics-rewriter-SKILL.md`\n\n**流水线批次编排（3 首一批）：**\n1. 将需要 step2 的歌曲按 3 首分批\n2. spawn 批次 1 的 3 个 subagent\n3. 批次 1 全部完成后：\n   - 更新 `_state.json` 为 `step2_ready`，写入 `step2_subagent` 元数据\n   - **不要**提前把 `step2` 写进 `completed_steps`\n   - 后台启动 `--resume` 跑批次 1 的 Phase 3\n   - 同时 spawn 批次 2 的 3 个 subagent\n4. 重复直到所有批次完成\n5. subagent 并发上限 5 个（OpenClaw 限制），所以 3 首一批最安全\n\n**subagent 错误处理：**\n\n超时（timed out）：\n- 先检查 step2_json_b.json 是否已写入磁盘\n- 文件存在且 JSON 合法 → 视为成功，继续\n- 文件不存在 → 等 30 秒后重新 spawn 一次\n\nAPI 过载（overloaded_error / LLM error）：\n- **这不是文件问题，是 Anthropic API 暂时过载**\n- 不要检查文件、不要分析原因，直接等 60 秒后重新 spawn\n- 如果连续 2 次过载，降低并发：从 3 个一批改为 2 个一批\n- 过载通常几分钟自行恢复\n\n通用重试规则：\n- 任何 subagent 返回非 \"completed successfully\" 的状态，先检查输出文件是否存在\n- 存在 → 继续；不存在 → 等待后重试，最多重试 2 次\n\n**state 更新（每首歌 step2 写盘后必须做）：**\n```python\nimport json\ns = json.load(open(f'{song_dir}/_state.json'))\ns['status'] = 'step2_ready'\ns['step2_subagent'] = {\n  'agent': 'v3',\n  'output_file': f'{song_dir}/step2_json_b.json'\n}\njson.dump(s, open(f'{song_dir}/_state.json', 'w'), ensure_ascii=False, indent=2)\n```\n> `step2` 真正进入 `completed_steps` 的时机，仍然是 `pipeline.py --resume` 跑过 Step 2.5 原创度校验之后。\n\n**发送结果（全部完成后）：**\n- 每首歌发封面图 + 2 个 mixed mp3\n- 命名：`{新歌名}_V{1|2}_mixed.mp3`\n- 通过飞书 `im/v1/files` 上传 + `im/v1/messages` 发送\n- **封面优先级**：`cover_seedream.jpg`（SeeDream 生成）> `cover_seedream.png` > `suno_cover_0.jpg`（Suno 自带封面，最后 fallback）\n\n### 仅调试/迭代单个步骤时用单步脚本\n\n```bash\ncd /home/debian/.openclaw/workspace/skills/music-pipeline/scripts\n\npython3 steps/step0_fetch.py \"歌名\" \"歌手\"                    # Step 0: 搜歌\npython3 steps/step05_download.py <audio_url> <output.mp3>      # Step 0.5: 下载\npython3 steps/step1_gemini.py <audio.mp3> <lyrics.txt> -o out  # Step 1: Gemini\npython3 steps/step3_suno.py <json_b.json> -o out               # Step 3: Suno\npython3 steps/step35_mix.py <input.mp3> <output.mp3> --json    # Step 3.5: 混音\npython3 steps/step4_seedream.py \"cover prompt\" -o out           # Step 4: 封面\npython3 steps/step5_feishu.py <output_dir>                      # Step 5: 飞书导出\n```\n\npipeline 内部就是 import 这些单步脚本，逻辑完全一致。\n\n---\n\n## 总体原则\n1. **必须用 step 脚本**，不要手动 curl 各音乐平台 API\n2. **PGC 歌词是真值**，Gemini 不负责自由听写整首歌词\n3. **音频负责风格信息**，歌词负责文字真值\n4. **Step 1 的标准输出是 JSON_A**：`{ song_id?, prompt, lyrics }`\n5. **Step 2 的标准输出是 JSON_B**：`{ song_id?, prompt, title, lyrics, cover }`（cover 含封面 prompt）\n6. **Step 4 用 JSON_B 的 `cover_prompt`（顶层字段） 调 SeeDream API 生成封面图**\n7. **开发模式默认回传每一步原始结果**，尤其是 Gemini 原始 JSON、Suno 原始返回和封面图 URL\n\n---\n\n## 完整链路\n\n```\n[输入] 用户给歌名/歌手/链接，或直接给 PGC 歌词 + 音频\n    ↓\n[Step 0] 音乐获取：调用音乐获取 skill，拿到音频链接 + 歌词 + 基本元数据\n    ↓\n[Step 0.5] 音频预处理：下载音频，必要时转成 mp3\n    ↓\n[Step 1] Gemini 分析：分析音频 + 以 PGC 歌词为真值 → 生成 JSON_A（prompt + lyrics）\n    ↓\n[Step 2] Claude 改写：按 lyrics-rewriter 规则 → 生成 JSON_B（prompt + lyrics + title + cover）\n    ↓\n[Step 3] Suno 生成：用 JSON_B 生成歌曲 → 返回 2 条试听链接\n    ↓\n[Step 4] 封面生成：用 JSON_B 的 cover_prompt（顶层字段） 调 SeeDream API → 返回封面图 URL\n    ↓\n[输出] 歌名 + 试听链接 + 封面图 + 中间结果（开发模式）\n```\n\n---\n\n## Step 0：音乐获取（固定方案）\n\n### 目的\n把后续步骤真正需要的素材补齐：\n- 音频链接\n- PGC 歌词\n- 标题 / 歌手 / 平台 / song_id\n\n### 这一阶段解决什么问题\n用户通常不会一次性给全：\n- 可能只说\"跑《特别的人》方大同\"\n- 可能只发一个平台链接\n- 可能只发歌名不发歌词\n\nStep 0 的作用就是把这些零散输入补齐成结构化结果，供 Step 1 使用。\n\n### 输入\n支持这些输入形式：\n- 歌名 + 歌手\n- 平台链接\n- 单独歌名\n- 用户直接给出的音频 + 歌词（此时可跳过 Step 0）\n\n### 输出（推荐 JSON）\n```json\n{\n  \"title\": \"歌曲名\",\n  \"artist\": \"歌手名\",\n  \"platform\": \"netease|qq|qishui|other\",\n  \"song_id\": \"平台歌曲ID或空\",\n  \"audio_url\": \"可访问的音频链接\",\n  \"lyrics\": \"完整 PGC 歌词\"\n}\n```\n\n### 调用策略\n1. **优先统一 skill**：`music-search`\n2. 如果用户明确指定平台，再落到对应平台专用 skill：\n   - `netease-cloud-music`\n   - `qq-music`\n   - `qishui-music`\n\n### 注意事项\n- 能拿到歌词真值时，不要把\"歌词识别\"压力丢给 Gemini\n- 能拿到稳定音频地址时，尽量保留原始来源信息\n- 如果只拿到平台试听链接但不可稳定访问，后续要进入 Step 0.5 下载/转码\n\n### 示例\n#### 示例 A：用户只给歌名\n用户：`帮我跑《特别的人》方大同`\n\nStep 0 应拿到：\n- title: 特别的人\n- artist: 方大同\n- audio_url: 某平台音频链接\n- lyrics: 完整歌词\n\n#### 示例 B：用户给平台链接\n用户：`把这个网易云链接做成 AI 版`\n\nStep 0 应做：\n- 解析链接\n- 抽取 song_id\n- 拉取歌词与音频信息\n\n---\n\n## Step 0.5：音频预处理\n\n### 目的\n把 Step 0 拿到的音频，变成 Step 1 最稳可用的输入格式。\n\n### 固定策略\n- 优先使用 **本地 mp3**\n- 如果拿到的是 flac / m4a / 其他格式，先转成 mp3\n- 如果拿到的是不稳定外链，先下载到本地再处理\n\n### 当前已验证的工程结论\n在官方 Gemini API 这条链路里，最稳的是：\n1. 下载到本地\n2. 转成 mp3\n3. 再转 base64 data URL\n4. 放进 `messages[].content[].image_url.url`\n\n### 示例\n```bash\nffmpeg -i source.flac -codec:a libmp3lame -q:a 2 -ar 44100 song_pgc.mp3\n```\n\n---\n\n## Step 1：Gemini 分析（固定方案）\n\n### 目的\n把\"音频里的风格信息\"和\"PGC 歌词里的文字真值\"融合成 Suno 可消费的 JSON_A。\n\n### 这一阶段解决什么问题\nStep 1 不只是转写，而是把素材变成两个关键产物：\n1. **prompt**：给 Suno 的风格蓝图\n2. **lyrics**：带 section label + producer notes 的结构化歌词\n\n### 职责边界\n- **音频负责**：\n  - 曲风\n  - BPM / Key / Rhythm\n  - 编曲元素\n  - 演唱方式\n  - 结构\n  - 情绪\n  - producer notes\n- **PGC 歌词负责**：\n  - 文字真值\n  - 中文歌词内容本体\n\n### 固定原则\n1. **必须传入 PGC 歌词**，不要只给音频\n2. **以 PGC 歌词为 ground truth**，不要让模型自由听写整首歌词\n3. **音频用于推断**：section、编曲提示、演唱方式、结构与风格特征\n4. 当前主链默认模型是 `gemini-2.5-pro`，实际运行模型可由 `GEMINI_MODEL` 覆盖\n5. `max_tokens` 固定为 `32000`\n6. 官方 Gemini API 下，音频输入最稳妥方式为：\n   - 本地音频转 **mp3**\n   - 再转成 **base64 data URL**\n   - 放入 `messages[].content[].image_url.url`\n\n### Step 1 固定 Prompt（推荐版）\n\n```text\nYou are a world-class music producer and hit-songwriter, acting as a \"style chameleon\". Your task is to perform a deep analysis of an input audio file.\n\nYou are given:\n1. The original PGC audio\n2. The original PGC lyrics\n\nImportant rule:\n- Use the provided PGC lyrics as the ground-truth text.\n- Use the audio to infer genre, arrangement, vocal delivery, song structure, emotional arc, and section-level musical cues.\n- Do NOT freely re-transcribe the entire song from audio unless resolving obvious discrepancies.\n\nYour output must be valid JSON in the following format:\n{\n  \"prompt\": \"...\",\n  \"lyrics\": \"...\"\n}\n\nRequirements for \"prompt\":\n1. Strict format:\nGenre: <Genre>; BPM: <BPM>; Key: <Key>; Rhythm: <Detailed Rhythm description>; Instruments: <Detailed Instruments list with timbre descriptions>; Vocals: <Detailed Vocal description including timbre, technique, gender, processing>; Mood: <Detailed Mood description>; Production: <Production description including mixing, space, effects>; Structure: <Song Structure description>; Language: <Language>; Highlight: <the most noteworthy element>\n2. Keep the entire prompt under 1000 characters.\n3. Highlight must be the final field.\n\nRequirements for \"lyrics\":\n1. Use the provided PGC lyrics as the text source.\n2. Add standard structure markers such as [intro], [verse], [chorus], [bridge], [outro].\n3. Immediately after each section marker, insert producer-note style musical guidance in parentheses.\n4. These notes must be keyword-only, not full sentences.\n5. Include musical details such as melody contour, melody rhythm, vocal performance, mix, instrumental interplay, and structural events.\n6. No pinyin.\n7. No timestamps.\n8. Keep the entire lyrics field under 5000 characters.\n\nReturn JSON only. No markdown fences. No explanation.\n```\n\n### Step 1 当前固定实现\n- endpoint: `https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL}:generateContent`\n- authentication: `?key=${GEMINI_API_KEY}`\n- default model: `gemini-2.5-pro`\n- max_tokens: `32000`\n\n### 已验证的工程结论\n1. 纯文本请求：成功\n2. 音频最小理解（语言/时长/曲风）：成功\n3. `prompt only`：成功\n4. `lyrics only`：成功\n5. `prompt + lyrics`：默认设置下不稳定；**加 `max_tokens: 32000` 后可成功**\n\n### 标准输出（JSON_A）\n```json\n{\n  \"song_id\": \"可选，推荐保留\",\n  \"prompt\": \"严格格式化后的风格提示词\",\n  \"lyrics\": \"带 [section] 和英文括号 producer notes 的结构化歌词\"\n}\n```\n\n### 示例\n#### 示例输出片段\n```json\n{\n  \"prompt\": \"Genre: Mandopop / Soul Ballad; BPM: 70; Key: E-flat Major; ...\",\n  \"lyrics\": \"[verse]\\n(Melody-Contour: ...; Vocal-Perf: ...; Mix: ...)\\n爱一个人或许要慷慨...\"\n}\n```\n\n### 回退策略\n如果官方 Gemini API 返回：\n- 429\n- 500\n- 502\n- 503\n\n则优先回退到分步方案：\n1. 第一次只拿 `prompt`\n2. 第二次只拿 `lyrics`\n3. 本地合并 JSON_A\n\n---\n\n## Step 2：Claude 改写（lyrics-rewriter）\n\n### 目的\n把 Step 1 的结构化歌词改写成\"新主题但保留风格与结构\"的版本，并同时生成专辑封面方案，产出 JSON_B。\n\n### 这一阶段解决什么问题\nStep 1 给的是\"原曲风格 + 原曲文本整理版\"；\nStep 2 的任务是：\n1. 变成\"**同风格、全新内容**\"的新歌词，用于后续 Suno 生成\n2. 根据新歌词情绪，生成可直接调用图片 API 的封面 prompt\n\n### 输入\n标准输入是 JSON_A：\n```json\n{\n  \"song_id\": \"...\",\n  \"prompt\": \"...\",\n  \"lyrics\": \"...\"\n}\n```\n\n### 输出\n标准输出是 JSON_B：\n```json\n{\n  \"song_id\": \"...\",\n  \"prompt\": \"...\",\n  \"title\": \"2-5字歌名\",\n  \"lyrics\": \"重写后的结构化歌词\",\n  \"cover\": {\n    \"template_id\": 1,\n    \"template_name\": \"模板名称\",\n    \"scene_description\": \"画面描绘内容\",\n    \"cover_prompt\": \"完整可用的封面生成 prompt\",\n    \"selection_reason\": \"选择理由\"\n  }\n}\n```\n\n### 固定规则摘要\n- `prompt` 原样保留\n- `song_id` 原样保留（若存在）\n- 段落标签原样保留\n- 英文括号提示词原样保留在原位置\n- 只重写中文歌词\n- 新增 `title`\n\n### 重点注意\n- 中文内容必须彻底改写，不能只是换同义词\n- 新主题必须契合原曲情绪和曲风\n- 副歌要更有传播性和张力\n- 要重点防止：\n  - 丢失英文括号提示词\n  - 重复 section 标签\n  - 结构顺序混乱\n\n### 示例\n#### 输入片段\n```text\n[verse]\n(Structure:Verse, Vocal-Perf:Intimate-breathy, ...)\n爱一个人或许要慷慨\n若只想要被爱\n```\n\n#### 输出片段\n```text\n[verse]\n(Structure:Verse, Vocal-Perf:Intimate-breathy, ...)\n窗外的灯一盏一盏暗下去\n那是谁的夜晚还没散去\n```\n\n---\n\n## Step 3：Suno 生成\n\n### 目的\n把 JSON_B 的 `prompt + lyrics` 直接喂给 Suno，拿到最终可试听样本。\n\n### 输入\n```json\n{\n  \"prompt\": \"...\",\n  \"title\": \"...\",\n  \"lyrics\": \"...\"\n}\n```\n\n### 调用方式\n```bash\n~/.openclaw/workspace/skills/suno/suno.sh custom \"{prompt字段内容}\" \"{lyrics字段内容}\"\n```\n\n### 输出\n- 默认 2 首歌曲\n- 每首包含：\n  - 任务状态\n  - 时长\n  - MP3 链接\n  - 封面链接\n  - song ID\n\n### 示例\n```text\n第1首: https://cdn1.suno.ai/xxx.mp3\n第2首: https://cdn1.suno.ai/yyy.mp3\n```\n\n### 注意事项\n- prompt 太长时，先压缩\n- lyrics 太长时，可能被 Suno 截断\n- 如果只是验证链路，优先先跑一版，不要一开始过度清洗\n\n---\n\n## Step 3.5：混音/母带（参考曲匹配模式）\n\n### 目的\n对 Suno 生成的原始音频做混音/母带处理，以原曲（PGC）频谱为参考方向，温和调整频谱平衡、动态和声场。\n\n### 脚本\n```bash\npython3 steps/step35_mix.py <input.mp3> <output.mp3> --ref <原曲.mp3> --json\n```\n\n### 信号链路\n```\n输入 → M/S分离 → Side链路 → Mid链路 → M/S重组 → Side增益\n→ 并行压缩 → 主链路EQ → 主压缩 → Limiter → 二次校验 → MP3编码(alimiter 0.95)\n```\n\n### M/S 链路（基准参数）\n\n| 通道 | 模块 | 参数 |\n|---|---|---|\n| Side | HighpassFilter | 120 Hz |\n| Side | HighShelfFilter | 10kHz, 0 dB（基准关闭） |\n| Side | 整体增益 side_gain_db | 0 dB（参考曲模式动态调） |\n| Mid | PeakFilter | 60 Hz, +0.5 dB, q=1.0 |\n| Mid | PeakFilter | 100 Hz, +0.5 dB, q=1.0 |\n| Mid | PeakFilter | 1000 Hz, +0.5 dB, q=0.5 |\n\n### 主链路（基准参数）\n\n| 序号 | 模块 | 参数 |\n|---|---|---|\n| 1 | Pre-gain | -1.5 dB（参考曲模式归零） |\n| 2 | HighpassFilter | 30 Hz |\n| 3 | 去闷 PeakFilter | 300 Hz, -1.0 dB, q=1.5 |\n| 4 | 低频增强 LowShelf | 80 Hz, +1.5 dB |\n| 5 | 收紧 PeakFilter | 120 Hz, -1.5 dB, q=1.5 |\n| 6 | 盒音切除（条件触发） | 800 Hz, -1.0 dB, q=1.0 |\n| 7 | 空气感 HighShelf | 16 kHz, +0.5 dB |\n| 8 | 主压缩 Compressor | -10 dB / 2.0:1 / atk 20ms / rel 100ms |\n| 9 | Limiter | -1.0 dB / rel 100ms |\n\n### 并行压缩（纽约压缩，M/S重组后、主链路前）\n\n| 参数 | 基准值 |\n|---|---|\n| threshold | -20 dB |\n| ratio | 8.0:1 |\n| attack | 5 ms |\n| release | 150 ms |\n| mix | 10% |\n\n### 参考曲匹配模式（`--ref` 原曲路径）\n\n分析原曲频谱作为**方向指引**（不追精确数字），温和调整：\n\n| 维度 | 死区 | 偏多时 | 偏少时 | 安全范围 |\n|---|---|---|---|---|\n| **低频** | ±5% | 去闷 max -2.0dB, sub 最低归零 | sub +1.0~+2.0, 去闷归零 | 去闷 0~-2.0, sub 0~+2.0 |\n| **高频** | ±2% | 空气感 min -0.5dB | 空气感 max +1.0dB | -0.5~+1.0 |\n| **LowMid(250-1k)** | ±3% | 600Hz cut max -0.5dB, Q=0.7 | 600Hz boost max +0.5dB, Q=0.7 | ±0.5 |\n| **Mid(1k-4k)** | ±3% | 2.5kHz cut max -0.5dB, Q=1.0 | 2.5kHz boost max +0.5dB, Q=1.0 | ±0.5 |\n| **Air(8k+)** | — | 额外降空气感 max -0.5dB | — | 叠加在高频调整上 |\n| **宽度** | ±2% | Side gain max -2.0dB | Side gain max +2.0dB | ±2.0 |\n| **动态** | ±3dB | 阈值 max -14dB, PC max 20% | 阈值 min -5dB, PC min 2% | 阈值 -5~-14, PC 2~20% |\n\n参考曲模式下 pre_gain 归零。\n\n### 二次校验（仅参考曲模式）\n\n处理完后重新分析六段频谱，按需补偿：\n\n| 检测项 | 触发条件 | 频点 | Q | 斜率 | 上限 |\n|---|---|---|---|---|---|\n| 低频补偿 | 输出 < 目标 8% | LowShelf 80Hz | — | Δ×0.06 | **+1.0dB** |\n| LowMid补偿 | 输出 < 目标 8% | Peak 600Hz | 0.7 | Δ×0.03 | **+0.5dB** |\n| Mid切削 | 输出 > 目标 5% | Peak 2.5kHz | 1.0 | Δ×0.03 | **-0.5dB** |\n| Air补偿/切削 | 输出偏差 >2% | HighShelf 10kHz | — | Δ×0.08 | **±0.5dB** |\n\n### 输出\nMP3 320kbps，ffmpeg `alimiter=limit=0.95` 防 intersample peak。\n\n### Pipeline 集成\nPipeline 自动从 Step 0 下载的原曲传入 `--ref`，无需手动指定。\n\n---\n\n## Step 4：封面生成（SeeDream API）\n\n### 目的\n用 Step 2 输出的 `cover_prompt`（顶层字段） 调用 SeeDream 图片生成 API，拿到专辑封面图片 URL。\n\n### 这一阶段解决什么问题\nStep 2 已经生成了完整的封面 prompt，Step 4 只负责把它丢给图片 API 拿回图。\n\n### 输入\n从 JSON_B 中取顶层 `cover_prompt` 字段（注意：不是 `cover.cover_prompt`，是顶层直接字段）。\n\n### API 调用方式\n\n```bash\ncurl -X POST 'https://api.acedata.cloud/seedream/images' \\\n-H 'accept: application/json' \\\n-H 'authorization: Bearer ${SEEDREAM_TOKEN}' \\\n-H 'content-type: application/json' \\\n-d '{\n  \"action\": \"generate\",\n  \"model\": \"doubao-seedream-4-5-251128\",\n  \"prompt\": \"<JSON_B.cover_prompt 的内容>\",\n  \"size\": \"2048x2048\",\n  \"watermark\": false\n}'\n```\n\n### Python 调用方式\n\n```python\nimport requests\n\ndef generate_cover(cover_prompt: str, token: str, model: str = \"doubao-seedream-4-5-251128\") -> str:\n    resp = requests.post(\n        \"https://api.acedata.cloud/seedream/images\",\n        headers={\n            \"accept\": \"application/json\",\n            \"authorization\": f\"Bearer {token}\",\n            \"content-type\": \"application/json\"\n        },\n        json={\n            \"action\": \"generate\",\n            \"model\": model,\n            \"prompt\": cover_prompt,\n            \"size\": \"2048x2048\",\n            \"watermark\": False\n        },\n        timeout=180\n    )\n    data = resp.json()\n    if data.get(\"success\"):\n        return data[\"data\"][0][\"image_url\"]\n    raise Exception(f\"SeeDream API error: {data}\")\n```\n\n### 固定参数\n- **API endpoint**: `https://api.acedata.cloud/seedream/images`\n- **默认模型**: `doubao-seedream-4-5-251128`\n- **备选模型**: `doubao-seedream-4-0-250828`\n- **图片尺寸**: `2048x2048`（正方形，专辑封面标准）\n- **水印**: `false`\n\n### ⚠️ 关键注意事项\n- 模型名用**短横线** `-` 而非点号 `.`（文档写的是点号，实际要用短横线）\n- API 调用耗时约 30-90 秒，需要足够的 timeout\n- Token 需在 [Acedata 平台](https://platform.acedata.cloud/documents/seedream-images) 单独开通 SeeDream 服务\n\n### 输出\n```json\n{\n  \"success\": true,\n  \"task_id\": \"...\",\n  \"data\": [\n    {\n      \"prompt\": \"...\",\n      \"size\": \"2048x2048\",\n      \"image_url\": \"https://platform.cdn.acedata.cloud/seedream/xxx.jpg\"\n    }\n  ]\n}\n```\n\n取 `data[0].image_url` 写回 JSON_B 的 `cover.image_url`。\n\n### 回退策略\n- 如果 4.5 模型失败，回退到 `doubao-seedream-4-0-250828`\n- 如果 API 返回 429（限频），等待 Retry-After 后重试\n- 如果 API 返回 400，检查 token 是否已开通 SeeDream 服务\n\n### 已验证的工程结论\n1. Token `82c5a4b2bf8c4a369aca02f9e12f4bd8` 已开通，4.5 和 4.0 均可用\n2. 中文 prompt 正常出图\n3. 三个模板的封面 prompt 均已验证出图效果\n4. 出图耗时约 30-60 秒\n\n---\n\n## 完整执行流程（主 agent 操作步骤）\n\n```\n1. 收到用户输入（歌名/歌手/链接，或直接给 PGC 歌词 + 音频）\n2. 如果素材不完整，先走 Step 0：音乐获取 skill → 拿到 audio_url + lyrics + metadata\n3. 进入 Step 0.5：下载音频，必要时转 mp3\n4. Gemini Step 1：分析音频 + 结合 PGC 歌词 → 得到 JSON_A\n5. Claude Step 2：按 lyrics-rewriter 改写 JSON_A → 得到 JSON_B（含 title + cover）\n6. Suno Step 3：生成歌曲 → 得到音频链接\n7. SeeDream Step 4：用 cover_prompt（顶层字段） 生成封面 → 得到封面图 URL\n8. 回复用户：\n   - 歌名：{title}\n   - 试听链接 1：{url1}\n   - 试听链接 2：{url2}\n   - 封面图：{cover_image_url}\n   - 开发模式下附中间结果\n```\n\n> Step 3 和 Step 4 可以并行执行（Suno 生成音乐 + SeeDream 生成封面互不依赖）。\n\n> 默认进入开发模式时，应回传每一步原始结果，尤其是 Gemini 原始 JSON、Claude 改写结果、Suno 原始返回。\n\n---\n\n## 输入格式要求\n\n用户支持两种输入方式：\n\n### 方式 A：直接给完整素材\n- **PGC 歌词**：原始完整歌词文本（或飞书文档链接）\n- **音频**：优先本地文件；若是外链，建议先下载并转 mp3\n\n### 方式 B：只给歌曲信息\n- 歌名\n- 歌手\n- 歌曲链接 / 平台链接（可选）\n\n此时先走 Step 0，用音乐获取 skill 补齐：\n- 音频链接\n- 歌词\n- 元数据\n\n可选：\n- 指定新歌主题方向（否则 Claude 自主选择）\n- 指定生成数量（默认 2 首）\n- 指定只调某一步（如\"只调 Step 2\"）\n\n---\n\n## 代码仓库与版本管理\n\n**本地路径**：`/home/debian/.openclaw/workspace/skills/music-pipeline/`\n**GitHub**：`git@github.com:hycccc/music-pipeline3000.git`\n**当前开发分支**：`v2.0-dev`\n**稳定版快照**：`tag/v1.0-stable`（acedata Suno + v1 step1 prompt）\n\n### 改代码后必须做：\n```bash\ncd /home/debian/.openclaw/workspace/skills/music-pipeline\ngit add .\ngit commit -m \"描述改了什么\"\ngit push origin HEAD:v2.0-dev\n```\n\n### 分支说明：\n| 分支/Tag | 内容 |\n|----------|------|\n| `v2.0-dev` | 当前开发版，自建 Suno API（默认） |\n| `main` | GitHub 原有历史 |\n| `tag/v1.0-stable` | v1 完整快照，acedata Suno |\n\n**⚠️ 不要用 rsync 同步，不要在 workspace 的大 git 里 commit pipeline 代码，只用这个独立 repo。**\n\n---\n\n## v1 vs v2 版本对比\n\n### 三条链路一览\n\n| 链路 | Step 1 Prompt | Step 3 Suno 接口 | 触发方式 |\n|------|--------------|-----------------|---------|\n| **v1** | 通用 base prompt（style chameleon） | acedata `/suno/audios`，chirp-v5 | 切换 step3 文件 |\n| **v2 通用** | 同 v1 prompt（EXACTLY 2 Chorus + Musical Arc） | 自建 `localhost:12138`，chirp-crow | `python3 pipeline.py`（默认） |\n| **v2 下沉（xiachen）** | 华语/民族专属 prompt，五声调式/hook-first | 同 v2 通用 | `python3 pipeline.py --profile xiachen` |\n\n### v1 和 v2 通用的 Step 1 prompt 差异\n\nv2 通用在 v1 基础上加了两条：\n- `EXACTLY 2 Chorus sections`（v1 是 at least 2）\n- Musical Arc 校验规则（Bridge 后必须接 Chorus / Verse 2 删了 Bridge 也要删等）\n\n### v2 下沉（xiachen）Step 1 prompt 额外内容\n\n- Genre 针对华语少数民族风格有专属 anchor（彝族/蒙古族/新疆等）\n- Instruments 必须识别民族乐器名称（erhu/马头琴/guzheng 等）\n- Melody priority: Hook-first design（副歌钩子必须一遍就能哼）\n- 文件位置：`scripts/profiles/xiachen.yaml`（`step1.prompt_template` 字段）\n\n---\n\n### 迭代时改哪个文件\n\n| 改什么 | 改哪个文件 |\n|--------|----------|\n| v2 通用 Step 1 prompt | `scripts/steps/step1_gemini.py` 里的 `STEP1_PROMPT_TEMPLATE` |\n| v2 下沉 Step 1 prompt | `scripts/profiles/xiachen.yaml` 里的 `step1.prompt_template` |\n| Step 2 歌词改写规则（通用） | `workspace/skills/lyrics-rewriter/SKILL.md` |\n| Step 2 歌词改写规则（下沉） | `scripts/profiles/xiachen/lyrics-rewriter-SKILL.md` |\n| Step 3 Suno 接口/参数 | `scripts/steps/step3_suno.py` |\n| Step 3.5 混音参数 | `scripts/steps/step35_mix.py` |\n| Step 4 封面生成 | `scripts/steps/step4_seedream.py` |\n| Step 5 飞书导出 | `scripts/steps/step5_feishu.py` |\n| Pipeline 编排逻辑 | `scripts/pipeline.py` |\n\n### 切换 v1 / v2 链路\n\n```bash\ncd /home/debian/.openclaw/workspace/skills/music-pipeline\n\n# 切换到 v1（acedata）\ncp scripts/steps/step3_suno_v1.py scripts/steps/step3_suno.py\n\n# 切回 v2（自建 API）\ngit checkout HEAD -- scripts/steps/step3_suno.py\n```\n\n---\n\n## 开发模式约定\n\n当用户明确说进入开发模式时：\n1. 每一步都要先说明当前在调哪一步\n2. 每一步都要回传原始结果\n3. 出错时优先定位\"是哪一层错了\"：\n   - 素材获取\n   - 转码\n   - Gemini 输入\n   - Gemini 输出格式\n   - Claude 改写规则\n   - Suno 接收\n4. 已验证过的工程结论要及时写回 skill\n\n---\n\n## 错误处理\n\n| 问题 | 处理方式 |\n|------|----------|\n| 用户只给歌名，没有歌词音频 | 先走 Step 0，用音乐获取 skill 补齐 |\n| 外链 flac 不稳定 | 先下载到本地并转 mp3 |\n| 官方 Gemini API 返回 429/5xx | 重试；必要时切换到 Step 1 拆分调用 |\n| Gemini 输出不是合法 JSON | 重新要求 JSON-only，或手动提取 `choices[0].message.content` |\n| `prompt + lyrics` 联合输出失败 | 保留 `max_tokens: 32000`；仍失败就拆成两次请求 |\n| Step 2 丢括号提示词 | 收紧 prompt，明确\"英文括号内容一字不动保留\" |\n| Step 2 重复 section 标签 | 增加去重修复步骤 |\n| Suno 生成失败 | 检查 prompt 长度与歌词长度，必要时缩短 |\n| 歌词太长导致 Suno 截断 | 精简歌词，保留核心段落 |\n| SeeDream 返回 400 model 错误 | 检查模型名是否用短横线（`doubao-seedream-4-5-251128`），不是点号 |\n| SeeDream 返回 400 但模型名正确 | token 可能未开通 SeeDream 服务，需到平台 Acquire |\n| SeeDream 返回 429 | 等待后重试，或降级到 4.0 模型 |\n| 封面出图有文字/人物 | 在 cover_prompt 中加强否定提示词，重新生成 |\n\n---\n\n## 迭代记录\n\n| 版本 | 日期 | 内容 |\n|------|------|------|\n| v1.0 | 2026-03-16 | 初始化，三段链路：Gemini反推 → Claude改写 → Suno生成 |\n| v1.1 | 2026-03-16 | 固定 Step 1 工程方案：PGC歌词为真值 + 音频用于风格分析；默认模型 gemini-2.5-pro；官方 Gemini API 需 max_tokens=32000；支持必要时拆分 prompt/lyrics 两次请求 |\n| v1.2 | 2026-03-16 | 补全 Step 0 / Step 0.5 / Step 1 / Step 2 / Step 3 的目的、输入输出、示例、开发模式约定与错误处理细节 |\n| v2.0 | 2026-03-17 | 新增 Step 4 封面生成（SeeDream API）；Step 2 输出新增 cover 字段（含封面 prompt）；lyrics-rewriter skill 同步升级为 v3.0；完整链路从五步扩展为六步 |\n\n\n## Step 1 分析数据 (step1_json_a.json)\n\n```json\n{\n  \"prompt\": \"Genre: Mandopop / Ballad; BPM: 140; Key: E Major; Rhythm: Slow, steady 4/4 ballad timefeel with a simple, clean acoustic kit backbeat and driving 8th-note hi-hats.; Instruments: Arpeggiated grand piano, prominent mournful cello, lush string section pads, clean electric guitar arpeggios, subtle acoustic guitar strumming; Vocals: Female; Clear, emotive, and dynamic, shifting from a breathy, conversational tone in verses to a powerful, soaring belt in the choruses, with light reverb and delay.; Mood: Staring out a rain-streaked window, replaying a final, quiet argument in your mind.; Production: Polished, wide-stereo studio mix. Vocals are front and center. Dynamic range builds from intimate verses to a full, dramatic chorus. Hall reverb creates an epic sense of space.; Structure: Intro - Verse - Chorus - Verse - Chorus - Bridge - Outro; Language: Mandarin; Highlight: The soaring, emotionally charged chorus melody.\",\n  \"lyrics\": \"[intro]\\n(Chords: E - A - C#m - B)\\n(Inst: Melancholy solo cello melody; Sparse, arpeggiated grand piano providing harmonic support.)\\n(Mix: Narrow and intimate, setting a reflective tone.)\\n\\n[verse]\\n(Chords: E - A - E - A - C#m - G#m - A - B)\\n(Vocal-Perf: Soft, breathy, conversational delivery; Mezzo-piano dynamics.)\\n(Melody-Contour: Mostly stepwise motion with small melodic leaps.)\\n(Inst: Piano plays block chords under the vocal; Bass enters with simple root notes; Acoustic kit with a light, simple backbeat.)\\n(Transition: Light drum fill with a crash cymbal; Strings swell into the chorus.)\\n我问为什么\\n那女孩 传简讯给我\\n而你为什么\\n不解释 低着头沉默\\n我该相信你很爱我\\n不愿意敷衍我\\n还是明白\\n你已不想挽回什么\\n\\n[chorus]\\n(Chords: A - B - G#m - C#m - F#m - B - E)\\n(Vocal-Perf: Soaring, powerful chest voice; Mezzo-forte; Sustained, anthemic notes.)\\n(Melody-Dev: Memorable, emotionally charged hook, embodying the song's core feeling.)\\n(Inst: Full band enters; Lush string section provides a wide pad; Drums are more driving; Clean electric guitar plays counter-melody arpeggios.)\\n(Mix: Expands to a wide stereo field; Vocals sit powerfully on top.)\\n我怀念的 是无话不说\\n我怀念的 是一起做梦\\n我怀念的 是争吵以后\\n还是想要 爱你的冲动\\n我记得那年生日\\n也记得那一首歌\\n记得那片星空\\n最紧的右手\\n最暖的胸口\\n谁记得\\n谁忘了\\n\\n[verse]\\n(Chords: E - A - E - A - C#m - G#m - A - B)\\n(Vocal-Perf: Returns to a softer dynamic, but with more emotional weight and intensity than the first verse.)\\n(Inst: The full band continues but pulls back in volume; Bass and drums maintain a steady, quiet groove.)\\n(Mix: Narrows slightly to create dynamic contrast with the chorus.)\\n(Transition: Strings build with a crescendo leading into the next chorus.)\\n想问为什么\\n我不再 是你的快乐\\n可是为什么\\n却苦笑 说我都懂了\\n自尊常常将人拖着\\n把爱都走曲折\\n假装了解是怕\\n真相太赤裸裸\\n狼狈比失去难受\\n\\n[chorus]\\n(Chords: A - B - G#m - C#m - F#m - B - E)\\n(Vocal-Perf: Maximum power and emotion; Forte; Expressive ad-libs and slight melodic variations towards the end.)\\n(Inst: Distorted electric guitar enters with long, sustained notes, adding grit and intensity; Strings are at their most prominent; Drum hits are heavier.)\\n(Mix: Very wide and full; All elements pushing for a climactic feel.)\\n我怀念的 是无话不说\\n我怀念的 是一起做夢\\n我怀念的 是争吵以后\\n还是想要 爱你的冲动\\n我记得那年生日\\n也记得那一首歌\\n记得那片星空\\n最紧的右手\\n最暖的胸口\\n谁忘了\\n\\n[bridge]\\n(Chords: F#m - G#m - A - B - F#m - G#m - C#m - B)\\n(Vocal-Perf: Dynamic drops to an intimate, almost pained whisper-belt; High emotional vulnerability.)\\n(Inst: Band pulls back significantly; Piano, strings, and clean electric guitar arpeggios carry the harmony; Drums play soft cymbal swells.)\\n(Structure: Provides a moment of reflection before the final emotional peak.)\\n我怀念的 是无言感动\\n我怀念的 是绝对炽热\\n我怀念的 是你很激动\\n求我原谅 抱得我都痛\\n我记得你在背后\\n也记得我颤抖着\\n记得感觉汹涌\\n最美的烟火\\n最长的相拥\\n\\n[outro]\\n(Chords: A - B - G#m - C#m - F#m - B - E)\\n(Vocal-Perf: A mix of powerful, high-note ad-libs and quiet, spoken-like phrases; A final, desperate emotional release that fades into resignation.)\\n(Inst: Full band plays with final, climactic energy, then quickly fades out, leaving a solitary piano playing the final chords with a long, resonant decay.)\\n(Mix: Maximum width and impact, then a sudden collapse to a single point of sound.)\\n我还有想要 爱你的冲动\\n我记得那年生日\\n也记得那一首歌\\n记得那片星空\\n最紧的右手\\n最暖的胸口\\n我放手\\n我让座\\n假洒脱\\n谁懂我多么不舍得\\n太爱了\\n所以我\\n没有哭\\n没有说\",\n  \"song_id\": \"01_我怀念的_20260513\",\n  \"_meta\": {\n    \"provider\": \"google\",\n    \"model\": \"gemini-2.5-pro\",\n    \"usage\": {\n      \"prompt_tokens\": 12291,\n      \"completion_tokens\": 1464,\n      \"total_tokens\": 16159\n    },\n    \"generated_at\": \"2026-05-13T01:40:57.466739+08:00\"\n  },\n  \"_prompt_compressed\": {\n    \"from\": 1038,\n    \"to\": 925\n  }\n}\n```\n\n## ⚠️ 关键约束（最高优先级）\n\n1. **原创度必须 ≥ 85%**：每一句歌词都必须是全新创作，严禁复用、微调或改写原词句。\n2. **主题必须彻底重构**：不能只做同义替换（如\"星座→缘分\"、\"酒吧→KTV\"），必须选择完全不同的叙事空间。\n3. **叙事事件必须全新**：原曲中的每个具体场景/事件，新歌词中禁止出现对应事件，即使换了细节也不行。\n4. **论证逻辑必须不同**：如果原曲是\"A信X → B嘲笑 → 举例 → 反转\"，新歌词必须用完全不同的情感推进方式。\n5. **角色关系必须重新设计**：原曲的人物关系模板禁止以任何变体出现。\n6. **结构标注（括号内容）可以保留**，但歌词文本行必须全新。\n7. **自检**：写完后并排对比——如果仅凭段落结构和叙事节奏就能看出是同一首歌的改写，必须重做。\n\n请严格按照 SKILL.md 的规则输出 step2_json_b.json 的完整 JSON。只输出 JSON，不要其他内容。用 json code block 包裹输出。\n⚠️ JSON 格式要求：lyrics 等字段是 JSON 字符串值，其中的双引号必须转义为 \"，或改用 「」 代替引号，禁止出现未转义的裸双引号。",
  "agent_meta": {
    "provider": "anthropic",
    "model": "claude-sonnet-4-6",
    "usage": {
      "input": 33058,
      "output": 4128,
      "cacheRead": 0,
      "cacheWrite": 0,
      "total": 37186
    },
    "duration_ms": 65350
  }
}