@starreel/mcp 0.1.133 → 0.1.135

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -1103,13 +1103,15 @@ to close") tells the vendor to fit that entire sequence into each 3-second shot.
1103
1103
  - **Audio**: `generate_tts` (required before final cut), `clone_voice`,
1104
1104
  `design_voice` → `get_voice_design` → `save_designed_voice` (no sample needed),
1105
1105
  `speak_with_voice`, `set_character_voice`, `list_voices`, `delete_voice`,
1106
- `generate_bgm` (optional `prompt` steers the music; read `get_bgm_prompt_guide` first; optional `director` hard-locks mood / instruments / bpm / tonality for the whole episode or per act — call the free read-only `get_bgm_plan` first to see the acts),
1106
+ `generate_bgm` (optional `prompt` steers the music; read `get_bgm_prompt_guide` first; optional `director` hard-locks mood / instruments / bpm / tonality for the whole episode or per act — call the free read-only `get_bgm_plan` first to see the acts; to let the client pick between full-length versions, `quote_bgm_candidates` → `generate_bgm` with `candidates` 2–3 + `quote_id` → `select_bgm_candidate`, each candidate billed per song),
1107
1107
  `get_bgm_prompt_guide`, `replace_shot_dialogue`, `revoice_narration`
1108
1108
  - **Mix** (free; settings only — `compose_episode` again to hear them, `rerender_episode`
1109
1109
  reuses the old timeline): whole drama via `update_project_settings`
1110
1110
  (`bgm_volume_preset` / `bgm_volume_db` / `lufs_target`); one episode via
1111
- `set_episode_audio`; one music section's level and fades via `list_bgm_cues` →
1112
- `update_bgm_cue`. Don't download the cut to re-mix it locally.
1111
+ `set_episode_audio` (music level, plus `clip_audio_gain_db` for the clips' own sound and
1112
+ `voice_gain_db` for the TTS dialogue + narration track); one music section's level, fades,
1113
+ where in the track it starts (`track_offset_ms`) and how late after its first shot it comes in
1114
+ (`start_shift_ms`) via `list_bgm_cues` → `update_bgm_cue`. Don't download the cut to re-mix it locally.
1113
1115
  - **Finish**: `compose_episode`, `get_final_cut`, `get_export`,
1114
1116
  `generate_episode_poster`, `generate_cover`
1115
1117
  - **What did this drama cost?** `get_drama_bill` — per kind `charged` / `refunded`
@@ -240,7 +240,7 @@ export const PIPELINE = [
240
240
  },
241
241
  {
242
242
  step: '9 音频',
243
- tools: ['assign_voices', 'generate_tts(仅 use_clip_audio=false)', 'clone_voice', 'generate_bgm', 'get_bgm_plan', 'get_bgm_status', 'generate_sfx', 'lipsync_shot', 'lipsync_episode', 'get_lipsync_status', 'set_shot_name_card'],
243
+ tools: ['assign_voices', 'generate_tts(仅 use_clip_audio=false)', 'clone_voice', 'generate_bgm', 'get_bgm_plan', 'quote_bgm_candidates', 'select_bgm_candidate', 'get_bgm_status', 'generate_sfx', 'lipsync_shot', 'lipsync_episode', 'get_lipsync_status', 'set_shot_name_card'],
244
244
  billing: '混合',
245
245
  note: '默认视频原声跳过 TTS 三步(pipeline-status 里显示 not_required,不是没做完)。',
246
246
  },
@@ -290,7 +290,7 @@ export const OPTIONAL_BOOSTS = [
290
290
  { what: '口型同步', tool: 'lipsync_episode', when: 'TTS 配音项目需要对口型时' },
291
291
  { what: '海报 / 封面', tool: 'generate_episode_poster', when: '成片后;`generate_drama_poster` / `generate_cover` 同族' },
292
292
  { what: '音效 / 特效 / 转场(本地库匹配)', tool: 'generate_sfx', when: '免费;`generate_effects` / `generate_transitions` 同族' },
293
- { what: '配乐', tool: 'generate_bgm', when: '按整集情绪弧线生成;终拼自动接管。客户想指定音乐方向就带 prompt(整集一条),写法先读 `get_bgm_prompt_guide`(免费);不带 prompt 就是全自动。客户给了明确参数(情绪/主奏配器/BPM/大小调)用 director 锁定(硬约束,可按幕;按幕前先 get_bgm_plan 免费看分幕)' },
293
+ { what: '配乐', tool: 'generate_bgm', when: '按整集情绪弧线生成;终拼自动接管。客户想指定音乐方向就带 prompt(整集一条),写法先读 `get_bgm_prompt_guide`(免费);不带 prompt 就是全自动。客户给了明确参数(情绪/主奏配器/BPM/大小调)用 director 锁定(硬约束,可按幕;按幕前先 get_bgm_plan 免费看分幕)。客户想先听几版再定:candidates=2~3 出完整候选(每首单独计费,先 quote_bgm_candidates 报价),select_bgm_candidate 挑' },
294
294
  { what: '字幕翻译', tool: 'translate_subtitles', when: '出海;双语烧录在项目设定里开' },
295
295
  ];
