@starreel/mcp 0.1.61 → 0.1.63
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 +12 -3
- package/dist/client.js +2 -2
- package/dist/path-guard.js +3 -1
- package/dist/tools/guide-data.js +88 -4
- package/dist/tools/guide.js +3 -1
- package/dist/tools/produce.js +36 -4
- package/openapi.json +54 -3
- package/package.json +1 -1
- package/server.json +2 -2
package/SKILL.md
CHANGED
|
@@ -72,6 +72,7 @@ announces a condensed version as MCP `instructions` at connect time.
|
|
|
72
72
|
| A finished **shot list** (per-shot seconds / shot size / camera move) | `get_storyboard_table_spec` (the contract: shot-size / camera-move vocabulary, body layout, text-card syntax, a prompt for the external AI) → `check_storyboard_table` (same parser as the import; clear `errors`, read every `warning` — a missing duration, an unrecognised shot size or an empty body all get imported as-is) → `import_storyboard_table` (defaults to `auto_complete`: one background batch fills the professional fields and expands every shot's platform-built base description into full image / video prompts — metered text, tell the customer first; `auto_complete: false` imports only) → `get_autofill_status` until `done` → `review_storyboards` | `rewrite_script` + `generate_storyboards` — strips every production parameter (measured: 8 shots / 36 s became 20 shots / 109 s); importing without the check; reviewing before the completion batch finishes (the token expires when shots change) |
|
|
73
73
|
| **Structured data** — their own tool / spreadsheet export, or an external AI producing JSON (cast + scenes + shots in one go) | `get_bulk_import_spec` (contract + template + worked example + enums, same source as the validator) → `check_bulk_import` (same zod schema; unresolved character / scene references, dead shots and stage directions inside dialogue are surfaced) → `bulk_import_storyboards` (`mode: "replace"` only with the customer's explicit OK; defaults to `auto_complete`: professional fields for every shot, full image / video prompts only for shots that had no `image_prompt` — prompts you supply yourself are kept verbatim — metered text, tell the customer first) → `get_autofill_status` until `done` → `review_storyboards` | converting the JSON to text for `import_storyboard_table`; hand-building shots with `update_shot`; reviewing before the completion batch finishes |
|
|
74
74
|
| Their own portraits / scene / prop / shot images | `upload_image` · `set_character_portrait` · `upload_scene_image` · `upload_prop_sheet` · `upload_shot_frame` | rendering a "fix" elsewhere and uploading it — use `generate_shot_frame` |
|
|
75
|
+
| Their own **footage** for a shot (screen recording, product b-roll, an existing clip) | `upload_shot_footage` → that shot is no longer AI-generated (frames/video skipped, final cut uses the clip as-is, duration written back from the clip); `clear_shot_footage` to revert | pasting an externally AI-generated clip to "fix" a shot — it carries none of this film's identity / style anchors; use `regenerate_shot_video` instead |
|
|
75
76
|
| A scene plate that came out wrong (backdrop, era, light, layout) | `get_scene_prompt` → `update_scene` (`image_prompt`) → `regenerate_scene_image`; already-rendered shot frames still anchor on the old plate, so regenerate those shots too | re-running `generate_scene_images` (it only fills scenes that have **no** plate — it will not touch this one) |
|
|
76
77
|
| A voice sample / a required voice | `clone_voice` → `speak_with_voice` → `set_character_voice` / `assign_voices` | cloning without the rights-holder's consent |
|
|
77
78
|
| A song + lyrics | `create_drama` (`project_type: "mv"`) → `set_mv_lyrics` → `generate_mv_story` → `generate_mv_script` | `rewrite_script` (blocked for MV) |
|
|
@@ -612,9 +613,17 @@ to close") tells the vendor to fit that entire sequence into each 3-second shot.
|
|
|
612
613
|
decide transitions and assemble the cut on your own side. Use this instead of
|
|
613
614
|
`compose_episode` when you want to judge each seam yourself; use
|
|
614
615
|
`compose_episode` when you want the platform's finishing pipeline (pre-flight
|
|
615
|
-
checks, A/V duration parity, loudness mastering).
|
|
616
|
-
`
|
|
617
|
-
|
|
616
|
+
checks, A/V duration parity, loudness mastering). Call
|
|
617
|
+
`get_capabilities_guide(section="local_postproduction")` for the full stage-by-stage
|
|
618
|
+
walkthrough. Three facts that will bite you: **where the voice lives is per-shot —
|
|
619
|
+
read `shots[].voice_track.location`, never the episode-level
|
|
620
|
+
`audio_contract.mode`** (that field is a declaration; fully-offscreen narration
|
|
621
|
+
shots get their voice mixed into the raw clip on our side, so even a `mode="tts"`
|
|
622
|
+
episode contains shots whose voice is already baked in — re-laying it says the line
|
|
623
|
+
twice, skipping it loses it, and `location="missing"` means we know the voice is
|
|
624
|
+
gone and you should regenerate that shot rather than paper over it). Check per line
|
|
625
|
+
with `shots[].spoken_lines[]`: subtitles are per-line but audio is per-shot, so
|
|
626
|
+
**having subtitles never means having sound**; every shot must be cut to
|
|
618
627
|
`trim_head_ms`/`duration_ms` or you splice in frames the platform already QC'd out;
|
|
619
628
|
and all subtitle/dialogue/SFX offsets are relative to **each shot's own trimmed
|
|
620
629
|
start**, not to the final timeline — add whatever overlapping transitions you like
|
package/dist/client.js
CHANGED
|
@@ -18,7 +18,7 @@ import { assertSafeLocalMediaPath } from './path-guard.js'; // v0.1.37 — P0③
|
|
|
18
18
|
const MIME_BY_EXT = {
|
|
19
19
|
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp',
|
|
20
20
|
'.gif': 'image/gif', '.bmp': 'image/bmp',
|
|
21
|
-
'.mp4': 'video/mp4', '.mov': 'video/quicktime',
|
|
21
|
+
'.mp4': 'video/mp4', '.mov': 'video/quicktime', '.webm': 'video/webm', '.m4v': 'video/x-m4v',
|
|
22
22
|
'.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.m4a': 'audio/mp4', '.aac': 'audio/aac',
|
|
23
23
|
'.ogg': 'audio/ogg', '.flac': 'audio/flac',
|
|
24
24
|
};
|
|
@@ -157,7 +157,7 @@ export class StarReelClient {
|
|
|
157
157
|
}
|
|
158
158
|
/**
|
|
159
159
|
* 本地文件 → 我方 COS:presign(门面 /upload-url)→ 直传字节到预签名 URL → 返回 public_url。
|
|
160
|
-
* 字节不经我们的业务服务器,只走 COS。kind: image(默认)/video/audio。
|
|
160
|
+
* 字节不经我们的业务服务器,只走 COS。kind: image(默认)/video(参考视频)/audio/footage(实拍素材整段视频,额度更大)。
|
|
161
161
|
*/
|
|
162
162
|
async uploadLocalFile(filePath, kind = 'image') {
|
|
163
163
|
// v0.1.37 — P0③:媒体扩展名白名单 + 隐藏/系统目录拒绝(realpath 后判,防符号链接绕过)。
|
package/dist/path-guard.js
CHANGED
|
@@ -15,6 +15,8 @@ import { sep } from 'node:path';
|
|
|
15
15
|
export const MEDIA_EXT_BY_KIND = {
|
|
16
16
|
image: new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif']),
|
|
17
17
|
video: new Set(['.mp4', '.mov', '.webm', '.m4v']),
|
|
18
|
+
// 0.1.63 实拍素材镜(整段视频当某镜的成片):格式同 video,额度更大(后端 presign kind=footage)
|
|
19
|
+
footage: new Set(['.mp4', '.mov', '.webm', '.m4v']),
|
|
18
20
|
audio: new Set(['.mp3', '.wav', '.m4a', '.aac', '.flac', '.ogg']),
|
|
19
21
|
};
|
|
20
22
|
const SYSTEM_PREFIXES = ['/etc/', '/private/etc/', '/proc/', '/sys/'];
|
|
@@ -22,7 +24,7 @@ const SYSTEM_PREFIXES = ['/etc/', '/private/etc/', '/proc/', '/sys/'];
|
|
|
22
24
|
export function assertSafeLocalMediaPath(filePath, kind, resolve = realpathSync) {
|
|
23
25
|
const ext = (filePath.match(/\.[a-z0-9]+$/i)?.[0] || '').toLowerCase();
|
|
24
26
|
if (!MEDIA_EXT_BY_KIND[kind].has(ext)) {
|
|
25
|
-
throw new Error(`file_path 只接受${kind === 'image' ? '图片' : kind === 'video' ? '视频' : '音频'}媒体文件`
|
|
27
|
+
throw new Error(`file_path 只接受${kind === 'image' ? '图片' : (kind === 'video' || kind === 'footage') ? '视频' : '音频'}媒体文件`
|
|
26
28
|
+ `(${[...MEDIA_EXT_BY_KIND[kind]].join('/')});收到 "${ext || '无扩展名'}"。`
|
|
27
29
|
+ '不要用本工具上传配置/密钥/文档类文件。');
|
|
28
30
|
}
|
package/dist/tools/guide-data.js
CHANGED
|
@@ -81,9 +81,10 @@ export const ENTRY_POINTS = [
|
|
|
81
81
|
'upload_scene_image',
|
|
82
82
|
'upload_prop_sheet',
|
|
83
83
|
'upload_shot_frame(只用于客户自有真实素材)',
|
|
84
|
+
'upload_shot_footage(客户自有整段视频当某镜成片:录屏/产品实拍/已有片段;登记后该镜不再 AI 出图出视频,终拼原样用,时长按素材回写;清除用 clear_shot_footage)',
|
|
84
85
|
],
|
|
85
86
|
avoid: '要「改某一镜画面」走 `generate_shot_frame`(平台自动带该镜身份锚·场景道具参考·画风锚);别在外部工具画好再 `upload_shot_frame`——外部图没有任何锚,人物/服装/画风必漂。',
|
|
86
|
-
flow: 'upload_image · set_character_portrait · upload_scene_image · upload_prop_sheet · upload_shot_frame(仅客户自有素材;要改画面走 generate_shot_frame)',
|
|
87
|
+
flow: 'upload_image · set_character_portrait · upload_scene_image · upload_prop_sheet · upload_shot_frame(仅客户自有素材;要改画面走 generate_shot_frame) · upload_shot_footage(整段实拍/录屏当某镜成片)',
|
|
87
88
|
},
|
|
88
89
|
{
|
|
89
90
|
customer_has: '自己的声音样本 / 指定音色',
|
|
@@ -141,7 +142,9 @@ export const ENTRY_POINTS = [
|
|
|
141
142
|
{
|
|
142
143
|
customer_has: '想自己剪:要逐镜素材包(裸片 / 对白轨 / 音效 / 配乐 / 字幕)',
|
|
143
144
|
use: ['export_handoff_pack', 'get_handoff_toolchain', 'save_handoff_toolchain'],
|
|
144
|
-
note: '与 `compose_episode`
|
|
145
|
+
note: '与 `compose_episode` 二选一。★人声在哪要**逐镜**看 voice_track.location——整集的 audio_contract.mode 只是声明,' +
|
|
146
|
+
'不是逐镜真值(全画外旁白镜的旁白是平台在完成侧混进裸片的);逐句核对看 spoken_lines,有字幕不等于有声音。' +
|
|
147
|
+
'完整流程见 local_postproduction 段。',
|
|
145
148
|
},
|
|
146
149
|
{
|
|
147
150
|
customer_has: '多语言发行',
|
|
@@ -202,7 +205,7 @@ export const PIPELINE = [
|
|
|
202
205
|
},
|
|
203
206
|
{
|
|
204
207
|
step: '8 出视频',
|
|
205
|
-
tools: ['quote_videos', 'generate_videos', 'get_scene_group_plan', 'generate_scene_groups', 'quote_regenerate_shot_video', 'regenerate_shot_video', 'quote_edit_video_shot', 'edit_video_shot', 'get_edit_capabilities'],
|
|
208
|
+
tools: ['quote_videos', 'generate_videos', 'get_scene_group_plan', 'generate_scene_groups', 'quote_regenerate_shot_video', 'regenerate_shot_video', 'quote_edit_video_shot', 'edit_video_shot', 'upload_shot_footage(实拍素材镜:客户录屏/产品实拍直接当该镜视频,不出视频不扣费)', 'get_edit_capabilities'],
|
|
206
209
|
billing: '报价确认后扣点',
|
|
207
210
|
note: 'video_engine 必须在出视频前定(seedance-2.5 默认 / hailuo-3 降本 / wan3.0 风格化·绝不用于写实真人);切换不回溯已生成镜头。',
|
|
208
211
|
},
|
|
@@ -277,6 +280,77 @@ export const COMMON_REQUESTS = [
|
|
|
277
280
|
{ customer_says: '我想自己剪', do: '`export_handoff_pack` + `get_handoff_toolchain`,不走 `compose_episode`。' },
|
|
278
281
|
{ customer_says: '要配音 / 不要视频原声', do: '`update_project_settings` use_clip_audio=false → `assign_voices` → `generate_tts` → `compose_episode`。' },
|
|
279
282
|
];
|
|
283
|
+
/**
|
|
284
|
+
* v0.9.1371 — 本地后期引导:客户把镜头下载到**自己电脑**上剪。
|
|
285
|
+
* 与 `compose_episode` 二选一——那条是「平台替你拼、带平台级质量闸」,这条是「素材给你、你自己拼」。
|
|
286
|
+
* 不单开一个 guide 工具:入口越多,agent 越要先猜「该调哪个」。
|
|
287
|
+
*/
|
|
288
|
+
export const LOCAL_POSTPRODUCTION = {
|
|
289
|
+
when: '客户要把镜头下载到自己电脑上剪、配乐、烧字幕、优化转场、做字卡、补旁白时走这条;' +
|
|
290
|
+
'想让平台代拼并要平台级质量闸(终拼预检 / 音画等长 / 响度母带)用 `compose_episode`。两条二选一。',
|
|
291
|
+
where_it_runs: '★脚本与 ffmpeg 全部跑在**客户自己的机器**上,平台只发素材 URL 与工具链源码。' +
|
|
292
|
+
'manifest 里是远程 URL,不是服务器上的本地路径;`save_handoff_toolchain` 的 dir 也是客户机器上的绝对路径。' +
|
|
293
|
+
'别把服务器路径当成客户电脑上的路径,也别替客户声称"已经在本地跑完了"——真正执行的是客户那侧。',
|
|
294
|
+
tools: ['export_handoff_pack', 'save_handoff_toolchain', 'get_handoff_toolchain'],
|
|
295
|
+
stages: [
|
|
296
|
+
{
|
|
297
|
+
stage: '1 对需求',
|
|
298
|
+
do: '先问清:哪一集、目标时长、客户有没有自带配乐、字幕样式与排版要求。' +
|
|
299
|
+
'画幅、字幕样式这些项目里已经定过的,用 `get_drama` 读出来直接沿用——客户上一版确认过的偏好别每版重问。',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
stage: '2 查环境',
|
|
303
|
+
do: '确认客户机器上有 ffmpeg(烧字幕要带 libass)、ffprobe、jq、python3,以及够放整集素材的磁盘。',
|
|
304
|
+
gotcha: '缺 libass 时字幕会**静默不烧**——成片看着正常,只是没有字幕。先查再跑,别等出片才发现。',
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
stage: '3 取包',
|
|
308
|
+
do: '`export_handoff_pack` 拿 manifest → `save_handoff_toolchain` 把三个脚本落到客户目录 → 跑 fetch_pack.py 下载素材并把内联字幕落成 SRT。',
|
|
309
|
+
gotcha: '素材 URL 到 expires_at 就失效;过期重新调 `export_handoff_pack`,别拿旧 manifest 硬跑。',
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
stage: '4 核声音',
|
|
313
|
+
do: '★动剪辑前先核对声音。**逐镜**读 voice_track.location 决定这镜该不该铺对白轨、能不能叠;' +
|
|
314
|
+
'**逐行**读 spoken_lines 逐句核对。location=unknown 的镜必须实际试听裸片。',
|
|
315
|
+
gotcha: '**有字幕不等于有声音**:字幕是逐句的、音频是逐镜的,粒度本来就对不上,' +
|
|
316
|
+
'别按「这镜有字幕」推定每句都有人念。voice_status=caption_no_voice 的行是字卡,本来就没有配音,不是缺失。',
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
stage: '5 判接缝',
|
|
320
|
+
do: '逐个接缝看前后画面,结合动作、景别、视线与声音选切点。scene_boundary="start" 是换场(适合给转场),' +
|
|
321
|
+
'"continue" 是同场景(平台默认硬切)。',
|
|
322
|
+
gotcha: '同场景逐镜叠化是"幻灯片拼凑感"的主因;缺失的交接动作**拉长叠化也补不出来**,该重生成就重生成。',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
stage: '6 装配',
|
|
326
|
+
do: '先做代表性样段(字卡、混音各挑一两处)给客户看,再整集出片:compile_timeline.py 展开时间轴 → assemble.sh 装配。',
|
|
327
|
+
gotcha: '裁剪与重叠转场都会移动入点——字幕/对白/音效的绝对时间一律交给 compile_timeline.py 算,**别手算累加**。' +
|
|
328
|
+
'只改声音时保留视频码流(-c:v copy),别整片重编码。',
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
stage: '7 验收',
|
|
332
|
+
do: '看**真实成片**:画面、字幕位置、旁白完整性、每个接缝、峰值与音画同步。交付可播放文件 + 版本 + 检查结果。',
|
|
333
|
+
gotcha: '没做的试听或视觉检查要**明说没做**,不许默认通过。',
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
voice_rules: [
|
|
337
|
+
'location=separate_file:对白在 dialogue_audio,必须自己铺轨,不铺这镜就没台词。',
|
|
338
|
+
'location=baked_in_clip:人声已在裸片音轨里,**再叠一遍是同一句说两遍**(全画外旁白镜最常见)。',
|
|
339
|
+
'location=missing:平台侧确认缺失。正解是回平台 `regenerate_shot_video` 重生成,或 `generate_tts` 补这句,' +
|
|
340
|
+
'音色复用已授权的克隆音(`list_voices` / `set_character_voice`),别在本地硬凑,也别拿转场掩盖。',
|
|
341
|
+
'人声与配乐分开控制:客户说"这句小一点"是调那句的对白轨增益,不是压整条 BGM;' +
|
|
342
|
+
'整体响度达标**不代表**每句都听得清。',
|
|
343
|
+
'clip.probed_has_audio_stream=false 表示平台实测这条裸片连音轨流都没有;反过来不成立——' +
|
|
344
|
+
'有音轨不代表有人声,全旁白镜的环境音本来就是要求厂商出的。',
|
|
345
|
+
],
|
|
346
|
+
not_verified_until: [
|
|
347
|
+
'「素材下载完成」不是验收。',
|
|
348
|
+
'「脚本退出码 0 / 执行成功」不是验收——ffmpeg 跑完不等于成片对。',
|
|
349
|
+
'「自动转写通过」不等于试听过,别拿它冒充人工听过。',
|
|
350
|
+
'只有看过真实成片(画面 / 字幕位置 / 旁白完整 / 接缝 / 音画同步)才算验收完成;' +
|
|
351
|
+
'平台侧成片用 `get_final_cut` 取。',
|
|
352
|
+
],
|
|
353
|
+
};
|
|
280
354
|
export const HOW_TO_READ = '先按 entry_points 判客户手上的材料该走哪条通道(这是最常被跳过的一步),再按 pipeline 顺序推进、每道 review_gates 必过;' +
|
|
281
355
|
'收费步按 billing.quote_flow 报价确认;遇到质量投诉按 qa_tools 的 symptom 选检测工具先定病因。';
|
|
282
356
|
export function buildGuide() {
|
|
@@ -290,9 +364,10 @@ export function buildGuide() {
|
|
|
290
364
|
optional_boosts: OPTIONAL_BOOSTS,
|
|
291
365
|
billing: BILLING,
|
|
292
366
|
common_requests: COMMON_REQUESTS,
|
|
367
|
+
local_postproduction: LOCAL_POSTPRODUCTION,
|
|
293
368
|
};
|
|
294
369
|
}
|
|
295
|
-
export const GUIDE_SECTIONS = ['entry_points', 'pipeline', 'review_gates', 'qa_tools', 'optional_boosts', 'billing', 'common_requests'];
|
|
370
|
+
export const GUIDE_SECTIONS = ['entry_points', 'pipeline', 'review_gates', 'qa_tools', 'optional_boosts', 'billing', 'common_requests', 'local_postproduction'];
|
|
296
371
|
const head = (s) => /^[a-z][a-z0-9_]*/.exec(s.trim())?.[0] ?? null;
|
|
297
372
|
const inProse = (s) => [...(s ?? '').matchAll(/`([a-z][a-z0-9_]*)`/g)].map((m) => m[1]);
|
|
298
373
|
/** 引导里引用到的全部工具名(去重)——哨兵测试用:每一个都必须真的注册了。 */
|
|
@@ -326,6 +401,15 @@ export function referencedTools() {
|
|
|
326
401
|
BILLING.tools.forEach(add);
|
|
327
402
|
for (const c of COMMON_REQUESTS)
|
|
328
403
|
inProse(c.do).forEach(add);
|
|
404
|
+
// v0.9.1371 — 本地后期段同样纳入哨兵:漏了这几行,这段引导就能悄悄指向不存在的工具。
|
|
405
|
+
LOCAL_POSTPRODUCTION.tools.forEach(add);
|
|
406
|
+
[LOCAL_POSTPRODUCTION.when, LOCAL_POSTPRODUCTION.where_it_runs].forEach((s) => inProse(s).forEach(add));
|
|
407
|
+
for (const s of LOCAL_POSTPRODUCTION.stages) {
|
|
408
|
+
inProse(s.do).forEach(add);
|
|
409
|
+
inProse(s.gotcha).forEach(add);
|
|
410
|
+
}
|
|
411
|
+
for (const r of [...LOCAL_POSTPRODUCTION.voice_rules, ...LOCAL_POSTPRODUCTION.not_verified_until])
|
|
412
|
+
inProse(r).forEach(add);
|
|
329
413
|
return [...out].sort();
|
|
330
414
|
}
|
|
331
415
|
/**
|
package/dist/tools/guide.js
CHANGED
|
@@ -10,7 +10,9 @@ export function registerGuideTools(server) {
|
|
|
10
10
|
'entry_points(客户手上是小说/成熟剧本/想去外部 AI 改写/成品分镜表/自有素材/声音样本/歌曲/产品/已有成片要改/想自己剪/多语言 → 各走哪些工具、别走哪条路)、' +
|
|
11
11
|
'pipeline(10 步产线每步的工具、免费还是收费、哪道审查闸)、review_gates(三道免费硬闸规则)、' +
|
|
12
12
|
'qa_tools(按客户描述的症状选检测工具与修法)、optional_boosts(可选增强及何时做)、billing(报价确认与免费族)、' +
|
|
13
|
-
'common_requests(客户常见原话 → 该做什么)
|
|
13
|
+
'common_requests(客户常见原话 → 该做什么)、' +
|
|
14
|
+
'local_postproduction(★客户要把镜头下载到**自己电脑**上剪 / 配乐 / 烧字幕 / 优化转场 / 补旁白时的完整流程:' +
|
|
15
|
+
'阶段顺序、执行位置、逐镜逐句怎么核对声音、什么才算验收完成)。传 section 只取一段。' +
|
|
14
16
|
'★工具描述回答"这个工具做什么",本工具回答"什么情况下该用哪个"——客户交来的是成品分镜表却被 set_script→rewrite_script 改写成散文,就是没先看这张表。', {
|
|
15
17
|
section: z.enum(GUIDE_SECTIONS).optional().describe('只取某一段;不传返回全部'),
|
|
16
18
|
}, async ({ section }) => {
|
package/dist/tools/produce.js
CHANGED
|
@@ -239,7 +239,7 @@ export function registerProduceTools(server, client) {
|
|
|
239
239
|
ethnicity_note: z.string().optional().describe("ethnicity='custom' 时的自由文本(如 北欧/波斯);其余取值忽略"),
|
|
240
240
|
project_type: z.enum(['drama', 'ad', 'mv', 'brand_film']).optional()
|
|
241
241
|
.describe('项目类型(默认 drama)。ad=广告(改写走 ad_script_rewriter);mv=音乐(走歌词→故事→剧本子流程);brand_film=品牌微电影(默认16:9)'),
|
|
242
|
-
rewrite_mode: z.enum(['standard', 'director']).optional().describe('AI
|
|
242
|
+
rewrite_mode: z.enum(['standard', 'director']).optional().describe('AI改写深度(★默认 director):director=只做格式规范化与最小可拍性修正,不擅自补台词补动机;standard=按商业短剧剧作律优化,会主动加戏'),
|
|
243
243
|
rewrite_pipeline: z.enum(['auto', 'two_pass', 'single_forced']).optional().describe('改写流水线:auto(默认,按原稿形态智能路由——剧本形态走两步保真,小说/大纲走创作改写)/two_pass(强制两步保真,客户自带成熟剧本必选)/single_forced(强制单步创作)'),
|
|
244
244
|
fidelity_enforce: z.number().int().min(0).max(1).optional().describe('1=改写保真硬闸:丢台词/丢人物/丢动作节拍直接拒收重做(客户要求逐句保留时开)'),
|
|
245
245
|
director_style: z.string().optional().describe('导演风格包 key'),
|
|
@@ -609,7 +609,7 @@ export function registerProduceTools(server, client) {
|
|
|
609
609
|
director_style: z.string().optional(),
|
|
610
610
|
theme_statement: z.string().optional().describe('一句话主题'),
|
|
611
611
|
subtitle_preset: z.string().optional(),
|
|
612
|
-
scene_group_mode: z.boolean().optional().describe('长镜模式(
|
|
612
|
+
scene_group_mode: z.boolean().optional().describe('长镜模式(连续动作/电影级长镜·★所有类型建剧默认开):false=改回逐镜独立生成再拼接'),
|
|
613
613
|
// 广告专属
|
|
614
614
|
cta_text: z.string().optional(),
|
|
615
615
|
target_duration_s: z.number().optional(),
|
|
@@ -687,6 +687,18 @@ export function registerProduceTools(server, client) {
|
|
|
687
687
|
const image_url = await client.uploadLocalFile(file_path, 'image');
|
|
688
688
|
return jsonResult(await client.producePost(`/storyboards/${storyboard_id}/frame`, { image_url, frame_type: frame_type ?? 'first_frame' }));
|
|
689
689
|
});
|
|
690
|
+
server.tool('upload_shot_footage', '用**客户自有的整段视频**(录屏 / 产品实拍 / 第三方成片)直接当某镜的视频——实拍素材镜。' +
|
|
691
|
+
'登记后该镜不再 AI 出图/出视频(generate_videos 会跳过它,单镜重生会被拒),终拼原样使用,镜头时长按素材真实长度回写,' +
|
|
692
|
+
'首/尾帧从素材抽帧供帧链与预览用。免费。适用:宣传片里的到账界面录屏、后台大屏实录、产品实拍、客户已有的成片片段。' +
|
|
693
|
+
'⚠️ 别用它把外部 AI 生成的视频贴进来"改画面"——那不带本片身份锚/画风锚,人物·画风必漂;要改画面走 regenerate_shot_video。' +
|
|
694
|
+
'要换回 AI 生成请先 clear_shot_footage。', {
|
|
695
|
+
storyboard_id: z.number().int().positive(),
|
|
696
|
+
file_path: z.string().describe('本地视频路径(mp4/mov/webm/m4v,≤300MB)'),
|
|
697
|
+
}, async ({ storyboard_id, file_path }) => {
|
|
698
|
+
const video_url = await client.uploadLocalFile(file_path, 'footage');
|
|
699
|
+
return jsonResult(await client.producePost(`/storyboards/${storyboard_id}/footage`, { video_url }));
|
|
700
|
+
});
|
|
701
|
+
server.tool('clear_shot_footage', '清除某镜的实拍素材(video_url 置空、来源标记清掉),让这一镜恢复可 AI 生成视频。免费。', { storyboard_id: z.number().int().positive() }, async ({ storyboard_id }) => jsonResult(await client.produceDelete(`/storyboards/${storyboard_id}/footage`)));
|
|
690
702
|
server.tool('upload_scene_image', '用客户自有图片作为某场景的参考图。自动上传+登记。免费。', { scene_id: z.number().int().positive(), file_path: z.string().describe('本地场景图路径') }, async ({ scene_id, file_path }) => {
|
|
691
703
|
const image_url = await client.uploadLocalFile(file_path, 'image');
|
|
692
704
|
return jsonResult(await client.producePost(`/scenes/${scene_id}/image`, { image_url }));
|
|
@@ -1029,8 +1041,14 @@ export function registerProduceTools(server, client) {
|
|
|
1029
1041
|
'写一份 plan.json;④ 用 get_handoff_toolchain 拿到 compile_timeline.py 展开时间轴、' +
|
|
1030
1042
|
'assemble.sh 装配出成片。工具链已经把「加了重叠转场之后字幕/对白/音效怎么跟着位移」算好了。\n' +
|
|
1031
1043
|
'\n【三个不看就会翻车的事实】\n' +
|
|
1032
|
-
'① audio_contract.mode
|
|
1033
|
-
'
|
|
1044
|
+
'① **人声在哪要逐镜读 `shots[].voice_track.location`,不能读整集的 audio_contract.mode**——' +
|
|
1045
|
+
'后者是整剧级声明,而全画外旁白镜的旁白是平台在完成侧混进裸片的,同一集里逐镜可以不同。' +
|
|
1046
|
+
'四种取值各有各的处置:`separate_file`=对白在 dialogue_audio,不铺这镜就没台词;' +
|
|
1047
|
+
'`baked_in_clip`=已在裸片音轨里,再叠一遍会双声;' +
|
|
1048
|
+
'`missing`=**平台侧确认缺失**(旁白补偿失败),字幕还在但声音哪都没有——回平台重生成该镜,' +
|
|
1049
|
+
'拉长转场掩盖不了;`unknown`=有台词却查不到来源,**必须试听裸片**再决定。' +
|
|
1050
|
+
'★有字幕从来不等于有声音:字幕是逐句的,音频是逐镜的。`conflicts_with_contract=true` 的镜' +
|
|
1051
|
+
'以实测为准,notes 里已按镜号点名。\n' +
|
|
1034
1052
|
'② 每镜必须按 trim_head_ms / duration_ms 裁剪再用;直接拼整条裸片会把平台已经 QC 掉的' +
|
|
1035
1053
|
'首尾形变帧一起拼进去。\n' +
|
|
1036
1054
|
'③ 字幕 cue、dialogue_audio.offset_ms、sfx[].offset_ms 的基准都是「该镜 trim 之后的第 0 毫秒」,' +
|
|
@@ -1042,6 +1060,15 @@ export function registerProduceTools(server, client) {
|
|
|
1042
1060
|
assembly_guide: {
|
|
1043
1061
|
step_1_download: '按 shots[].clip.url / dialogue_audio.url / sfx[].url / bgm[].url 下载素材。' +
|
|
1044
1062
|
'URL 到 expires_at 失效,过期重新调本工具。',
|
|
1063
|
+
step_1b_verify_voice: '★下载完先核对声音,再动剪辑。**逐镜**读 voice_track.location 决定这镜的人声该不该铺、' +
|
|
1064
|
+
'能不能叠;location="unknown" 的镜**必须实际试听裸片**,不能凭字幕存在推定有声音;' +
|
|
1065
|
+
'location="missing" 的镜先回平台重生成,别开始剪。' +
|
|
1066
|
+
'clip.probed_has_audio_stream=false 表示平台实测这条裸片连音轨流都没有——' +
|
|
1067
|
+
'注意反过来不成立:有音轨不代表有人声,全旁白镜的环境音本来就是要求厂商出的。\n' +
|
|
1068
|
+
'★再**逐行**读 shots[].spoken_lines[]:字幕是逐句的、音频是逐镜的,' +
|
|
1069
|
+
'「这镜有字幕」推不出「每句都有声音」。每行带 speaker / text / voice_status,' +
|
|
1070
|
+
'把它当逐句核对清单用。voice_status="caption_no_voice" 的行是字卡文本,' +
|
|
1071
|
+
'**本来就没有配音**(平台侧也是当独立字幕呈现的)——不是缺失,别去补一段不该存在的配音。',
|
|
1045
1072
|
step_2_decide_transitions: '自己分析画面决定每个接缝的转场。scene_boundary="start" 是换场(适合给转场),' +
|
|
1046
1073
|
'"continue" 是同场景(平台默认硬切——同场景逐镜叠化是"幻灯片拼凑感"的主因)。' +
|
|
1047
1074
|
'transition_hint 是平台建议,你可以覆盖。',
|
|
@@ -1054,6 +1081,11 @@ export function registerProduceTools(server, client) {
|
|
|
1054
1081
|
' python3 compile_timeline.py ./pack --transitions plan.json\n' +
|
|
1055
1082
|
' ./assemble.sh ./pack out.mp4 plan.json',
|
|
1056
1083
|
gotchas: [
|
|
1084
|
+
'voice_track.location="baked_in_clip" 的镜**不要**再铺 dialogue_audio,那一镜的人声已经在裸片音轨里,' +
|
|
1085
|
+
'叠上去是同一句话说两遍(全画外旁白镜最常见)。',
|
|
1086
|
+
'subtitle.cues 的文本已剥掉说话人前缀与画外标记(与平台烧字幕同一条清洗),直接烧即可,' +
|
|
1087
|
+
'别再自己解析「名:」——但 cues 的**切分**是按语音停顿来的,与 spoken_lines 的行不是一一对应,' +
|
|
1088
|
+
'逐句核对声音请用 spoken_lines,不要按 cue 条数推定句数。',
|
|
1057
1089
|
'clip.duration_source="authored" 的镜是 probe 失败退回声明时长的,请自行 ffprobe 校正,否则拼接有累积误差。',
|
|
1058
1090
|
'render_target.color_lut 非 null 时,裸片是**未调色**的:必须施加随包的 haldclut 查找表,' +
|
|
1059
1091
|
'否则你的成片与平台成片有色差。fetch_pack.py 会下载它、assemble.sh 会自动施加。',
|
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.
|
|
6
|
-
"description": "Turn a script into a finished, downloadable short-drama episode over REST.\n\nPipeline: script → AI rewrite → cast/scenes/props extraction → portraits & sheets → storyboards → 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). 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.
|
|
5
|
+
"version": "0.1.63",
|
|
6
|
+
"description": "Turn a script into a finished, downloadable short-drama episode over REST.\n\nPipeline: script → AI rewrite → cast/scenes/props extraction → portraits & sheets → storyboards → 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). 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.63 tool surface (operationIds match MCP tool names 1:1)."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
|
9
9
|
{
|
|
@@ -3358,7 +3358,7 @@
|
|
|
3358
3358
|
"get": {
|
|
3359
3359
|
"operationId": "export_handoff_pack",
|
|
3360
3360
|
"summary": "导出本集「素材交接包」清单:逐镜裸片 + 对白音轨 + 音效 + 配乐 + 字幕的可下载 URL,交给你在**自己那边**完成转场决策、拼接、混音、烧字幕——平台不参与终拼",
|
|
3361
|
-
"description": "导出本集「素材交接包」清单:逐镜裸片 + 对白音轨 + 音效 + 配乐 + 字幕的可下载 URL,交给你在**自己那边**完成转场决策、拼接、混音、烧字幕——平台不参与终拼。免费,零扣费。\n\n【推荐流程】① 调本工具拿 manifest;② 按 clips[].url 把裸片下载到本地;③ 你自己看片判断每个接缝该用什么转场(manifest 给了 scene_boundary 场景边界作判据),写一份 plan.json;④ 用 get_handoff_toolchain 拿到 compile_timeline.py 展开时间轴、assemble.sh 装配出成片。工具链已经把「加了重叠转场之后字幕/对白/音效怎么跟着位移」算好了。\n\n【三个不看就会翻车的事实】\n① audio_contract.mode
|
|
3361
|
+
"description": "导出本集「素材交接包」清单:逐镜裸片 + 对白音轨 + 音效 + 配乐 + 字幕的可下载 URL,交给你在**自己那边**完成转场决策、拼接、混音、烧字幕——平台不参与终拼。免费,零扣费。\n\n【推荐流程】① 调本工具拿 manifest;② 按 clips[].url 把裸片下载到本地;③ 你自己看片判断每个接缝该用什么转场(manifest 给了 scene_boundary 场景边界作判据),写一份 plan.json;④ 用 get_handoff_toolchain 拿到 compile_timeline.py 展开时间轴、assemble.sh 装配出成片。工具链已经把「加了重叠转场之后字幕/对白/音效怎么跟着位移」算好了。\n\n【三个不看就会翻车的事实】\n① **人声在哪要逐镜读 `shots[].voice_track.location`,不能读整集的 audio_contract.mode**——后者是整剧级声明,而全画外旁白镜的旁白是平台在完成侧混进裸片的,同一集里逐镜可以不同。四种取值各有各的处置:`separate_file`=对白在 dialogue_audio,不铺这镜就没台词;`baked_in_clip`=已在裸片音轨里,再叠一遍会双声;`missing`=**平台侧确认缺失**(旁白补偿失败),字幕还在但声音哪都没有——回平台重生成该镜,拉长转场掩盖不了;`unknown`=有台词却查不到来源,**必须试听裸片**再决定。★有字幕从来不等于有声音:字幕是逐句的,音频是逐镜的。`conflicts_with_contract=true` 的镜以实测为准,notes 里已按镜号点名。\n② 每镜必须按 trim_head_ms / duration_ms 裁剪再用;直接拼整条裸片会把平台已经 QC 掉的首尾形变帧一起拼进去。\n③ 字幕 cue、dialogue_audio.offset_ms、sfx[].offset_ms 的基准都是「该镜 trim 之后的第 0 毫秒」,不是成片绝对时间。你加多少重叠转场都不用改它们——交给 compile_timeline.py 展开,别手算累加。\n\n想让平台代拼、要平台级质量闸(终拼预检/音画等长/响度母带),改用 compose_episode。",
|
|
3362
3362
|
"tags": [
|
|
3363
3363
|
"episodes"
|
|
3364
3364
|
],
|
|
@@ -6612,6 +6612,53 @@
|
|
|
6612
6612
|
}
|
|
6613
6613
|
}
|
|
6614
6614
|
},
|
|
6615
|
+
"/storyboards/{storyboard_id}/footage": {
|
|
6616
|
+
"delete": {
|
|
6617
|
+
"operationId": "clear_shot_footage",
|
|
6618
|
+
"summary": "清除某镜的实拍素材(video_url 置空、来源标记清掉),让这一镜恢复可 AI 生成视频",
|
|
6619
|
+
"description": "清除某镜的实拍素材(video_url 置空、来源标记清掉),让这一镜恢复可 AI 生成视频。免费。",
|
|
6620
|
+
"tags": [
|
|
6621
|
+
"storyboards"
|
|
6622
|
+
],
|
|
6623
|
+
"parameters": [
|
|
6624
|
+
{
|
|
6625
|
+
"name": "storyboard_id",
|
|
6626
|
+
"in": "path",
|
|
6627
|
+
"required": true,
|
|
6628
|
+
"schema": {
|
|
6629
|
+
"type": "integer",
|
|
6630
|
+
"exclusiveMinimum": 0
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
],
|
|
6634
|
+
"responses": {
|
|
6635
|
+
"200": {
|
|
6636
|
+
"description": "StarReel envelope",
|
|
6637
|
+
"content": {
|
|
6638
|
+
"application/json": {
|
|
6639
|
+
"schema": {
|
|
6640
|
+
"type": "object",
|
|
6641
|
+
"properties": {
|
|
6642
|
+
"code": {
|
|
6643
|
+
"type": "integer"
|
|
6644
|
+
},
|
|
6645
|
+
"message": {
|
|
6646
|
+
"type": "string"
|
|
6647
|
+
},
|
|
6648
|
+
"data": {
|
|
6649
|
+
"description": "Operation result payload"
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
},
|
|
6656
|
+
"402": {
|
|
6657
|
+
"description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
},
|
|
6615
6662
|
"/storyboards/{storyboard_id}/frame/generate": {
|
|
6616
6663
|
"post": {
|
|
6617
6664
|
"operationId": "generate_shot_frame",
|
|
@@ -7536,6 +7583,10 @@
|
|
|
7536
7583
|
"tool": "upload_shot_frame",
|
|
7537
7584
|
"reason": "unmappable: local file upload flow"
|
|
7538
7585
|
},
|
|
7586
|
+
{
|
|
7587
|
+
"tool": "upload_shot_footage",
|
|
7588
|
+
"reason": "unmappable: local file upload flow"
|
|
7589
|
+
},
|
|
7539
7590
|
{
|
|
7540
7591
|
"tool": "upload_scene_image",
|
|
7541
7592
|
"reason": "unmappable: local file upload flow"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starreel/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
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.
|
|
5
|
+
"version": "0.1.63",
|
|
6
6
|
"websiteUrl": "https://starreel.ai",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@starreel/mcp",
|
|
11
|
-
"version": "0.1.
|
|
11
|
+
"version": "0.1.63",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
},
|