@starreel/mcp 0.1.110 → 0.1.112

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
@@ -258,7 +258,13 @@ content that will be rejected.
258
258
  `native_voice_anchor` lists `unanchored_speakers` (not locked yet) and
259
259
  `stale_shots` (made before the lock — `regenerate_shot_video` them). Decide
260
260
  the voice reference early; picking it after the whole episode is rendered
261
- means redoing shots.
261
+ means redoing shots. The shot's line field may be empty — then the actual
262
+ audio decides: a calibrated speech probe must hear a voice in the chosen
263
+ window, and its transcript becomes the anchor text (`text_source:
264
+ "transcript"`); no voice or no usable transcript is refused. Call it with
265
+ `preview: true` first: you get `preview_url` and the anchor text without
266
+ anything being written, then call again without `preview` (same
267
+ `start_s` / `dur_s`) to lock it.
262
268
 
263
269
  **No sample? Design one.** `design_voice` turns a one-sentence description
264
270
  (age, gender, timbre, pace, mood — say what you want, not what you don't)
@@ -829,6 +835,21 @@ error from `replace_shot_dialogue`) — re-voicing can't fix them; regenerate th
829
835
  shot instead.
830
836
  Either way, `compose_episode` afterwards — the final cut still holds the old audio until you do.
831
837
 
838
+ **Narration / voice-over is a separate track — use `revoice_narration`, not
839
+ `replace_shot_dialogue`.** On clip-audio projects the narration is mixed into the
840
+ shot's video when it is generated, so `replace_shot_dialogue` refuses
841
+ narration-only shots ("no on-screen line to replace"). To change the narrator's
842
+ voice, set it first (`set_character_voice` on the narrator character, or
843
+ `design_voice`), or edit the narration line with `update_shot`, then call
844
+ `revoice_narration`: it keeps the picture and on-screen lines, silences only the
845
+ narration spans, re-synthesises them in the current voice, mixes them back and
846
+ re-checks — no video regeneration. Shots that mix on-screen lines and narration
847
+ work only when the clause count matches the speech spans heard in the clip;
848
+ otherwise it refuses rather than guess (a wrong guess would silence an actor).
849
+ If the vendor *spoke on its own* in a shot with no lines at all, use
850
+ `replace_shot_dialogue` with `mute_only: true` (free; on clip-audio projects it
851
+ silences any narration in that shot too).
852
+
832
853
  Four distinct causes; they need **opposite** fixes, so identify the family first.
833
854
 
834
855
  1. **The line moved after the video was made.** If you edited `dialogue` on a
@@ -1058,7 +1079,12 @@ to close") tells the vendor to fit that entire sequence into each 3-second shot.
1058
1079
  `design_voice` → `get_voice_design` → `save_designed_voice` (no sample needed),
1059
1080
  `speak_with_voice`, `set_character_voice`, `list_voices`, `delete_voice`,
1060
1081
  `generate_bgm` (optional `prompt` steers the music; read `get_bgm_prompt_guide` first),
1061
- `get_bgm_prompt_guide`, `replace_shot_dialogue`
1082
+ `get_bgm_prompt_guide`, `replace_shot_dialogue`, `revoice_narration`
1083
+ - **Mix** (free; settings only — `compose_episode` again to hear them, `rerender_episode`
1084
+ reuses the old timeline): whole drama via `update_project_settings`
1085
+ (`bgm_volume_preset` / `bgm_volume_db` / `lufs_target`); one episode via
1086
+ `set_episode_audio`; one music section's level and fades via `list_bgm_cues` →
1087
+ `update_bgm_cue`. Don't download the cut to re-mix it locally.
1062
1088
  - **Finish**: `compose_episode`, `get_final_cut`, `get_export`,
1063
1089
  `generate_episode_poster`, `generate_cover`
1064
1090
  - **What did this drama cost?** `get_drama_bill` — per kind `charged` / `refunded`
@@ -1161,6 +1187,7 @@ Full reference: https://api.shortreelai.com/docs/mcp
1161
1187
  - `ready` 和生成数量只表示产物存在;当前有效数量使用 `get_pipeline_status.asset_versions_summary.*.current_effective`。换帧后旧视频仍可查看,但失效版本不能混入当前有效汇总。
1162
1188
  - `get_asset_versions(storyboard_id)` 查看历史版本链接、依赖 ID/版本哈希及各自状态。历史没有快照时不从当前绑定伪造依赖。