296
296
  export const BILLING = {
@@ -1490,11 +1490,17 @@ export function registerProduceTools(server, client) {
1490
1490
  '整集锁写在 director 顶层;只锁某一幕写进 director.acts[{act,…}](同名字段覆盖整集的)。' +
1491
1491
  '幕是按情绪弧线自动分的:按幕锁之前先调 get_bgm_plan(免费,只读)看本集分几幕、每幕覆盖哪些镜。' +
1492
1492
  '参数拼错(bpm 越界、调性不是 major/minor/modal、幕号不存在)会 400;配器里写人声/环境声会被丢掉并在 director_warnings 点名。' +
1493
- '客户说「要慢一点、小调、用古筝」这类**明确参数**用 director;说「整体更压抑」这类**方向**用 prompt;两者可同时用。', {
1493
+ '客户说「要慢一点、小调、用古筝」这类**明确参数**用 director;说「整体更压抑」这类**方向**用 prompt;两者可同时用。\n' +
1494
+ '★candidates 可选=候选整版对比:每幕出 2~3 首**完整**候选让客户挑(挑中的直接用,听到什么成片里就是什么)。' +
1495
+ '每首单独按首计费 ⇒ 费用是单首的 N 倍,**必须先 quote_bgm_candidates 报价、把点数告诉客户、客户同意后**带 quote_id 调本工具。' +
1496
+ '不传或传 1=原行为(每幕一首,无需报价)。生成后 get_bgm_status 的 candidate_groups 给每首的试听地址,select_bgm_candidate 挑选(免费),不挑默认第 1 首。', {
1494
1497
  episode_id: z.number().int().positive(),
1495
1498
  prompt: z.string().optional().describe('配乐方向,整集一条,≤800 字。只写音乐维度,别写剧情。传空字符串=清除已存的提示词。'
1496
1499
  + '不传=沿用该集上次填的(没填过就是全自动)。'),
1497
1500
  prompt_mode: z.enum(['guide', 'override']).optional().describe('guide(默认)=提示词与平台护栏一起生效;override=直通跳过审美护栏(技术底线仍在)。'),
1501
+ candidates: z.number().int().min(1).max(3).optional()
1502
+ .describe('每幕出几首完整候选(1~3,默认 1)。≥2 时每首单独计费,必须带 quote_bgm_candidates 拿到的 quote_id'),
1503
+ quote_id: z.string().optional().describe('candidates≥2 时必填:quote_bgm_candidates 返回的报价凭证(一次性,15 分钟有效)'),
1498
1504
  director: z.object({
1499
1505
  ...BGM_LOCK_FIELDS,
1500
1506
  acts: z.array(z.object({
@@ -1503,32 +1509,57 @@ export function registerProduceTools(server, client) {
1503
1509
  }).strict()).optional().describe('按幕锁定;同名字段覆盖整集的,没写的继承整集'),
1504
1510
  // strict:拼错的字段(如 tempo)要报校验错误;zod 默认会静默剥掉它,转发出去的就是空锁 ⇒ 一次不受控的扣费生成
1505
1511
  }).strict().optional().describe('结构化锁定(硬约束)。顶层字段整集生效;不传=全由配乐总监判断'),
1506
- }, async ({ episode_id, prompt, prompt_mode, director }) => jsonResult(await client.producePost(`/episodes/${episode_id}/bgm`, {
1512
+ }, async ({ episode_id, prompt, prompt_mode, director, candidates, quote_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/bgm`, {
1507
1513
  ...(prompt === undefined ? {} : { prompt }),
1508
1514
  ...(prompt_mode ? { prompt_mode } : {}),
1509
1515
  ...(director ? { director } : {}),
1516
+ ...(candidates === undefined ? {} : { candidates }),
1517
+ ...(quote_id ? { quote_id } : {}),
1510
1518
  })));
1519
+ server.tool('quote_bgm_candidates', '给「配乐候选整版对比」报价(免费):每幕出 candidates 首完整候选的总点数 = 有配乐的幕数 × 候选数 × 单首价。' +
1520
+ '返回 quote_id(15 分钟有效、一次性)。⚠️ 把 estimated_points 原样告诉用户,用户明确同意后才拿 quote_id 调 generate_bgm(candidates 必须与这里一致),不要擅自确认。' +
1521
+ '另有少量配乐总监 LLM 文本费(每幕一次),不在报价内。', {
1522
+ episode_id: z.number().int().positive(),
1523
+ candidates: z.number().int().min(2).max(3).describe('每幕几首候选(2~3)'),
1524
+ }, async ({ episode_id, candidates }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/bgm/quote?candidates=${candidates}`)));
1525
+ server.tool('select_bgm_candidate', '在候选整版对比里给某一幕挑一首(免费)。cue_id 与 track_id 取自 get_bgm_status 的 candidate_groups;' +
1526
+ '同一幕的所有配乐段一起换。改完 compose_episode 重新合成才进成片。', {
1527
+ episode_id: z.number().int().positive(),
1528
+ cue_id: z.number().int().positive().describe('candidate_groups[].cue_ids 里任一个'),
1529
+ track_id: z.number().int().positive().describe('candidate_groups[].candidates[].track_id'),
1530
+ }, async ({ episode_id, cue_id, track_id }) => jsonResult(await client.producePost(`/episodes/${episode_id}/bgm-cues/${cue_id}/select`, { track_id })));
1511
1531
  server.tool('get_bgm_plan', '查本集配乐会分成几幕(免费、只读、不生成):每幕的幕号 act、覆盖镜号 sb_from~sb_to、时长、平均情绪强度。' +
1512
1532
  '要用 generate_bgm 的 director.acts 按幕锁定参数时,先调这个拿幕号。' +
1513
1533
  '★这是独立的只读工具:看分幕**不要**去调 generate_bgm——那个一调就是扣费生成。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/bgm-plan`)));
1514
- server.tool('set_episode_audio', '设置**本集**配乐音量(覆盖整剧设定,免费)。整剧统一的用 update_project_settings。' +
1534
+ server.tool('set_episode_audio', '设置**本集**成片混音(免费):配乐音量(覆盖整剧设定)、片段原声增益、配音轨增益。整剧统一的配乐音量用 update_project_settings。' +
1515
1535
  '★只改设置:成片要 compose_episode 重新合成才变(rerender_episode 用旧时间线,不带上)。' +
1516
- '★想调某一段配乐的音量或淡入淡出,用 list_bgm_cues + update_bgm_cue。', {
1536
+ '★想调某一段配乐的音量、淡入淡出或起点,用 list_bgm_cues + update_bgm_cue。' +
1537
+ '★原声=视频片段自带的声音(原声剧里就是角色台词);配音轨=TTS 生成的对白与旁白(两者在同一轨里预混,只能整轨调,分不开)。' +
1538
+ '★本集若走逐镜合成片终拼(每镜都有合成片),配音已烤进每镜、与原声是同一条音频,这两个增益不生效——响应里会有 warning,照实转告客户。', {
1517
1539
  episode_id: z.number().int().positive(),
1518
1540
  bgm_volume_preset: z.enum(['off', 'low', 'auto', 'high', 'inherit']).optional()
1519
1541
  .describe('本集配乐音量档:off静音/low轻(-28dB)/auto自适应/high强(-16dB)/inherit跟随整剧'),
1520
1542
  bgm_volume_db: z.number().min(-60).max(0).nullable().optional()
1521
1543
  .describe('本集自定义配乐音量(dB,-60~0),覆盖档位并关自适应;null=清除、跟随整剧'),
1544
+ clip_audio_gain_db: z.number().min(-30).max(6).nullable().optional()
1545
+ .describe('片段原声比系统默认响/轻多少(dB,-30~+6;0=默认);原声剧台词听不清→调高,环境噪音太吵→调低;null=清回默认'),
1546
+ voice_gain_db: z.number().min(-24).max(6).nullable().optional()
1547
+ .describe('配音轨(TTS 对白与旁白)比系统默认响/轻多少(dB,-24~+6;0=默认);null=清回默认'),
1522
1548
  }, async ({ episode_id, ...rest }) => jsonResult(await client.producePut(`/episodes/${episode_id}/audio`, rest)));
1523
- server.tool('list_bgm_cues', '列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms。' +
1549
+ server.tool('list_bgm_cues', '列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms、track_offset_ms / start_shift_ms(null=系统决定)。' +
1524
1550
  'episode_id 为 null 的是**整剧共用段**,改它会影响所有集。免费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/bgm-cues`)));
1525
- server.tool('update_bgm_cue', '调某一段配乐的音量与淡入淡出(免费)。只改这三项;换曲目或改覆盖范围用 generate_bgm 重新生成。' +
1526
- '★例:配乐进得太突兀→加大 fade_in_ms;某段压对白→降 gain_db。改完 compose_episode 重新合成才生效。', {
1551
+ server.tool('update_bgm_cue', '调某一段配乐的音量、淡入淡出与起点(免费)。换曲目或改覆盖镜号范围用 generate_bgm 重新生成。' +
1552
+ '★例:配乐进得太突兀→加大 fade_in_ms;某段压对白→降 gain_db;想从曲子的高潮部分开始→设 track_offset_ms;' +
1553
+ '想等这段第一镜开口之后再进→设 start_shift_ms。改完 compose_episode 重新合成才生效。', {
1527
1554
  episode_id: z.number().int().positive(),
1528
1555
  cue_id: z.number().int().positive().describe('来自 list_bgm_cues'),
1529
1556
  gain_db: z.number().min(-60).max(0).nullable().optional().describe('这段配乐的音量(dB,-60~0),设了就覆盖音量档;null=恢复跟随本集/整剧音量档'),
1530
1557
  fade_in_ms: z.number().int().min(0).max(15000).optional().describe('淡入毫秒(整数,0~15000)'),
1531
1558
  fade_out_ms: z.number().int().min(0).max(15000).optional().describe('淡出毫秒(整数,0~15000)'),
1559
+ track_offset_ms: z.number().int().min(0).max(600000).nullable().optional()
1560
+ .describe('从配乐文件第几毫秒开始播(0~600000),覆盖系统自动挑的切入点;超过曲长会钳到曲尾前 1 秒;null=交回系统自动挑'),
1561
+ start_shift_ms: z.number().int().min(0).max(60000).nullable().optional()
1562
+ .describe('这段配乐在它第一镜开始后再晚多少毫秒进(0~60000);默认卡在第一镜起点。段尾不变,至少留 1 秒;null=不平移'),
1532
1563
  }, async ({ episode_id, cue_id, ...rest }) => jsonResult(await client.producePut(`/episodes/${episode_id}/bgm-cues/${cue_id}`, rest)));
1533
1564
  server.tool('get_bgm_prompt_guide', '取「AI 配乐提示词」的书写规范:两个档位怎么选、该写哪些维度(附可照抄的示例)、' +
1534
1565
  '哪些是写了也不会生效的硬限制(纯器乐/时长/不做音效/不复刻具体曲目)、常见写坏的方式。' +
package/openapi.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "StarReel Production API",
5
- "version": "0.1.133",
6
- "description": "Turn a script into a finished, downloadable short-drama episode over REST.\n\nPipeline: script → AI rewrite → cast/scenes/props extraction → storyboards → portraits & sheets + scene plates → keyframes → video shots → TTS → final cut (.mp4).\n\n**Billing is prepaid and agent-safe**: big-ticket stages are quote-then-generate (`quote_*` returns a `quote_id`). For video, quote == actual charge. For images the quote is a *range* — models billed per reference image are priced before those are assembled, so `estimated_points` is the upper bound (size the balance by it and a run never dies half-way) and `typical_points` is the usual cost; fixed-price models return the two equal. Insufficient balance returns 402 — nothing half-runs and the account never goes negative.\n\nAuth: exchange your API key at `POST /v1/agent/token` for a 15-minute bearer token.\n\nGenerated from the @starreel/mcp v0.1.133 tool surface (operationIds match MCP tool names 1:1)."
5
+ "version": "0.1.135",
6
+ "description": "Turn a script into a finished, downloadable short-drama episode over REST.\n\nPipeline: script → AI rewrite → cast/scenes/props extraction → storyboards → portraits & sheets + scene plates → keyframes → video shots → TTS → final cut (.mp4).\n\n**Billing is prepaid and agent-safe**: big-ticket stages are quote-then-generate (`quote_*` returns a `quote_id`). For video, quote == actual charge. For images the quote is a *range* — models billed per reference image are priced before those are assembled, so `estimated_points` is the upper bound (size the balance by it and a run never dies half-way) and `typical_points` is the usual cost; fixed-price models return the two equal. Insufficient balance returns 402 — nothing half-runs and the account never goes negative.\n\nAuth: exchange your API key at `POST /v1/agent/token` for a 15-minute bearer token.\n\nGenerated from the @starreel/mcp v0.1.135 tool surface (operationIds match MCP tool names 1:1)."
7
7
  },
8
8
  "servers": [
9
9
  {
@@ -3475,8 +3475,8 @@
3475
3475
  "/episodes/{episode_id}/audio": {
3476
3476
  "put": {
3477
3477
  "operationId": "set_episode_audio",
3478
- "summary": "设置**本集**配乐音量(覆盖整剧设定,免费)",
3479
- "description": "设置**本集**配乐音量(覆盖整剧设定,免费)。整剧统一的用 update_project_settings。★只改设置:成片要 compose_episode 重新合成才变(rerender_episode 用旧时间线,不带上)。★想调某一段配乐的音量或淡入淡出,用 list_bgm_cues + update_bgm_cue。",
3478
+ "summary": "设置**本集**成片混音(免费):配乐音量(覆盖整剧设定)、片段原声增益、配音轨增益",
3479
+ "description": "设置**本集**成片混音(免费):配乐音量(覆盖整剧设定)、片段原声增益、配音轨增益。整剧统一的配乐音量用 update_project_settings。★只改设置:成片要 compose_episode 重新合成才变(rerender_episode 用旧时间线,不带上)。★想调某一段配乐的音量、淡入淡出或起点,用 list_bgm_cues + update_bgm_cue。★原声=视频片段自带的声音(原声剧里就是角色台词);配音轨=TTS 生成的对白与旁白(两者在同一轨里预混,只能整轨调,分不开)。★本集若走逐镜合成片终拼(每镜都有合成片),配音已烤进每镜、与原声是同一条音频,这两个增益不生效——响应里会有 warning,照实转告客户。",
3480
3480
  "tags": [
3481
3481
  "episodes"
3482
3482
  ],
@@ -3519,6 +3519,30 @@
3519
3519
  "type": "null"
3520
3520
  }
3521
3521
  ]
3522
+ },
3523
+ "clip_audio_gain_db": {
3524
+ "anyOf": [
3525
+ {
3526
+ "type": "number",
3527
+ "minimum": -30,
3528
+ "maximum": 6
3529
+ },
3530
+ {
3531
+ "type": "null"
3532
+ }
3533
+ ]
3534
+ },
3535
+ "voice_gain_db": {
3536
+ "anyOf": [
3537
+ {
3538
+ "type": "number",
3539
+ "minimum": -24,
3540
+ "maximum": 6
3541
+ },
3542
+ {
3543
+ "type": "null"
3544
+ }
3545
+ ]
3522
3546
  }
3523
3547
  }
3524
3548
  }
@@ -3669,7 +3693,7 @@
3669
3693
  "post": {
3670
3694
  "operationId": "generate_bgm",
3671
3695
  "summary": "给整集生成/更换 AI 配乐(按情绪弧线)",
3672
- "description": "给整集生成/更换 AI 配乐(按情绪弧线)。后台异步,按用量后付不欠费。返回情绪弧线段数与预估耗时;用 get_bgm_status 轮询生成进度。★配乐生成/改动**不会自动进已有成片**——完成后必须重新 compose_episode(免费)才能听到;get_final_cut 的 bgm_stale=true 就是在提示这一步。别用 re-render(吃旧时间线,不含新配乐)。\n★prompt 可选:不传=全自动(和以前一样)。传了就是在自动结果上再加方向,整集一条,系统仍按情绪弧线分幕。怎么写见 get_bgm_prompt_guide(免费);要点=只写音乐维度(情绪气质/主奏配器/速度动态/厚薄空间/风格参照),别写剧情(「主角发现真相时要紧张」→写「紧张,节奏推进感强」)。只出纯器乐:要人声/歌词/拟音当乐器都不会生效,会在 prompt_warnings 里点名但**不拦生成**。\n★prompt_mode: guide(默认)=你的要求与平台专业护栏(时代与题材匹配/配器节制/高潮保规模/段间差异)一起生效;override=直通,跳过护栏,只保留技术底线(纯器乐/时长/可循环)。override 效果自负,**先用 guide 试**,确实拧不过来再换。不传 prompt 时沿用该集上次填的(get_bgm_status 可查)。\n★director 可选=结构化锁定(硬约束):逐项指定 mood 情绪 / instruments 主奏配器 / bpm / tonality 大小调,生成时**强制覆盖**配乐总监的判断(不像 prompt 只是方向,可能被护栏或 AI 取舍改写);没锁的项仍自动判断。整集锁写在 director 顶层;只锁某一幕写进 director.acts[{act,…}](同名字段覆盖整集的)。幕是按情绪弧线自动分的:按幕锁之前先调 get_bgm_plan(免费,只读)看本集分几幕、每幕覆盖哪些镜。参数拼错(bpm 越界、调性不是 major/minor/modal、幕号不存在)会 400;配器里写人声/环境声会被丢掉并在 director_warnings 点名。客户说「要慢一点、小调、用古筝」这类**明确参数**用 director;说「整体更压抑」这类**方向**用 prompt;两者可同时用。",
3696
+ "description": "给整集生成/更换 AI 配乐(按情绪弧线)。后台异步,按用量后付不欠费。返回情绪弧线段数与预估耗时;用 get_bgm_status 轮询生成进度。★配乐生成/改动**不会自动进已有成片**——完成后必须重新 compose_episode(免费)才能听到;get_final_cut 的 bgm_stale=true 就是在提示这一步。别用 re-render(吃旧时间线,不含新配乐)。\n★prompt 可选:不传=全自动(和以前一样)。传了就是在自动结果上再加方向,整集一条,系统仍按情绪弧线分幕。怎么写见 get_bgm_prompt_guide(免费);要点=只写音乐维度(情绪气质/主奏配器/速度动态/厚薄空间/风格参照),别写剧情(「主角发现真相时要紧张」→写「紧张,节奏推进感强」)。只出纯器乐:要人声/歌词/拟音当乐器都不会生效,会在 prompt_warnings 里点名但**不拦生成**。\n★prompt_mode: guide(默认)=你的要求与平台专业护栏(时代与题材匹配/配器节制/高潮保规模/段间差异)一起生效;override=直通,跳过护栏,只保留技术底线(纯器乐/时长/可循环)。override 效果自负,**先用 guide 试**,确实拧不过来再换。不传 prompt 时沿用该集上次填的(get_bgm_status 可查)。\n★director 可选=结构化锁定(硬约束):逐项指定 mood 情绪 / instruments 主奏配器 / bpm / tonality 大小调,生成时**强制覆盖**配乐总监的判断(不像 prompt 只是方向,可能被护栏或 AI 取舍改写);没锁的项仍自动判断。整集锁写在 director 顶层;只锁某一幕写进 director.acts[{act,…}](同名字段覆盖整集的)。幕是按情绪弧线自动分的:按幕锁之前先调 get_bgm_plan(免费,只读)看本集分几幕、每幕覆盖哪些镜。参数拼错(bpm 越界、调性不是 major/minor/modal、幕号不存在)会 400;配器里写人声/环境声会被丢掉并在 director_warnings 点名。客户说「要慢一点、小调、用古筝」这类**明确参数**用 director;说「整体更压抑」这类**方向**用 prompt;两者可同时用。\n★candidates 可选=候选整版对比:每幕出 2~3 首**完整**候选让客户挑(挑中的直接用,听到什么成片里就是什么)。每首单独按首计费 ⇒ 费用是单首的 N 倍,**必须先 quote_bgm_candidates 报价、把点数告诉客户、客户同意后**带 quote_id 调本工具。不传或传 1=原行为(每幕一首,无需报价)。生成后 get_bgm_status 的 candidate_groups 给每首的试听地址,select_bgm_candidate 挑选(免费),不挑默认第 1 首。",
3673
3697
  "tags": [
3674
3698
  "episodes"
3675
3699
  ],
@@ -3786,6 +3810,14 @@
3786
3810
  }
3787
3811
  },
3788
3812
  "additionalProperties": false
3813
+ },
3814
+ "candidates": {
3815
+ "type": "integer",
3816
+ "minimum": 1,
3817
+ "maximum": 3
3818
+ },
3819
+ "quote_id": {
3820
+ "type": "string"
3789
3821
  }
3790
3822
  }
3791
3823
  }
@@ -3823,8 +3855,8 @@
3823
3855
  "/episodes/{episode_id}/bgm-cues": {
3824
3856
  "get": {
3825
3857
  "operationId": "list_bgm_cues",
3826
- "summary": "列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms",
3827
- "description": "列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms。episode_id 为 null 的是**整剧共用段**,改它会影响所有集。免费。",
3858
+ "summary": "列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms、track_offset_ms / start",
3859
+ "description": "列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms、track_offset_ms / start_shift_ms(null=系统决定)。episode_id 为 null 的是**整剧共用段**,改它会影响所有集。免费。",
3828
3860
  "tags": [
3829
3861
  "episodes"
3830
3862
  ],
@@ -3870,8 +3902,8 @@
3870
3902
  "/episodes/{episode_id}/bgm-cues/{cue_id}": {
3871
3903
  "put": {
3872
3904
  "operationId": "update_bgm_cue",
3873
- "summary": "调某一段配乐的音量与淡入淡出(免费)",
3874
- "description": "调某一段配乐的音量与淡入淡出(免费)。只改这三项;换曲目或改覆盖范围用 generate_bgm 重新生成。★例:配乐进得太突兀→加大 fade_in_ms;某段压对白→降 gain_db。改完 compose_episode 重新合成才生效。",
3905
+ "summary": "调某一段配乐的音量、淡入淡出与起点(免费)",
3906
+ "description": "调某一段配乐的音量、淡入淡出与起点(免费)。换曲目或改覆盖镜号范围用 generate_bgm 重新生成。★例:配乐进得太突兀→加大 fade_in_ms;某段压对白→降 gain_db;想从曲子的高潮部分开始→设 track_offset_ms;想等这段第一镜开口之后再进→设 start_shift_ms。改完 compose_episode 重新合成才生效。",
3875
3907
  "tags": [
3876
3908
  "episodes"
3877
3909
  ],
@@ -3924,10 +3956,111 @@
3924
3956
  "type": "integer",
3925
3957
  "minimum": 0,
3926
3958
  "maximum": 15000
3959
+ },
3960
+ "track_offset_ms": {
3961
+ "anyOf": [
3962
+ {
3963
+ "type": "integer",
3964
+ "minimum": 0,
3965
+ "maximum": 600000
3966
+ },
3967
+ {
3968
+ "type": "null"
3969
+ }
3970
+ ]
3971
+ },
3972
+ "start_shift_ms": {
3973
+ "anyOf": [
3974
+ {
3975
+ "type": "integer",
3976
+ "minimum": 0,
3977
+ "maximum": 60000
3978
+ },
3979
+ {
3980
+ "type": "null"
3981
+ }
3982
+ ]
3983
+ }
3984
+ }
3985
+ }
3986
+ }
3987
+ }
3988
+ },
3989
+ "responses": {
3990
+ "200": {
3991
+ "description": "StarReel envelope",
3992
+ "content": {
3993
+ "application/json": {
3994
+ "schema": {
3995
+ "type": "object",
3996
+ "properties": {
3997
+ "code": {
3998
+ "type": "integer"
3999
+ },
4000
+ "message": {
4001
+ "type": "string"
4002
+ },
4003
+ "data": {
4004
+ "description": "Operation result payload"
4005
+ }
3927
4006
  }
3928
4007
  }
3929
4008
  }
3930
4009
  }
4010
+ },
4011
+ "402": {
4012
+ "description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
4013
+ }
4014
+ }
4015
+ }
4016
+ },
4017
+ "/episodes/{episode_id}/bgm-cues/{cue_id}/select": {
4018
+ "post": {
4019
+ "operationId": "select_bgm_candidate",
4020
+ "summary": "在候选整版对比里给某一幕挑一首(免费)",
4021
+ "description": "在候选整版对比里给某一幕挑一首(免费)。cue_id 与 track_id 取自 get_bgm_status 的 candidate_groups;同一幕的所有配乐段一起换。改完 compose_episode 重新合成才进成片。",
4022
+ "tags": [
4023
+ "episodes"
4024
+ ],
4025
+ "parameters": [
4026
+ {
4027
+ "name": "episode_id",
4028
+ "in": "path",
4029
+ "required": true,
4030
+ "schema": {
4031
+ "type": "integer",
4032
+ "exclusiveMinimum": 0
4033
+ }
4034
+ },
4035
+ {
4036
+ "name": "cue_id",
4037
+ "in": "path",
4038
+ "required": true,
4039
+ "schema": {
4040
+ "type": "integer",
4041
+ "exclusiveMinimum": 0,
4042
+ "description": "candidate_groups[].cue_ids 里任一个"
4043
+ }
4044
+ }
4045
+ ],
4046
+ "requestBody": {
4047
+ "required": true,
4048
+ "content": {
4049
+ "application/json": {
4050
+ "schema": {
4051
+ "type": "object",
4052
+ "properties": {
4053
+ "track_id": {
4054
+ "type": "integer",
4055
+ "exclusiveMinimum": 0,
4056
+ "description": "candidate_groups[].candidates[].track_id"
4057
+ }
4058
+ },
4059
+ "required": [
4060
+ "track_id"
4061
+ ]
4062
+ }
4063
+ }
3931
4064
  }
3932
4065
  },
3933
4066
  "responses": {
@@ -4052,6 +4185,64 @@
4052
4185
  }
4053
4186
  }
4054
4187
  },
4188
+ "/episodes/{episode_id}/bgm/quote": {
4189
+ "get": {
4190
+ "operationId": "quote_bgm_candidates",
4191
+ "summary": "给「配乐候选整版对比」报价(免费):每幕出 candidates 首完整候选的总点数 = 有配乐的幕数 × 候选数 × 单首价",
4192
+ "description": "给「配乐候选整版对比」报价(免费):每幕出 candidates 首完整候选的总点数 = 有配乐的幕数 × 候选数 × 单首价。返回 quote_id(15 分钟有效、一次性)。⚠️ 把 estimated_points 原样告诉用户,用户明确同意后才拿 quote_id 调 generate_bgm(candidates 必须与这里一致),不要擅自确认。另有少量配乐总监 LLM 文本费(每幕一次),不在报价内。",
4193
+ "tags": [
4194
+ "episodes"
4195
+ ],
4196
+ "parameters": [
4197
+ {
4198
+ "name": "episode_id",
4199
+ "in": "path",
4200
+ "required": true,
4201
+ "schema": {
4202
+ "type": "integer",
4203
+ "exclusiveMinimum": 0
4204
+ }
4205
+ },
4206
+ {
4207
+ "name": "candidates",
4208
+ "in": "query",
4209
+ "required": true,
4210
+ "schema": {
4211
+ "type": "integer",
4212
+ "minimum": 2,
4213
+ "maximum": 3,
4214
+ "description": "每幕几首候选(2~3)"
4215
+ }
4216
+ }
4217
+ ],
4218
+ "responses": {
4219
+ "200": {
4220
+ "description": "StarReel envelope",
4221
+ "content": {
4222
+ "application/json": {
4223
+ "schema": {
4224
+ "type": "object",
4225
+ "properties": {
4226
+ "code": {
4227
+ "type": "integer"
4228
+ },
4229
+ "message": {
4230
+ "type": "string"
4231
+ },
4232
+ "data": {
4233
+ "description": "Operation result payload"
4234
+ }
4235
+ }
4236
+ }
4237
+ }
4238
+ }
4239
+ },
4240
+ "402": {
4241
+ "description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
4242
+ }
4243
+ }
4244
+ }
4245
+ },
4055
4246
  "/episodes/{episode_id}/chain-frames": {
4056
4247
  "post": {
4057
4248
  "operationId": "chain_frames",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starreel/mcp",
3
- "version": "0.1.133",
3
+ "version": "0.1.135",
4
4
  "mcpName": "ai.starreel/starreel",
5
5
  "description": "StarReel MCP server — drive the AI short-drama production pipeline (script → storyboards → frames → video → final cut) from Claude, Cursor, or any MCP client",
6
6
  "license": "MIT",
package/server.json CHANGED
@@ -2,13 +2,13 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
3
3
  "name": "ai.starreel/starreel",
4
4
  "description": "Turn a script into a finished, downloadable short-drama episode — via MCP or REST.",
5
- "version": "0.1.133",
5
+ "version": "0.1.135",
6
6
  "websiteUrl": "https://starreel.ai",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "@starreel/mcp",
11
- "version": "0.1.133",
11
+ "version": "0.1.135",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  },