1163
1189
  - `get_image_dispatch(image_id)` 查一张镜头帧的派发台账:每张参考图的用途、所属角色、是否送达模型,派发前被剔的图在哪一步因什么被剔(如时代预检点名的冲突物件),以及最终发给模型的提示词。画面「不像某角色/少了某道具」时先查它,确认是参考没送到还是模型没画对,再决定改什么;`ledger` 不是 `full` 时缺失项为 `null`,不能当作已送达。
1190
+ - `get_stale_portrait_frames(episode_id)` 换定妆图后查影响范围:列出在用帧仍基于角色旧资产的镜头,每条附证据(`ledger-old-asset` / `references-previous-asset` / `portrait-born-after-frame`)。`unknown` 是证据不足,不是已过期,不要据此批量重出。先把 `stale` 清单和 `has_video` 告诉客户,由客户决定重出哪些镜——重出帧和视频都要付费。
1164
1191
  - `get_asset_binding_repair_plan(episode_id)` 只返回缺失场景绑定的候选和依据,`applied=false`。必须逐项审核,不按名字自动回填,也不自动重新生成客户已完成的片子。
1165
1192
 
1166
1193
  ### Original product lettering
@@ -255,6 +255,7 @@ const PROJECT_SETTINGS_FIELDS = {
255
255
  bgm_source: z.enum(['own', 'clip']).optional().describe("BGM来源:own=自有BGM流水线(默认,抑制裸片BGM);clip=保留视频原生BGM、终拼不叠加"),
256
256
  bgm_volume_preset: z.enum(['off', 'low', 'auto', 'high']).optional().describe('BGM音量档:off静音/low轻(-28dB)/auto自适应(默认,静段可闻·对白不压麦)/high强'),
257
257
  bgm_volume_db: z.number().optional().describe('自定义BGM音量(dB,负值),覆盖预设档、关自适应'),
258
+ lufs_target: z.number().min(-23).max(-10).optional().describe('成片整体响度目标(LUFS,默认-16;越接近0越响)。终拼母带按它处理,改完需 compose_episode 重新合成'),
258
259
  use_clip_audio: z.boolean().optional().describe('用视频原声(★默认开):true/不传=跳过TTS配音直接用视频自带声;false=改回TTS配音。所有类型默认视频原声,建剧时应主动告知客户可切换配音(★直接改成片音频)'),
259
260
  // 字幕(烧录/双语/仅译文/位置/边距/动效)
260
261
  show_subtitles: z.boolean().optional().describe('字幕烧录总开关:false=不烧字幕轨'),
@@ -527,6 +528,7 @@ export function registerProduceTools(server, client) {
527
528
  })));
528
529
  server.tool('get_frame_retry_diagnosis', '免费只读查询图片任务的连败诊断、证据任务 ID 和下一步动作。diagnosis_required 时先调用本工具:审计故障重审已有候选,未知原因先复核;不得原样重抽或擅自改剧本。', { image_id: z.number().int().positive() }, async ({ image_id }) => jsonResult(await client.produceGet(`/images/${image_id}/diagnosis`)));
529
530
  server.tool('get_image_dispatch', '免费只读查询一张镜头帧的派发台账:实际用了哪几张参考图、每张的用途与所属角色、是否送达模型、没送达或派发前被剔除的是哪一步因什么被剔(如时代预检点名的冲突物件),以及真正发给图片模型的最终提示词。用于回答「到底用了哪张图、为什么画面不像」;先查清再决定是否重出,别靠猜反复付费重抽。ledger=partial/none 表示该图生成于台账上线前,缺失字段如实为 null。', { image_id: z.number().int().positive() }, async ({ image_id }) => jsonResult(await client.produceGet(`/images/${image_id}/dispatch`)));
531
+ server.tool('get_stale_portrait_frames', '免费只读查询一集里哪些镜头的在用首帧/尾帧,是拿角色已被换掉的旧资产(旧定妆图、旧设定图、旧身体/发型图)画的。换定妆图后用它确认影响范围,再由客户决定重出哪些镜(重出要付费,本工具不改任何数据)。verdict=stale 必附证据:ledger-old-asset(派发台账直接记录用了旧图)/ references-previous-asset(参考图里有该角色以前的旧资产)/ portrait-born-after-frame(当前定妆图晚于该帧诞生);证不了的标 unknown 并给原因,不当作已过期。has_video=true 表示该镜视频也基于旧帧,重出帧后视频需重生。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/stale-portrait-frames`)));
530
532
  server.tool('get_asset_versions', '只读查询一个镜头的全部图片/视频版本、历史查看链接、依赖 ID 与版本哈希,以及已生成→与当前输入一致→审核有效→已采用四项独立事实。历史缺少依赖记录时显示未知,不补造快照。免费。', { storyboard_id: z.number().int().positive() }, async ({ storyboard_id }) => jsonResult(await client.produceGet(`/storyboards/${storyboard_id}/asset-versions`)));
531
533
  server.tool('get_asset_binding_repair_plan', '只读生成一集历史缺失场景绑定的可审核修复提案。返回原始地点/时段、候选 ID、匹配依据、歧义和 proposed_scene_id;applied=false,绝不按名字自动回填。逐项核对后才由客户决定是否改绑。免费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/asset-binding-repair-plan`)));
532
534
  server.tool('get_storyboards', '读某一集的分镜列表(供审阅/查进度)。含每镜首帧(first_frame_image)与视频(video_url)是否就绪。' +
@@ -1338,7 +1340,14 @@ export function registerProduceTools(server, client) {
1338
1340
  '★该镜已有对口型产物时,成片优先用对口型版,换轨不会生效。整集批量用 repair_episode_dialogue。' +
1339
1341
  '\n★写回前会对产物复核(与 scan_dialogue_coverage 同一判据):换完仍断音(truncated)、重复念/多念(extra_speech)、' +
1340
1342
  '念的不是台词(off_script)→ **不写回**、本次费用自动退回,返回错误说明哪一类——这类镜换音修不好,改用 regenerate_shot_video。' +
1341
- '成功时返回 verify:verified=false 表示没判出来(转写失败/台词太短),照常写回但没复核过,自己听一下。', { storyboard_id: z.number().int().positive() }, async ({ storyboard_id }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/dialogue-replace`)));
1343
+ '成功时返回 verify:verified=false 表示没判出来(转写失败/台词太短),照常写回但没复核过,自己听一下。', { storyboard_id: z.number().int().positive(), mute_only: z.boolean().optional().describe('只静音、不重配(不扣费):把视频原声里的人声段静掉。用于厂商在**没有台词的镜里自己念出声**的情况。★原声剧上本镜的旁白也在原声里,会一起被静掉——要换旁白用 revoice_narration,别用它') }, async ({ storyboard_id, mute_only }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/dialogue-replace`, mute_only ? { mode: 'mute' } : {})));
1344
+ server.tool('revoice_narration', '换某镜**旁白/画外音**的声音(或改过旁白文字后重配)。**不重新生成视频**:保留画面与出镜台词,只把旁白那几段静掉、' +
1345
+ '按当前旁白声音重新合成并混回,再复核。同步调用,按 TTS 档(千字符)计费。' +
1346
+ '★先改好再调:想换声音先 set_character_voice 给「旁白」角色(没有就 design_voice 造一个),想改文字先 update_shot 改 dialogue 里的旁白行。' +
1347
+ '★纯旁白镜(本镜只有旁白)直接可用;**混合镜**(既有出镜台词又有旁白)要求分句数与视频里的语音段数一致,' +
1348
+ '对不上就报错不猜(猜错会把出镜台词一起静掉)——这时只能 regenerate_shot_video。' +
1349
+ '★台词栏没有旁白行会直接报错;厂商在无台词镜里乱念,用 replace_shot_dialogue 的 mute_only。' +
1350
+ '★写回后成片会直接用它(不需要额外开关),rerender_episode 重拼即可;复核不通过不写回,旁白合成费不退。', { storyboard_id: z.number().int().positive() }, async ({ storyboard_id }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/dialogue-replace`, { mode: 'narration' })));
1342
1351
  server.tool('repair_episode_dialogue', '★整集批量修「台词没念完/念了别的」。默认只修 scan_dialogue_coverage 判为有缺陷的镜' +
1343
1352
  '(truncated/off_script/extra_speech);minor_gap 那一族是转写误差、原声本来是好的,不动。' +
1344
1353
  '原理与 replace_shot_dialogue 相同:克隆音重配整句 + 静音原声段,**不重新生成视频**——' +
@@ -1353,9 +1362,11 @@ export function registerProduceTools(server, client) {
1353
1362
  only_flagged: z.boolean().optional()
1354
1363
  .describe('默认 true=只修被判有缺陷的镜。传 false 会把全集可替换镜都重配一遍——既花钱又可能把本来对的音频换坏,除非确有需要别关'),
1355
1364
  force: z.boolean().optional().describe('对已有替换产物的镜也重跑(默认跳过,幂等)'),
1356
- }, async ({ episode_id, only_flagged, force }) => jsonResult(await client.producePost(`/episodes/${episode_id}/dialogue-repair`, {
1365
+ mute_only: z.boolean().optional().describe('只静音、不重配(不扣费):把视频原声里的人声段静掉。用于厂商在**没有台词的镜里自己念出声**的情况。★原声剧上本镜的旁白也在原声里,会一起被静掉——要换旁白用 revoice_narration,别用它'),
1366
+ }, async ({ episode_id, only_flagged, force, mute_only }) => jsonResult(await client.producePost(`/episodes/${episode_id}/dialogue-repair`, {
1357
1367
  ...(only_flagged === undefined ? {} : { only_flagged }),
1358
1368
  ...(force ? { force: true } : {}),
1369
+ ...(mute_only ? { mute_only: true } : {}),
1359
1370
  })));
1360
1371
  server.tool('get_dialogue_repair_status', '查台词修复的进度(免费)。repair_episode_dialogue 是后台异步串行跑的,用这个轮询。'
1361
1372
  + '\n★这个工具此前**不存在**,而 repair_episode_dialogue 的描述里却写着「进度用 '
@@ -1384,6 +1395,25 @@ export function registerProduceTools(server, client) {
1384
1395
  ...(prompt === undefined ? {} : { prompt }),
1385
1396
  ...(prompt_mode ? { prompt_mode } : {}),
1386
1397
  })));
1398
+ server.tool('set_episode_audio', '设置**本集**配乐音量(覆盖整剧设定,免费)。整剧统一的用 update_project_settings。' +
1399
+ '★只改设置:成片要 compose_episode 重新合成才变(rerender_episode 用旧时间线,不带上)。' +
1400
+ '★想调某一段配乐的音量或淡入淡出,用 list_bgm_cues + update_bgm_cue。', {
1401
+ episode_id: z.number().int().positive(),
1402
+ bgm_volume_preset: z.enum(['off', 'low', 'auto', 'high', 'inherit']).optional()
1403
+ .describe('本集配乐音量档:off静音/low轻(-28dB)/auto自适应/high强(-16dB)/inherit跟随整剧'),
1404
+ bgm_volume_db: z.number().min(-60).max(0).nullable().optional()
1405
+ .describe('本集自定义配乐音量(dB,-60~0),覆盖档位并关自适应;null=清除、跟随整剧'),
1406
+ }, async ({ episode_id, ...rest }) => jsonResult(await client.producePut(`/episodes/${episode_id}/audio`, rest)));
1407
+ server.tool('list_bgm_cues', '列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms。' +
1408
+ 'episode_id 为 null 的是**整剧共用段**,改它会影响所有集。免费。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/bgm-cues`)));
1409
+ server.tool('update_bgm_cue', '调某一段配乐的音量与淡入淡出(免费)。只改这三项;换曲目或改覆盖范围用 generate_bgm 重新生成。' +
1410
+ '★例:配乐进得太突兀→加大 fade_in_ms;某段压对白→降 gain_db。改完 compose_episode 重新合成才生效。', {
1411
+ episode_id: z.number().int().positive(),
1412
+ cue_id: z.number().int().positive().describe('来自 list_bgm_cues'),
1413
+ gain_db: z.number().min(-60).max(0).nullable().optional().describe('这段配乐的音量(dB,-60~0),设了就覆盖音量档;null=恢复跟随本集/整剧音量档'),
1414
+ fade_in_ms: z.number().int().min(0).max(15000).optional().describe('淡入毫秒(整数,0~15000)'),
1415
+ fade_out_ms: z.number().int().min(0).max(15000).optional().describe('淡出毫秒(整数,0~15000)'),
1416
+ }, async ({ episode_id, cue_id, ...rest }) => jsonResult(await client.producePut(`/episodes/${episode_id}/bgm-cues/${cue_id}`, rest)));
1387
1417
  server.tool('get_bgm_prompt_guide', '取「AI 配乐提示词」的书写规范:两个档位怎么选、该写哪些维度(附可照抄的示例)、' +
1388
1418
  '哪些是写了也不会生效的硬限制(纯器乐/时长/不做音效/不复刻具体曲目)、常见写坏的方式。' +
1389
1419
  '免费,纯静态,与具体剧目无关——写 generate_bgm 的 prompt 之前先读它,别自己猜。' +
@@ -1791,11 +1821,15 @@ export function registerProduceTools(server, client) {
1791
1821
  '\n★选哪一镜:该角色**独自说话、台词较长、没有背景音乐和别人插话**的镜;start_s/dur_s 可只截其中一段(不传则取默认窗口,时长会按各引擎上限自动钳制)。' +
1792
1822
  '\n★只对**之后**生成的视频生效。锁之前已经出好的镜会出现在 get_pipeline_status 的 native_voice_anchor.stale_shots 里,' +
1793
1823
  '用 regenerate_shot_video 重出才会统一。所以最省的做法是:每个说话角色先出一镜、选定声线,再批量出其余镜。' +
1824
+ '\n★台词栏为空也能用:以实际音频为准——平台用已标定的人声探测确认所选区间里有人声,并把转写文字当锚文本;' +
1825
+ '探测不到人声、或转写不出可用文字就拒(锚选错会污染此后所有镜的嗓音,宁拒不猜)。响应里 text_source 标明文本来自台词还是转写。' +
1826
+ '\n★先 preview:true 试听:只截取并返回 preview_url 与锚文本,**不写入角色**;听着对再去掉 preview、用同样的 start_s/dur_s 正式提交。' +
1794
1827
  '\n★有客户授权的外部音源时改用 clone_voice + set_character_voice;两者二选一,后设的覆盖先设的。', {
1795
1828
  character_id: z.number().int().positive(),
1796
- storyboard_id: z.number().int().positive().describe('本剧里有该角色台词、已出视频的镜'),
1829
+ storyboard_id: z.number().int().positive().describe('本剧里已出视频、该角色在里面说话的镜(台词栏为空时以实际音频为准)'),
1797
1830
  start_s: z.number().min(0).optional().describe('可选:从该镜第几秒开始截'),
1798
1831
  dur_s: z.number().positive().optional().describe('可选:截多长(秒)'),
1832
+ preview: z.boolean().optional().describe('true=只截取试听、返回 preview_url 与锚文本,不写入角色。建议先试听再正式提交'),
1799
1833
  }, async ({ character_id, ...rest }) => jsonResult(await client.producePost(`/characters/${character_id}/voice-anchor-from-shot`, rest)));
1800
1834
  // ── 素材交接包:客户自己拼片的通道 ──────────────────────────────────────
1801
1835
  // 与 compose_episode 的分工:compose_episode = 「平台替你拼」;本组工具 = 「素材给你,
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.110",
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.110 tool surface (operationIds match MCP tool names 1:1)."
5
+ "version": "0.1.112",
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.112 tool surface (operationIds match MCP tool names 1:1)."
7
7
  },
8
8
  "servers": [
9
9
  {
@@ -567,7 +567,7 @@
567
567
  "post": {
568
568
  "operationId": "set_voice_anchor_from_shot",
569
569
  "summary": "从本剧某一镜选定角色的声线(声音基准)",
570
- "description": "从本剧某一镜选定角色的声线(声音基准)。免费。不需要客户提供外部音源——就用片子里这个角色已经念出来的声音。\n★★【原声剧(use_clip_audio=true,默认)里这就是「锁声线」】选定后,之后生成的视频会把这段声音交给视频厂商当参考音频,同一角色前后镜的嗓音跟它走;不锁的话每镜由厂商各自采样,前后镜声线会漂。\n★选哪一镜:该角色**独自说话、台词较长、没有背景音乐和别人插话**的镜;start_s/dur_s 可只截其中一段(不传则取默认窗口,时长会按各引擎上限自动钳制)。\n★只对**之后**生成的视频生效。锁之前已经出好的镜会出现在 get_pipeline_status 的 native_voice_anchor.stale_shots 里,用 regenerate_shot_video 重出才会统一。所以最省的做法是:每个说话角色先出一镜、选定声线,再批量出其余镜。\n★有客户授权的外部音源时改用 clone_voice + set_character_voice;两者二选一,后设的覆盖先设的。",
570
+ "description": "从本剧某一镜选定角色的声线(声音基准)。免费。不需要客户提供外部音源——就用片子里这个角色已经念出来的声音。\n★★【原声剧(use_clip_audio=true,默认)里这就是「锁声线」】选定后,之后生成的视频会把这段声音交给视频厂商当参考音频,同一角色前后镜的嗓音跟它走;不锁的话每镜由厂商各自采样,前后镜声线会漂。\n★选哪一镜:该角色**独自说话、台词较长、没有背景音乐和别人插话**的镜;start_s/dur_s 可只截其中一段(不传则取默认窗口,时长会按各引擎上限自动钳制)。\n★只对**之后**生成的视频生效。锁之前已经出好的镜会出现在 get_pipeline_status 的 native_voice_anchor.stale_shots 里,用 regenerate_shot_video 重出才会统一。所以最省的做法是:每个说话角色先出一镜、选定声线,再批量出其余镜。\n★台词栏为空也能用:以实际音频为准——平台用已标定的人声探测确认所选区间里有人声,并把转写文字当锚文本;探测不到人声、或转写不出可用文字就拒(锚选错会污染此后所有镜的嗓音,宁拒不猜)。响应里 text_source 标明文本来自台词还是转写。\n★先 preview:true 试听:只截取并返回 preview_url 与锚文本,**不写入角色**;听着对再去掉 preview、用同样的 start_s/dur_s 正式提交。\n★有客户授权的外部音源时改用 clone_voice + set_character_voice;两者二选一,后设的覆盖先设的。",
571
571
  "tags": [
572
572
  "characters"
573
573
  ],
@@ -592,7 +592,7 @@
592
592
  "storyboard_id": {
593
593
  "type": "integer",
594
594
  "exclusiveMinimum": 0,
595
- "description": "本剧里有该角色台词、已出视频的镜"
595
+ "description": "本剧里已出视频、该角色在里面说话的镜(台词栏为空时以实际音频为准)"
596
596
  },
597
597
  "start_s": {
598
598
  "type": "number",
@@ -601,6 +601,9 @@
601
601
  "dur_s": {
602
602
  "type": "number",
603
603
  "exclusiveMinimum": 0
604
+ },
605
+ "preview": {
606
+ "type": "boolean"
604
607
  }
605
608
  },
606
609
  "required": [
@@ -816,6 +819,11 @@
816
819
  "bgm_volume_db": {
817
820
  "type": "number"
818
821
  },
822
+ "lufs_target": {
823
+ "type": "number",
824
+ "minimum": -23,
825
+ "maximum": -10
826
+ },
819
827
  "use_clip_audio": {
820
828
  "type": "boolean"
821
829
  },
@@ -1151,6 +1159,11 @@
1151
1159
  "bgm_volume_db": {
1152
1160
  "type": "number"
1153
1161
  },
1162
+ "lufs_target": {
1163
+ "type": "number",
1164
+ "minimum": -23,
1165
+ "maximum": -10
1166
+ },
1154
1167
  "use_clip_audio": {
1155
1168
  "type": "boolean"
1156
1169
  },
@@ -3259,6 +3272,80 @@
3259
3272
  }
3260
3273
  }
3261
3274
  },
3275
+ "/episodes/{episode_id}/audio": {
3276
+ "put": {
3277
+ "operationId": "set_episode_audio",
3278
+ "summary": "设置**本集**配乐音量(覆盖整剧设定,免费)",
3279
+ "description": "设置**本集**配乐音量(覆盖整剧设定,免费)。整剧统一的用 update_project_settings。★只改设置:成片要 compose_episode 重新合成才变(rerender_episode 用旧时间线,不带上)。★想调某一段配乐的音量或淡入淡出,用 list_bgm_cues + update_bgm_cue。",
3280
+ "tags": [
3281
+ "episodes"
3282
+ ],
3283
+ "parameters": [
3284
+ {
3285
+ "name": "episode_id",
3286
+ "in": "path",
3287
+ "required": true,
3288
+ "schema": {
3289
+ "type": "integer",
3290
+ "exclusiveMinimum": 0
3291
+ }
3292
+ }
3293
+ ],
3294
+ "requestBody": {
3295
+ "required": false,
3296
+ "content": {
3297
+ "application/json": {
3298
+ "schema": {
3299
+ "type": "object",
3300
+ "properties": {
3301
+ "bgm_volume_preset": {
3302
+ "type": "string",
3303
+ "enum": [
3304
+ "off",
3305
+ "low",
3306
+ "auto",
3307
+ "high",
3308
+ "inherit"
3309
+ ]
3310
+ },
3311
+ "bgm_volume_db": {
3312
+ "type": "number",
3313
+ "minimum": -60,
3314
+ "maximum": 0
3315
+ }
3316
+ }
3317
+ }
3318
+ }
3319
+ }
3320
+ },
3321
+ "responses": {
3322
+ "200": {
3323
+ "description": "StarReel envelope",
3324
+ "content": {
3325
+ "application/json": {
3326
+ "schema": {
3327
+ "type": "object",
3328
+ "properties": {
3329
+ "code": {
3330
+ "type": "integer"
3331
+ },
3332
+ "message": {
3333
+ "type": "string"
3334
+ },
3335
+ "data": {
3336
+ "description": "Operation result payload"
3337
+ }
3338
+ }
3339
+ }
3340
+ }
3341
+ }
3342
+ },
3343
+ "402": {
3344
+ "description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
3345
+ }
3346
+ }
3347
+ }
3348
+ },
3262
3349
  "/episodes/{episode_id}/audit-seams": {
3263
3350
  "post": {
3264
3351
  "operationId": "audit_seams",
@@ -3440,6 +3527,137 @@
3440
3527
  }
3441
3528
  }
3442
3529
  },
3530
+ "/episodes/{episode_id}/bgm-cues": {
3531
+ "get": {
3532
+ "operationId": "list_bgm_cues",
3533
+ "summary": "列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms",
3534
+ "description": "列出本集的配乐段(AI 配乐按情绪分幕,每段覆盖一段镜号 sb_from~sb_to):id、覆盖范围、gain_db(null=跟随音量档)、fade_in_ms / fade_out_ms。episode_id 为 null 的是**整剧共用段**,改它会影响所有集。免费。",
3535
+ "tags": [
3536
+ "episodes"
3537
+ ],
3538
+ "parameters": [
3539
+ {
3540
+ "name": "episode_id",
3541
+ "in": "path",
3542
+ "required": true,
3543
+ "schema": {
3544
+ "type": "integer",
3545
+ "exclusiveMinimum": 0
3546
+ }
3547
+ }
3548
+ ],
3549
+ "responses": {
3550
+ "200": {
3551
+ "description": "StarReel envelope",
3552
+ "content": {
3553
+ "application/json": {
3554
+ "schema": {
3555
+ "type": "object",
3556
+ "properties": {
3557
+ "code": {
3558
+ "type": "integer"
3559
+ },
3560
+ "message": {
3561
+ "type": "string"
3562
+ },
3563
+ "data": {
3564
+ "description": "Operation result payload"
3565
+ }
3566
+ }
3567
+ }
3568
+ }
3569
+ }
3570
+ },
3571
+ "402": {
3572
+ "description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
3573
+ }
3574
+ }
3575
+ }
3576
+ },
3577
+ "/episodes/{episode_id}/bgm-cues/{cue_id}": {
3578
+ "put": {
3579
+ "operationId": "update_bgm_cue",
3580
+ "summary": "调某一段配乐的音量与淡入淡出(免费)",
3581
+ "description": "调某一段配乐的音量与淡入淡出(免费)。只改这三项;换曲目或改覆盖范围用 generate_bgm 重新生成。★例:配乐进得太突兀→加大 fade_in_ms;某段压对白→降 gain_db。改完 compose_episode 重新合成才生效。",
3582
+ "tags": [
3583
+ "episodes"
3584
+ ],
3585
+ "parameters": [
3586
+ {
3587
+ "name": "episode_id",
3588
+ "in": "path",
3589
+ "required": true,
3590
+ "schema": {
3591
+ "type": "integer",
3592
+ "exclusiveMinimum": 0
3593
+ }
3594
+ },
3595
+ {
3596
+ "name": "cue_id",
3597
+ "in": "path",
3598
+ "required": true,
3599
+ "schema": {
3600
+ "type": "integer",
3601
+ "exclusiveMinimum": 0,
3602
+ "description": "来自 list_bgm_cues"
3603
+ }
3604
+ }
3605
+ ],
3606
+ "requestBody": {
3607
+ "required": false,
3608
+ "content": {
3609
+ "application/json": {
3610
+ "schema": {
3611
+ "type": "object",
3612
+ "properties": {
3613
+ "gain_db": {
3614
+ "type": "number",
3615
+ "minimum": -60,
3616
+ "maximum": 0
3617
+ },
3618
+ "fade_in_ms": {
3619
+ "type": "integer",
3620
+ "minimum": 0,
3621
+ "maximum": 15000
3622
+ },
3623
+ "fade_out_ms": {
3624
+ "type": "integer",
3625
+ "minimum": 0,
3626
+ "maximum": 15000
3627
+ }
3628
+ }
3629
+ }
3630
+ }
3631
+ }
3632
+ },
3633
+ "responses": {
3634
+ "200": {
3635
+ "description": "StarReel envelope",
3636
+ "content": {
3637
+ "application/json": {
3638
+ "schema": {
3639
+ "type": "object",
3640
+ "properties": {
3641
+ "code": {
3642
+ "type": "integer"
3643
+ },
3644
+ "message": {
3645
+ "type": "string"
3646
+ },
3647
+ "data": {
3648
+ "description": "Operation result payload"
3649
+ }
3650
+ }
3651
+ }
3652
+ }
3653
+ }
3654
+ },
3655
+ "402": {
3656
+ "description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
3657
+ }
3658
+ }
3659
+ }
3660
+ },
3443
3661
  "/episodes/{episode_id}/bgm-status": {
3444
3662
  "get": {
3445
3663
  "operationId": "get_bgm_status",
@@ -4043,6 +4261,9 @@
4043
4261
  },
4044
4262
  "force": {
4045
4263
  "type": "boolean"
4264
+ },
4265
+ "mute_only": {
4266
+ "type": "boolean"
4046
4267
  }
4047
4268
  }
4048
4269
  }
@@ -6663,6 +6884,53 @@
6663
6884
  }
6664
6885
  }
6665
6886
  },
6887
+ "/episodes/{episode_id}/stale-portrait-frames": {
6888
+ "get": {
6889
+ "operationId": "get_stale_portrait_frames",
6890
+ "summary": "免费只读查询一集里哪些镜头的在用首帧/尾帧,是拿角色已被换掉的旧资产(旧定妆图、旧设定图、旧身体/发型图)画的",
6891
+ "description": "免费只读查询一集里哪些镜头的在用首帧/尾帧,是拿角色已被换掉的旧资产(旧定妆图、旧设定图、旧身体/发型图)画的。换定妆图后用它确认影响范围,再由客户决定重出哪些镜(重出要付费,本工具不改任何数据)。verdict=stale 必附证据:ledger-old-asset(派发台账直接记录用了旧图)/ references-previous-asset(参考图里有该角色以前的旧资产)/ portrait-born-after-frame(当前定妆图晚于该帧诞生);证不了的标 unknown 并给原因,不当作已过期。has_video=true 表示该镜视频也基于旧帧,重出帧后视频需重生。",
6892
+ "tags": [
6893
+ "episodes"
6894
+ ],
6895
+ "parameters": [
6896
+ {
6897
+ "name": "episode_id",
6898
+ "in": "path",
6899
+ "required": true,
6900
+ "schema": {
6901
+ "type": "integer",
6902
+ "exclusiveMinimum": 0
6903
+ }
6904
+ }
6905
+ ],
6906
+ "responses": {
6907
+ "200": {
6908
+ "description": "StarReel envelope",
6909
+ "content": {
6910
+ "application/json": {
6911
+ "schema": {
6912
+ "type": "object",
6913
+ "properties": {
6914
+ "code": {
6915
+ "type": "integer"
6916
+ },
6917
+ "message": {
6918
+ "type": "string"
6919
+ },
6920
+ "data": {
6921
+ "description": "Operation result payload"
6922
+ }
6923
+ }
6924
+ }
6925
+ }
6926
+ }
6927
+ },
6928
+ "402": {
6929
+ "description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
6930
+ }
6931
+ }
6932
+ }
6933
+ },
6666
6934
  "/episodes/{episode_id}/storyboards": {
6667
6935
  "get": {
6668
6936
  "operationId": "get_storyboards",
@@ -9815,6 +10083,22 @@
9815
10083
  }
9816
10084
  }
9817
10085
  ],
10086
+ "requestBody": {
10087
+ "required": false,
10088
+ "content": {
10089
+ "application/json": {
10090
+ "schema": {
10091
+ "type": "object",
10092
+ "properties": {
10093
+ "mode": {
10094
+ "type": "string",
10095
+ "description": "(assembled by the client from tool args — see tool 'replace_shot_dialogue')"
10096
+ }
10097
+ }
10098
+ }
10099
+ }
10100
+ }
10101
+ },
9818
10102
  "responses": {
9819
10103
  "200": {
9820
10104
  "description": "StarReel envelope",
@@ -9840,7 +10124,10 @@
9840
10124
  "402": {
9841
10125
  "description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
9842
10126
  }
9843
- }
10127
+ },
10128
+ "x-also-tools": [
10129
+ "revoice_narration"
10130
+ ]
9844
10131
  }
9845
10132
  },
9846
10133
  "/storyboards/{storyboard_id}/edit/generate": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starreel/mcp",
3
- "version": "0.1.110",
3
+ "version": "0.1.112",
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.110",
5
+ "version": "0.1.112",
6
6
  "websiteUrl": "https://starreel.ai",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "@starreel/mcp",
11
- "version": "0.1.110",
11
+ "version": "0.1.112",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  },