@starreel/mcp 0.1.211 → 0.1.212
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 +7 -2
- package/dist/i18n/tools.en.json +3 -1
- package/dist/i18n/tools.zh-CN.json +3 -1
- package/dist/tools/guide-data.js +3 -1
- package/dist/tools/produce.js +6 -0
- package/openapi.json +106 -3
- package/package.json +1 -1
- package/server.json +2 -2
package/SKILL.md
CHANGED
|
@@ -107,7 +107,7 @@ announces a condensed version as MCP `instructions` at connect time.
|
|
|
107
107
|
| Customer explicitly accepted a shot's video, then its frames / scene / character changed (the platform now flags that video stale) | `lock_shot_video` (only after the customer confirms; locks the **current** video plus the inputs it was made from — frames, prompt, reference list). While locked: `review_videos` stops reporting `video_stale` for it (a `video_locked_frames_changed` warning instead), `get_asset_versions` shows `acceptance_locked`, native-voice `stale_shots` skip it, and every call that would replace it (`regenerate_shot_video`, `edit_video_shot`, repaint / reskin, footage upload / clear, a whole-episode `generate_videos` that would re-run its scene group) is refused with **409 VIDEO_LOCKED**. To replace it later, get the customer's OK and `unlock_shot_video` first. If the video is swapped by another path the lock lapses on its own (`video_lock.broken`) | regenerating an accepted shot just because it shows as stale; locking on your own judgement or because generation finished |
|
|
108
108
|
| Wants an old image **permanently** deleted from storage (not just unlinked) | `preview_delete_asset` → show the customer exactly what it says (blocking references, history that will break, copies) → only after an explicit "confirm delete" → `delete_asset` with the `confirm_token`. Irreversible; only the drama owner; only files with trusted provenance (generated for this drama, or uploaded through the platform with a grant record) | deleting without showing the preview; retrying automatically; treating `list_unlinked_assets` as proof the file is unused |
|
|
109
109
|
| Cross-era drama (scenes in different periods) — checking props fit their period | `get_props` → read each prop's `era_audit` (`form_consistent`, `conflicting_scopes`, `violations`); `audit_prop_era` to re-check after a new sheet. On a conflict: `update_prop` with a precise `era_lock` (dynasty + physical form) → `generate_prop_sheet`, or bind a different prop to those shots. `run_precheck` also lists shots whose bound prop conflicts (`prop-era-conflict`) | trusting the prop's name or description alone; regenerating shot frames before the prop sheet is fixed |
|
|
110
|
-
| Wants to assemble the cut themselves | `export_handoff_pack` → `get_handoff_toolchain` → **`register_external_delivery`** when the cut is final | `compose_episode` (pick one); delivering a cut the platform has no record of |
|
|
110
|
+
| Wants to assemble the cut themselves | `export_handoff_pack` → `build_av_timing_contract` → `get_handoff_toolchain` → **`register_external_delivery`** when the cut is final | `compose_episode` (pick one); delivering a cut the platform has no record of |
|
|
111
111
|
| A multi-language release | `translate_subtitles` · `subtitle_secondary_lang` in project settings | — |
|
|
112
112
|
|
|
113
113
|
### Customer-supplied images
|
|
@@ -1547,7 +1547,12 @@ to close") tells the vendor to fit that entire sequence into each 3-second shot.
|
|
|
1547
1547
|
`compose_episode` when you want the platform's finishing pipeline (pre-flight
|
|
1548
1548
|
checks, A/V duration parity, loudness mastering). Call
|
|
1549
1549
|
`get_capabilities_guide(section="local_postproduction")` for the full stage-by-stage
|
|
1550
|
-
walkthrough.
|
|
1550
|
+
walkthrough. Before you assemble, run `build_av_timing_contract` →
|
|
1551
|
+
`get_av_timing_contract` (free): per shot the measured source length, the
|
|
1552
|
+
platform's in/out points, transition overlap and freeze padding, and whether the
|
|
1553
|
+
voice is already mixed into the clip (`voice.in_final_cut=mixed_into_video` —
|
|
1554
|
+
never dub it again) or on a separate track; check your total against it.
|
|
1555
|
+
Three facts that will bite you: **where the voice lives is per-shot —
|
|
1551
1556
|
read `shots[].voice_track.location`, never the episode-level
|
|
1552
1557
|
`audio_contract.mode`** (that field is a declaration; fully-offscreen narration
|
|
1553
1558
|
shots get their voice mixed into the raw clip on our side, so even a `mode="tts"`
|
package/dist/i18n/tools.en.json
CHANGED
|
@@ -205,6 +205,8 @@
|
|
|
205
205
|
"audit_seams": "Audit edit continuity of current bilateral frames; checks only, does not regenerate or replace assets. Default dry_run=true returns the scope to audit and is free. Insert details only check object identity and state, not composition copying. Existing current valid conclusions are not re-audited. Execution is billed by actual text token usage; estimated_points=null means the amount is not quoted, not free. First show the plan and billing method; only pass dry_run=false and confirm_usage_billing=true after explicit client acceptance. After execution, use get_storyboards seam_review to inspect actual results; attempted does not mean passed.",
|
|
206
206
|
"preview_seams": "★Run this first when checking transition/seam problems instead of repeatedly exporting the whole episode. Free, zero charge (pure ffmpeg; no vendor calls and no on-the-fly TTS synthesis). Renders one short clip for every seam between adjacent shots in the episode: the last 1 second before the outgoing shot's out point + the first 1 second after the incoming shot's in point (for transitions longer than 0.5 s each side takes 2× the transition), applying the current trim windows and transition settings with the same timeline-building and rendering logic as the final compose (only the canvas is scaled down; no BGM and no subtitles). Also returns an edge-frame comparison image for both sides, plus the existing audit_seams conclusion. Async: this tool only starts the job; poll get_seam_previews until status=done. seams is optional: re-render only the listed seam indexes (use it to re-check after changing a trim/transition). ★Decide the fix after watching: if the action does not connect, run recommend_trim_window → trim_shot; if the transition is wrong, change the transition. Patch-up tricks such as crossfade ghosting, zoom-in inserts or occluding overlays often introduce new problems themselves — after changing anything, run this tool again on that seam.",
|
|
207
207
|
"get_seam_previews": "Check progress and results of preview_seams. Free. While status=running, read progress.done/total; when status=done, each entry of result.seams[] gives: from_shot_id/to_shot_id, transition (effective transition), left/right (in/out points on the source video, src_in_ms/src_out_ms, trim already applied), preview_url (the short clip), compare_image_url (left = last frame of the outgoing shot before the transition starts, right = first frame of the incoming shot after the transition ends), audit (audit_seams conclusion; not_enabled = not enabled for this drama). A non-empty result.synth_skipped_shot_ids means those shots have no synthesized voice-over yet and the preview treats them as silent. A single-shot episode returns seam_count=0.",
|
|
208
|
+
"build_av_timing_contract": "★Run this first when an external agent assembles the cut itself, or to check why the final cut has the length it has — do not assemble by the seconds written in the storyboard. Free, zero charge (pure ffprobe + local activity detection; no vendor calls and no on-the-fly TTS synthesis). Returns the episode's A/V timing contract. Per shot: source.duration_ms (ffprobe-measured source length, not the storyboard seconds) and excess_over_declared_ms (extra tail the vendor delivered); trim.in_ms/out_ms (source in/out points used in the cut, auto head-trim and trim_shot included; untrimmed shots have full_length=true); action (effective action window and recommended trim window, reusing recommend_trim_window's activity detection); tail (freeze_pad_ms = frames the renderer clones to pad the shot, low_motion_in_cut_ms = low-motion tail still in the cut after the action ends); voice.in_final_cut (mixed_into_video = the voice is already mixed into the clip's audio, never dub it again; separate_track = separate dialogue track, dialogue_track gives its timeline interval; pending_synthesis/missing/unknown/double_voice — see instruction); timeline (start/end on the final-cut timeline, transition_in overlap, transition_out_overlap_ms, exclusive interval). The episode summary gives total duration, total transition overlap, per-track spans and tracks that run past the end of the picture. planned uses the same timeline build as compose_episode; final_cut reads the timeline the last final cut actually rendered and measures the final file (duration_check); drift_vs_planned lists shots that would change if you re-composed now. Async: this tool only starts the job; poll get_av_timing_contract until status=done. include_activity=false skips activity detection (faster, but no action window or low-motion tail).",
|
|
209
|
+
"get_av_timing_contract": "Check progress and results of build_av_timing_contract. Free. While status=running read progress (stage=build_timeline/activity/probe); when status=done, result.planned is the per-shot contract a re-compose would produce now, result.final_cut is the last final cut (contract = derived from the stored timeline, probed = measured final file, duration_check.matches = total duration agrees within 100 ms, drift_vs_planned = shots that differ; when timeline_association≠verified the stored timeline is no longer the one the final cut rendered, so contract is not provided). ★When assembling yourself, place shots by shots[].timeline and cut sources by trim.in_ms/out_ms; for voice.in_final_cut=mixed_into_video use the clip's own audio and never lay another voice on top. A non-empty result.synth_skipped_shot_ids means those shots have no synthesized voice yet (synthesized and billed at compose time), so their dialogue-track intervals are missing.",
|
|
208
210
|
"chain_frames": "Whole-episode first/last-frame relay chain: make each shot's first frame inherit the previous shot's last frame so inter-shot visuals are continuous (smoother than generating each shot independently). ★When to call: after first frames are done and any last frames required by tail_frame_plan are also filled, before review_frames / generate_videos. The platform only auto-runs this once when batch frame generation on the official site finishes; episodes generated shot by shot (generate_frames / generate_shot_frame) often have never been chained — get_storyboards seam_state=unchained means this kind of seam.\n★First use dry_run:true (free, does not write DB or generate images): returns planned_copy / planned_reframe / estimated_points / needs_last_frame. copy = directly replace this shot's first frame with the previous shot's last frame (free, but this shot's shot size becomes the same as the previous shot); reframe = use the previous shot's last frame as an anchor to regenerate this shot's first frame according to this shot's visual description (one image per seam, preserves shot size). Tell the client the plan and estimated_points; after confirmation, call again **without dry_run** to execute (postpaid by usage).\n★The platform decides which seams to chain: intentional cuts (scene/time jump/costume change/insert shot), shots with existing video, and already-chained seams are skipped — “already chained” = the first frame is the previous shot's last frame, or reframe is chained to the previous shot's **current** last frame, so repeated execution will not charge again. If the previous shot's last frame is later regenerated, that seam will reappear in the plan; that seam is indeed broken. To fill individual seams, use chain_shot_from_prev / chain_shot_from. Shots whose first frame is changed by execution and already have video will be marked pending regeneration. ★Execution only dispatches reframe and does not wait for image generation: use get_jobs to view first_frame generation rows for these shots; wait until all pending/processing are finished (for failed, check error_msg; for a single seam use chain_shot_from to reconnect; while the old first frame still exists, get_storyboards frame_status remains ready and failure is not visible), then use get_storyboards to verify seam_state and review_frames — auditing too early will audit the old first frame, and the token will be invalidated later.",
|
|
209
211
|
"chain_shot_from_prev": "(Free) Directly set the previous shot's last frame as this shot's first frame: visuals connect 100%, zero cost, no image generation. ★Use when: the two shots are a continuation from the same camera position and same shot size (seam_state=unchained/broken and this shot should already share the previous shot's composition). ★Do not use for seams with shot-size changes (wide → close-up, etc.): it will overwrite this shot's shot-size design, and later image generation may be judged “inconsistent with design” — for such seams use chain_shot_from with mode=reframe. If this shot already has video, it will be marked pending regeneration. If this shot's first frame is already client-locked (first_frame_pinned), the call is rejected; pass force:true only to overwrite it (any execution mode requires explicit client consent, including managed mode).",
|
|
210
212
|
"quote_chain_shot_from": "(Free quote) chain_shot_from with mode=reframe deducts **one image** (regenerate this shot's first frame anchored on the source frame); quote before execution (quote confirmation follows execution mode: professional mode / unconfirmed plan / this step is customer-controlled → explain to the customer and proceed only after customer approval; do not decide for them; this step is autopiloted → do not ask the customer, execute per get_production_plan autopilot.default_decisions, and the platform records it in get_autopilot_log). Returns quote_id / estimated_points; quote_id is one-time and valid only for this shot. mode=copy needs no quote.",
|
|
@@ -239,7 +241,7 @@
|
|
|
239
241
|
"save_designed_voice": "Save the candidate selected by the client into the voice library and return voice_id (like lib:12). **Charged the same as voice cloning** (saving the same candidate repeatedly does not charge again). Then bind it to a character with set_character_voice (narrator is also a character) before using it for dubbing; you can also use speak_with_voice first to audition more sentences.",
|
|
240
242
|
"set_character_voice": "Bind a voice to a character; later generate_tts will use it to dub that character. Free. This is only needed when producing video—cloning and auditioning do not require a character.\n★★【In native-audio dramas (use_clip_audio=true, default), this step is “voice locking”】When the bound voice is **the client’s own authorized voice** (cloned by clone_voice / uploaded by the client, not a public voice), later video generation will pass this audio directly to the video vendor as reference audio. The vendor will speak according to it, and lip sync is naturally aligned—otherwise each group/single shot samples the voice independently, so the same character’s voice may drift across shots. Public voices are only used for TTS dubbing and will not be passed to the vendor.\n★Cost: shots where this character appears will no longer feed a character-sheet video anchor during video generation (the reference video’s audio track overrides reference audio; only one can be used). Character identity must instead be carried by the character sheet/reference images—so confirm the character sheet and reference images are ready before binding.\n★Only affects videos generated **after binding**: bind first, then generate_videos. For videos already generated, the cheapest way to unify the voice is repair_episode_dialogue (only_flagged=false, re-dub full lines with the cloned voice, do not regenerate video, charged at the TTS tier); regenerate_shot_video also works but is orders of magnitude more expensive.\n★After binding, assign_voices will no longer overwrite this character (the client’s own voice has highest priority).",
|
|
241
243
|
"set_voice_anchor_from_shot": "Select a character’s voice line (voice baseline) from a shot in this episode. Free. No external audio source is required from the client—use the voice this character has already spoken in the video.\n★★【In native-audio dramas (use_clip_audio=true, default), this is “voice locking”】After selection, later video generation will pass this audio to the video vendor as reference audio, so the same character’s voice across shots follows it; without locking, the vendor samples each shot independently and the voice may drift across shots.\n★Which shot to choose: a shot where the character **speaks alone, has a longer line, and has no background music or interruptions by others**; start_s/dur_s can clip only part of it (if omitted, the default window is used, and duration is automatically clamped to each engine’s limit).\n★Only affects videos generated **afterward**. Shots already generated before locking will appear in get_pipeline_status under native_voice_anchor.stale_shots; regenerate them with regenerate_shot_video to unify the voice. The cheapest workflow is: generate one shot for each speaking character first, select the voice line, then batch-generate the remaining shots.\n★Works even if the dialogue field is empty: actual audio is authoritative—the platform uses calibrated voice activity detection to confirm there is speech in the selected interval, and uses the transcription text as anchor text; if no speech is detected or no usable text can be transcribed, it rejects (a wrong anchor would pollute all later shots’ voices, so reject rather than guess). The response field text_source indicates whether text came from dialogue or transcription.\n★Preview first with preview:true: only clips and returns preview_url plus anchor text, **without writing to the character**; if it sounds right, remove preview and formally submit with the same start_s/dur_s.\n★Noise removal and acceptance check (effective when enabled by the platform for the episode; response contains hygiene): automatically removes pure environmental-noise pauses between two lines and measures sample noise floor (noise_floor_before/after, threshold threshold). In preview, preview_url is the **cleaned** sample and raw_preview_url is the original—**ask the client to compare both before/after by listening**. verdict=fail means obvious environmental noise or music is under the voice (removing pauses cannot fix it; cloning and the video vendor will learn it as part of the timbre), and formal submission will be rejected: choose a cleaner shot, or adjust start_s/dur_s to avoid background sound; only pass allow_issues:true to write it if the client has listened and explicitly insists.\n★If the client has an authorized external audio source, use clone_voice + set_character_voice instead; choose one of the two, and the later setting overrides the earlier one.",
|
|
242
|
-
"export_handoff_pack": "After delivery traceability is enabled, an immutable export manifest is saved (assets are not modified). Keep pack_id and manifest_sha256, and reference them when returning an external final cut. Export the “asset handoff package” manifest for this episode: downloadable URLs for each-shot raw clips + dialogue tracks + sound effects + music + subtitles, for you to complete transition decisions, stitching, mixing, and subtitle burn-in **on your own side**—the platform does not participate in final assembly. Free, zero credits.\n★Local color grading / third-party AI post: purpose=local_color; when there are only images, pass media_type=images, no need to generate video. This mode preserves source files, disables the export package’s automatic LUT, and includes post-production notes and a local comparison-page flow; it does not upload to third parties, charge for generation, or backfill the platform. Image sources may already have been processed, so not all exported images can be promised as ungraded originals.\n\n【Recommended workflow】① Call this tool to get manifest; ② Download raw clips locally via clips[].url; ③ Watch the clips yourself and decide which transition to use at each seam (manifest provides scene_boundary as a scene-boundary criterion), write a plan.json; ④ Use get_handoff_toolchain to get compile_timeline.py to expand the timeline and assemble.sh to assemble the final cut. The toolchain already calculates how subtitles/dialogue/SFX shift after overlapping transitions are added.\n\n【Three facts that will break things if ignored】\n① **Where the voice is must be read per shot from `shots[].voice_track.location`, not from the episode-level audio_contract.mode**—the latter is an episode-level declaration, while in all-offscreen-narration shots the narration is mixed into the raw clip on the platform completion side, so it can differ per shot within the same episode. The four values require different handling: `separate_file`=dialogue is in dialogue_audio; if you do not lay it in, this shot has no lines; `baked_in_clip`=already in the raw clip audio track; overlaying it again causes double voice; `missing`=**platform side confirmed missing** (narration compensation failed); subtitles still exist but the sound is nowhere—regenerate this shot on the platform; extending a transition cannot hide it; `unknown`=there are lines but the source cannot be found, **you must listen to the raw clip** before deciding. ★Subtitles never imply there is sound: subtitles are per sentence, audio is per shot. For shots with `conflicts_with_contract=true`, trust measurement; notes already name them by shot number.\n② Each shot must be trimmed using trim_head_ms / duration_ms before use; directly stitching the full raw clip will include head/tail deformed frames that the platform already QC’d out.\n③ Subtitle cues, dialogue_audio.offset_ms, and sfx[].offset_ms are all based on “millisecond 0 after this shot’s trim”, not absolute final-cut time. No matter how many overlapping transitions you add, do not modify them—let compile_timeline.py expand them; do not manually accumulate times.\n\nIf you want the platform to assemble it, or need platform-level quality gates (final assembly precheck / equal audio-video duration / loudness mastering), use compose_episode.",
|
|
244
|
+
"export_handoff_pack": "After delivery traceability is enabled, an immutable export manifest is saved (assets are not modified). Keep pack_id and manifest_sha256, and reference them when returning an external final cut. Export the “asset handoff package” manifest for this episode: downloadable URLs for each-shot raw clips + dialogue tracks + sound effects + music + subtitles, for you to complete transition decisions, stitching, mixing, and subtitle burn-in **on your own side**—the platform does not participate in final assembly. Free, zero credits.\n★Local color grading / third-party AI post: purpose=local_color; when there are only images, pass media_type=images, no need to generate video. This mode preserves source files, disables the export package’s automatic LUT, and includes post-production notes and a local comparison-page flow; it does not upload to third parties, charge for generation, or backfill the platform. Image sources may already have been processed, so not all exported images can be promised as ungraded originals.\n\n【Recommended workflow】① Call this tool to get manifest; ② Download raw clips locally via clips[].url; ③ Watch the clips yourself and decide which transition to use at each seam (manifest provides scene_boundary as a scene-boundary criterion), write a plan.json; ④ Use get_handoff_toolchain to get compile_timeline.py to expand the timeline and assemble.sh to assemble the final cut. The toolchain already calculates how subtitles/dialogue/SFX shift after overlapping transitions are added.\n\n【Three facts that will break things if ignored】\n① **Where the voice is must be read per shot from `shots[].voice_track.location`, not from the episode-level audio_contract.mode**—the latter is an episode-level declaration, while in all-offscreen-narration shots the narration is mixed into the raw clip on the platform completion side, so it can differ per shot within the same episode. The four values require different handling: `separate_file`=dialogue is in dialogue_audio; if you do not lay it in, this shot has no lines; `baked_in_clip`=already in the raw clip audio track; overlaying it again causes double voice; `missing`=**platform side confirmed missing** (narration compensation failed); subtitles still exist but the sound is nowhere—regenerate this shot on the platform; extending a transition cannot hide it; `unknown`=there are lines but the source cannot be found, **you must listen to the raw clip** before deciding. ★Subtitles never imply there is sound: subtitles are per sentence, audio is per shot. For shots with `conflicts_with_contract=true`, trust measurement; notes already name them by shot number.\n② Each shot must be trimmed using trim_head_ms / duration_ms before use; directly stitching the full raw clip will include head/tail deformed frames that the platform already QC’d out.\n③ Subtitle cues, dialogue_audio.offset_ms, and sfx[].offset_ms are all based on “millisecond 0 after this shot’s trim”, not absolute final-cut time. No matter how many overlapping transitions you add, do not modify them—let compile_timeline.py expand them; do not manually accumulate times.\n\nBefore you assemble, run build_av_timing_contract → get_av_timing_contract (free): per shot the measured source length, the platform final cut's in/out points, transition overlap and freeze padding, and whether the voice is already mixed into the clip or on a separate track — check your timeline's total against it.\n\nIf you want the platform to assemble it, or need platform-level quality gates (final assembly precheck / equal audio-video duration / loudness mastering), use compose_episode.",
|
|
243
245
|
"get_handoff_toolchain": "Get the source code for the asset handoff package assembly toolchain (compile_timeline.py timeline compiler / assemble.sh ffmpeg assembly script). Free. This tool only returns source text and **will not** write files on your machine—save it to your working directory yourself before running.\ncompile_timeline.py expands “shot-relative” subtitle/dialogue/SFX anchors into absolute timecodes on your own timeline and handles global offsets caused by overlapping transitions; assemble.sh is a complete ffmpeg assembly baseline from raw clips to final cut (trim → normalize → concat/xfade → music sidechain → subtitles → two-pass master). Dependencies: ffmpeg (with libass for subtitle burn-in), ffprobe, jq, python3.",
|
|
244
246
|
"save_handoff_toolchain": "Write the assembly toolchain (fetch_pack.py / compile_timeline.py / assemble.sh) directly to a local directory, avoiding manual copy/paste. Free. The directory must **already exist** and be an absolute path; filenames are fixed and cannot be customized.\nRefuses to write into hidden directories (~/.ssh, ~/.config, .git…) and system directories—this is disk writing, not reading, and overwriting the wrong place is irreversible.\nFull workflow after writing to disk:\n python3 fetch_pack.py manifest.json -o ./pack # Download assets and materialize inline subtitles as SRT\n python3 compile_timeline.py ./pack [--transitions plan.json]\n ./assemble.sh ./pack out.mp4 [plan.json]",
|
|
245
247
|
"get_post_production_checklist": "Read this episode's **post-production effects checklist** (free). Items marked in the script as [VFX: …] or written in shot prompts as \"post-production / composited later\" are **not composited by the platform**—image and video generation leave them out and the final cut does not add them, so the finished episode lacks these story elements unless handled. Per item: shot_number, time start_s~end_s, content, source (vfx_tag shot annotation / prompt / script_unassigned = in the rewritten script but not attached to any shot), narrative_required (story-critical; cosmetic items such as color grading are false), status (pending / done_external / waived). ★Review before composing; tell the client about narrative_required pending items and ask: do it in external post (then mark_post_production_item done_external), change the shot so the platform draws it (update_shot then regenerate that shot), or drop it (waived). review_videos / compose_episode give an advisory for unhandled items.",
|
|
@@ -218,6 +218,8 @@
|
|
|
218
218
|
"audit_seams": "审核当前双边帧的剪辑连续性,只检查、不重生、不替换资产。默认 dry_run=true 返回待审范围,不收费。插入细节仅检查物体身份和状态,不要求复制构图。已有当前有效结论不重复审核。执行会按实际文本 token 用量计费,estimated_points=null 表示金额未报价,并非免费。先展示计划和计费方式,经客户明确接受后才传 dry_run=false 与 confirm_usage_billing=true。执行后用 get_storyboards 的 seam_review 检查实际结果;attempted 不代表通过。",
|
|
219
219
|
"preview_seams": "★查转场/接缝问题先跑这个,不要反复导出整片。免费零扣费(纯 ffmpeg,不调厂商,也不现场合成配音)。按集为每处相邻镜接缝渲染一段短片:前镜出点前 1 秒 + 后镜入点后 1 秒(转场长于 0.5 秒时两侧各取 2×转场),套用当前 trim 窗口与转场设置,与终拼同一套建轴和渲染逻辑,看到的就是 compose_episode 重拼会得到的接缝(只缩小画幅;不含配乐与字幕)。同时给出两侧边缘帧对照图,并附上 audit_seams 的现有结论。异步:本工具只起任务,用 get_seam_previews 轮询到 status=done。seams 可选,只重渲指定序号的缝(改了某处 trim/转场后复查用)。★看完再决定怎么修:动作没接上先 recommend_trim_window → trim_shot;转场不合适改转场;叠化重影、放大插镜、遮挡这类补救手法本身常引入新问题,改完务必再跑一次本工具复查那道缝。",
|
|
220
220
|
"get_seam_previews": "查 preview_seams 的进度与结果。免费。status=running 时看 progress.done/total;status=done 时 result.seams[] 每道缝给出:from_shot_id/to_shot_id、transition(有效转场)、left/right(两侧在源视频上的入出点 src_in_ms/src_out_ms,已含 trim)、preview_url(短片)、compare_image_url(左=前镜转场开始前最后一帧,右=后镜转场结束后第一帧)、audit(audit_seams 结论;not_enabled=本剧未开启)。result.synth_skipped_shot_ids 非空表示这些镜配音尚未合成,预览按无配音处理。单镜集 seam_count=0。",
|
|
221
|
+
"build_av_timing_contract": "★外部 agent 自己拼片、或要核对「成片为什么是这个长度」时先跑这个,别按分镜写的秒数直接拼。免费零扣费(纯 ffprobe + 本地活动检测,不调厂商,也不现场合成配音)。按集给出「音画时长契约」:逐镜 source.duration_ms(ffprobe 实测源片时长,不是分镜秒数)与 excess_over_declared_ms(厂商多给的尾段);trim.in_ms/out_ms(进成片的源片入出点,已含自动掐头与 trim_shot;无 trim 的镜 full_length=true);action(有效动作区间与推荐裁剪窗口,复用 recommend_trim_window 的活动检测);tail(freeze_pad_ms=渲染器克隆末帧补齐的定格时长,low_motion_in_cut_ms=动作结束后仍留在成片里的低运动尾段);voice.in_final_cut(mixed_into_video=人声已混在视频音轨里,不许再配音;separate_track=独立对白轨,dialogue_track 给时间轴区间;pending_synthesis/missing/unknown/double_voice 见指令);timeline(终拼时间轴起止、transition_in 重叠、transition_out_overlap_ms、exclusive 独占区间)。整集 summary 给总时长、转场重叠总和、各轨跨度与超出画面终点的轨。planned 与 compose_episode 同一套建轴;final_cut 读上次成片实际用的时间轴并实测成片时长(duration_check),drift_vs_planned 列出现在重拼会变的镜。异步:本工具只起任务,用 get_av_timing_contract 轮询到 status=done。include_activity=false 可跳过活动检测(快,但没有动作区间与低运动尾段)。",
|
|
222
|
+
"get_av_timing_contract": "查 build_av_timing_contract 的进度与结果。免费。status=running 时看 progress(stage=build_timeline/activity/probe);status=done 时 result.planned 是现在重拼会得到的逐镜契约,result.final_cut 是上次成片(contract=存档时间轴推出的契约,probed=成片实测,duration_check.matches=总时长在 100ms 容差内一致,drift_vs_planned=两者不同的镜;timeline_association≠verified 时存档时间轴已不是成片用的那份,contract 不提供)。★自己拼片按 shots[].timeline 排位、按 trim.in_ms/out_ms 截源片;voice.in_final_cut=mixed_into_video 的镜直接用视频原声,绝不再叠配音。result.synth_skipped_shot_ids 非空 = 这些镜配音尚未合成(成片时现场合成扣费),对白轨区间暂缺。",
|
|
221
223
|
"audit_seam_directions": "出视频前的接缝方向对照(提示性,不拦任何步骤):对每对相邻镜拿「上一镜尾帧(没有就首帧)↔ 本镜首帧」看五件事——同一主体在画面左右跳边(视线也反了会一并写明)、运动方向反了、景别跳级(同一主体大远景↔特写)。单镜都对、拼起来跳,多半是这几维。视线单独反向、主光方向会被观察但不报:生产回放里这两维误报率太高(宁漏不扰)。与 audit_seams 互补:那个比身份/服装/道具/物体状态,不管方向和位置。★何时调:首帧(和要的尾帧)都出完、chain_frames 跑过之后,review_frames / generate_videos 之前。换场、声明跳切、插入镜、字卡黑场的接缝自动跳过。★先 dry_run(默认,免费):回待观察接缝与 estimated_points(上界)/ typical_points(通常花费),两个都告诉客户,按实际文本 token 扣费;客户同意后传 dry_run=false + confirm_usage_billing=true。执行是异步的,立即返回;过一两分钟用 get_seam_directions 读结论。已观察过且两侧帧没换的接缝不重复扣费。",
|
|
222
224
|
"get_seam_directions": "(免费)读 audit_seam_directions 的结论:issues 每条带接缝镜号、问题(主体跳边、运动反向、景别跳级)与 fixes 修复建议(chain_shot_from mode=reframe 以上一镜尾帧为锚重出本镜首帧 / update_shot 写明朝向后 generate_shot_frame / 上一镜已出视频时 align_prev_shot / split_shot 插过渡景别)。结论是提示性的:讲给客户,由客户挑要修的;有意的反打、转身、冲击剪辑可忽略。not_audited>0 = 还有接缝没观察或帧已换过;running=true = 还在跑。review_frames 的 seam_direction_advisory 是同一份结论的摘要。本工具只看出视频前的静帧;视频出完后查接缝用 preview_seams(免费短片预览)。",
|
|
223
225
|
"chain_frames": "整集首尾帧接力链:让每镜首帧承接上镜尾帧,镜间画面连续(比各镜独立出帧更顺)。★何时调:首帧出完、tail_frame_plan 要的尾帧也补完之后,review_frames / generate_videos 之前。平台只在官网批量出帧结束时自动跑一次;逐镜出帧(generate_frames / generate_shot_frame)的集多半从没接过——get_storyboards 的 seam_state=unchained 就是这种缝。\n★先 dry_run:true(免费,不写库不出图):回 planned_copy / planned_reframe / estimated_points / needs_last_frame。copy=本镜首帧直接换成上镜尾帧(免费,但本镜景别会变成和上镜一样);reframe=以上镜尾帧为锚按本镜画面描述重生首帧(每道一张图,保景别)。把计划和 estimated_points 告诉客户,确认后**不带 dry_run** 再调一次执行(按用量后付)。\n★平台自己判哪道缝该接:有意切换(换场/时间跳跃/换装/插入镜)、已出视频的镜、已经接好的缝都会跳过——「已接好」= 首帧就是上镜尾帧,或 reframe 接在上镜**当前**尾帧上,所以重复执行不会重复扣费。上镜尾帧后来被重生过的缝会重新出现在计划里,那道缝确实断了。只补个别缝用 chain_shot_from_prev / chain_shot_from。执行后改了首帧的镜若已有视频会被标为待重生。★执行只派发 reframe、不等出图:用 get_jobs 看这些镜的 first_frame 出图行,等 pending/processing 全部结束(failed 的看 error_msg,单道用 chain_shot_from 重接;旧首帧还在时 get_storyboards 的 frame_status 仍是 ready、看不出失败),再用 get_storyboards 核对 seam_state、review_frames——提前审会审到旧首帧,token 随后作废。",
|
|
@@ -254,7 +256,7 @@
|
|
|
254
256
|
"save_designed_voice": "把客户挑中的那条候选存进音色库,返回 voice_id(形如 lib:12)。**按声音克隆同价扣费**(同一条重复保存不重复扣)。之后用 set_character_voice 绑到角色(旁白也是一个角色)才会用它配音;也可以先 speak_with_voice 试听更多句子。",
|
|
255
257
|
"set_character_voice": "把音色绑到某角色,之后 generate_tts 用它给该角色配音。免费。只有要出片时才需要这步——克隆和试听都不需要角色。\n★★【原声剧(use_clip_audio=true,默认)里这一步就是「锁声线」】绑的是**客户自己的授权音色**(clone_voice 克隆的 / 客户上传的,不是公共音色)时,之后生成的视频会把这段声音直接交给视频厂商当参考音频,厂商按它发声、口型天然同步——否则每个组/单镜各自采样嗓音,同一角色前后镜声线会漂。公共音色只用于 TTS 配音,不会交给厂商。\n★代价:该角色出场的镜头生成视频时不再喂定妆视频锚(参考视频的音轨会压过参考音频,只能二选一),人物身份改由定妆图/设定图承担——所以绑之前先确认定妆图、设定图已经到位。\n★只对**绑定之后**生成的视频生效:要先绑、再 generate_videos。已经出好的视频要统一声线,用 repair_episode_dialogue(only_flagged=false,克隆音重配整句、不重生视频、按 TTS 档计费)最省;重生视频(regenerate_shot_video)也行但贵几个数量级。\n★绑好后 assign_voices 不会再覆盖这个角色(客户自己的声音优先级最高)。",
|
|
256
258
|
"set_voice_anchor_from_shot": "从本剧某一镜选定角色的声线(声音基准)。免费。不需要客户提供外部音源——就用片子里这个角色已经念出来的声音。\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★去环境音与验收(平台按剧开启时生效,响应里有 hygiene 字段):自动剔除两句之间的纯环境音停顿,并测样本底噪(noise_floor_before/after,阈值 threshold)。preview 时 preview_url 是**清理后**的样本、raw_preview_url 是原样本——**请让客户前后对比试听**。verdict=fail 表示人声下面垫着明显的环境声或音乐(剔停顿救不了,克隆和视频厂商会把它当音色学走),正式提交会被拒:换一镜念得更干净的、或调 start_s/dur_s 避开背景声;只有客户听过后明确坚持,才带 allow_issues:true 写入。\n★有客户授权的外部音源时改用 clone_voice + set_character_voice;两者二选一,后设的覆盖先设的。",
|
|
257
|
-
"export_handoff_pack": "启用交付追溯后会保存不可变导出清单(不改素材)。保留 pack_id 和 manifest_sha256,回传外部成片时引用。导出本集「素材交接包」清单:逐镜裸片 + 对白音轨 + 音效 + 配乐 + 字幕的可下载 URL,交给你在**自己那边**完成转场决策、拼接、混音、烧字幕——平台不参与终拼。免费,零扣费。\n★本地调色/第三方 AI 后期:purpose=local_color;只有图片时传 media_type=images,无需生成视频。本模式保留源文件、关闭导出包的自动 LUT、附带后期说明和本地对比页流程;不上传第三方、不收费生成、不回填平台。图片来源可能已有处理,不能把所有导出图承诺为未调色原片。\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。",
|
|
259
|
+
"export_handoff_pack": "启用交付追溯后会保存不可变导出清单(不改素材)。保留 pack_id 和 manifest_sha256,回传外部成片时引用。导出本集「素材交接包」清单:逐镜裸片 + 对白音轨 + 音效 + 配乐 + 字幕的可下载 URL,交给你在**自己那边**完成转场决策、拼接、混音、烧字幕——平台不参与终拼。免费,零扣费。\n★本地调色/第三方 AI 后期:purpose=local_color;只有图片时传 media_type=images,无需生成视频。本模式保留源文件、关闭导出包的自动 LUT、附带后期说明和本地对比页流程;不上传第三方、不收费生成、不回填平台。图片来源可能已有处理,不能把所有导出图承诺为未调色原片。\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拼之前先跑 build_av_timing_contract → get_av_timing_contract(免费):逐镜实测源片时长、平台终拼的入出点/转场占用/定格补帧、人声已混入视频还是独立轨,拿它核对你的时间轴总长。\n想让平台代拼、要平台级质量闸(终拼预检/音画等长/响度母带),改用 compose_episode。",
|
|
258
260
|
"get_handoff_toolchain": "拿到素材交接包的装配工具链源码(compile_timeline.py 时间轴编译器 / assemble.sh ffmpeg 装配脚本)。免费。本工具只返回源码文本,**不会**在你机器上写文件——请自行保存到工作目录再执行。\ncompile_timeline.py 负责把「镜相对」的字幕/对白/音效锚点展开成你自己时间轴上的绝对时间码,并处理重叠转场引起的整体位移;assemble.sh 是从裸片到成片的完整 ffmpeg 装配基线(裁剪→规范化→拼接/xfade→配乐侧链→字幕→两 pass 母带)。依赖 ffmpeg(烧字幕需带 libass)、ffprobe、jq、python3。",
|
|
259
261
|
"save_handoff_toolchain": "把装配工具链(fetch_pack.py / compile_timeline.py / assemble.sh)直接写到你本地的一个目录,省掉自己复制粘贴。免费。目录必须**已存在**且是绝对路径;文件名固定,不接受自定义。\n拒绝写入隐藏目录(~/.ssh、~/.config、.git…)与系统目录——这是写盘不是读盘,覆盖错地方不可逆。\n落盘后完整流程:\n python3 fetch_pack.py manifest.json -o ./pack # 下载素材、内联字幕落成 SRT\n python3 compile_timeline.py ./pack [--transitions plan.json]\n ./assemble.sh ./pack out.mp4 [plan.json]"
|
|
260
262
|
}
|
package/dist/tools/guide-data.js
CHANGED
|
@@ -169,6 +169,7 @@ export const ENTRY_POINTS = [
|
|
|
169
169
|
'trim_shot',
|
|
170
170
|
'recommend_trim_window',
|
|
171
171
|
'preview_seams / get_seam_previews(每道接缝一段 2 秒短片 + 边缘帧对照,与终拼同逻辑;查转场不必导整片;免费)',
|
|
172
|
+
'build_av_timing_contract / get_av_timing_contract(逐镜音画时长契约:实测源片时长/入出点/定格与低运动尾段/人声混入视频还是独立轨/终拼起止与转场;与终拼同逻辑;免费)',
|
|
172
173
|
'regenerate_shot_video',
|
|
173
174
|
'edit_video_shot(就地编辑;get_edit_capabilities 先看当前引擎支持什么)',
|
|
174
175
|
'repaint_shot_video / reskin_shot_video(保留原片动作、整体换人换装;get_repaint_capabilities 先看本剧开没开)',
|
|
@@ -195,6 +196,7 @@ export const ENTRY_POINTS = [
|
|
|
195
196
|
use: ['export_handoff_pack', 'get_handoff_toolchain', 'save_handoff_toolchain'],
|
|
196
197
|
note: '与 `compose_episode` 二选一。★人声在哪要**逐镜**看 voice_track.location——整集的 audio_contract.mode 只是声明,' +
|
|
197
198
|
'不是逐镜真值(全画外旁白镜的旁白是平台在完成侧混进裸片的);逐句核对看 spoken_lines,有字幕不等于有声音。' +
|
|
199
|
+
'拼之前跑 build_av_timing_contract(免费):逐镜实测源片时长、平台终拼的入出点与转场占用、哪几镜旁白已混进视频(别再配音),拿它核对总时长。' +
|
|
198
200
|
'完整流程见 local_postproduction 段。',
|
|
199
201
|
},
|
|
200
202
|
{
|
|
@@ -370,7 +372,7 @@ export const COMMON_REQUESTS = [
|
|
|
370
372
|
{ customer_says: '镜头之间接不上 / 画面跳 / 衔接不顺', do: '`get_storyboards` 看 seam_state:unchained / broken 才要修(unknown 是系统没判、intended 是有意切换,都别动)。还没出视频:整集 `chain_frames`(先 dry_run),单道同景别 `chain_shot_from_prev`(免费)、景别有变化 `quote_chain_shot_from` → `chain_shot_from` mode=reframe。上一镜视频已出好、不想动本镜首帧:`quote_align_prev_shot` → `align_prev_shot`(重生上一镜视频)。改了首帧的镜视频要重生。' },
|
|
371
373
|
{ customer_says: '成片里话没说完 / 切太快', do: '先 `scan_dialogue_coverage` / `scan_intra_shot_cuts` 定病因,再按 qa_tools 的 then 修;别默认去加长镜头。' },
|
|
372
374
|
{ customer_says: '下载原图或视频到本地调色、用第三方 AI 后期', do: '`export_handoff_pack` purpose=local_color;只有图片传 media_type=images;按 local_postproduction.color_handoff 执行。' },
|
|
373
|
-
{ customer_says: '我想自己剪', do: '`export_handoff_pack` + `get_handoff_toolchain`,不走 `compose_episode`。' },
|
|
375
|
+
{ customer_says: '我想自己剪', do: '`export_handoff_pack` + `build_av_timing_contract`(音画时长契约) + `get_handoff_toolchain`,不走 `compose_episode`。' },
|
|
374
376
|
{ customer_says: '做动作片 / 打斗 / 技能展示 / 这条参考动作片我们能不能做', do: '先 `get_capabilities_guide` section=action_film:资产照常走 `rewrite_script` → `review_script` → `extract_assets`,分镜改用自己写的表(`import_storyboard_table`)而不是 AI 拆镜,首帧停在起势,拖沓尾段用 `trim_shot` 剪;平台不支持镜内分段变速。' },
|
|
375
377
|
{ customer_says: '要配音 / 不要视频原声', do: '`update_project_settings` use_clip_audio=false → `assign_voices` → `generate_tts` → `compose_episode`。' },
|
|
376
378
|
];
|
package/dist/tools/produce.js
CHANGED
|
@@ -2383,6 +2383,11 @@ export function registerProduceTools(server, client) {
|
|
|
2383
2383
|
+ 'left/right(两侧在源视频上的入出点 src_in_ms/src_out_ms,已含 trim)、preview_url(短片)、'
|
|
2384
2384
|
+ 'compare_image_url(左=前镜转场开始前最后一帧,右=后镜转场结束后第一帧)、audit(audit_seams 结论;not_enabled=本剧未开启)。'
|
|
2385
2385
|
+ 'result.synth_skipped_shot_ids 非空表示这些镜配音尚未合成,预览按无配音处理。单镜集 seam_count=0。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/seam-previews`)));
|
|
2386
|
+
server.tool('build_av_timing_contract', '★外部 agent 自己拼片、或要核对「成片为什么是这个长度」时先跑这个,别按分镜写的秒数直接拼。免费零扣费(纯 ffprobe + 本地活动检测,不调厂商,也不现场合成配音)。按集给出「音画时长契约」:逐镜 source.duration_ms(ffprobe 实测源片时长,不是分镜秒数)与 excess_over_declared_ms(厂商多给的尾段);trim.in_ms/out_ms(进成片的源片入出点,已含自动掐头与 trim_shot;无 trim 的镜 full_length=true);action(有效动作区间与推荐裁剪窗口,复用 recommend_trim_window 的活动检测);tail(freeze_pad_ms=渲染器克隆末帧补齐的定格时长,low_motion_in_cut_ms=动作结束后仍留在成片里的低运动尾段);voice.in_final_cut(mixed_into_video=人声已混在视频音轨里,不许再配音;separate_track=独立对白轨,dialogue_track 给时间轴区间;pending_synthesis/missing/unknown/double_voice 见指令);timeline(终拼时间轴起止、transition_in 重叠、transition_out_overlap_ms、exclusive 独占区间)。整集 summary 给总时长、转场重叠总和、各轨跨度与超出画面终点的轨。planned 与 compose_episode 同一套建轴;final_cut 读上次成片实际用的时间轴并实测成片时长(duration_check),drift_vs_planned 列出现在重拼会变的镜。异步:本工具只起任务,用 get_av_timing_contract 轮询到 status=done。include_activity=false 可跳过活动检测(快,但没有动作区间与低运动尾段)。', {
|
|
2387
|
+
episode_id: z.number().int().positive(),
|
|
2388
|
+
include_activity: z.boolean().optional().describe('默认 true:逐镜跑活动检测(有效动作区间 / 低运动尾段);false 只算时间轴与探测,快'),
|
|
2389
|
+
}, async ({ episode_id, include_activity }) => jsonResult(await client.producePost(`/episodes/${episode_id}/av-timing-contract`, include_activity === false ? { include_activity: false } : {})));
|
|
2390
|
+
server.tool('get_av_timing_contract', '查 build_av_timing_contract 的进度与结果。免费。status=running 时看 progress(stage=build_timeline/activity/probe);status=done 时 result.planned 是现在重拼会得到的逐镜契约,result.final_cut 是上次成片(contract=存档时间轴推出的契约,probed=成片实测,duration_check.matches=总时长在 100ms 容差内一致,drift_vs_planned=两者不同的镜;timeline_association≠verified 时存档时间轴已不是成片用的那份,contract 不提供)。★自己拼片按 shots[].timeline 排位、按 trim.in_ms/out_ms 截源片;voice.in_final_cut=mixed_into_video 的镜直接用视频原声,绝不再叠配音。result.synth_skipped_shot_ids 非空 = 这些镜配音尚未合成(成片时现场合成扣费),对白轨区间暂缺。', { episode_id: z.number().int().positive() }, async ({ episode_id }) => jsonResult(await client.produceGet(`/episodes/${episode_id}/av-timing-contract`)));
|
|
2386
2391
|
server.tool('audit_seam_directions', '出视频前的接缝方向对照(提示性,不拦任何步骤):对每对相邻镜拿「上一镜尾帧(没有就首帧)↔ 本镜首帧」看五件事——' +
|
|
2387
2392
|
'同一主体在画面左右跳边(视线也反了会一并写明)、运动方向反了、景别跳级(同一主体大远景↔特写)。单镜都对、拼起来跳,多半是这几维。' +
|
|
2388
2393
|
'视线单独反向、主光方向会被观察但不报:生产回放里这两维误报率太高(宁漏不扰)。' +
|
|
@@ -2635,6 +2640,7 @@ export function registerProduceTools(server, client) {
|
|
|
2635
2640
|
'首尾形变帧一起拼进去。\n' +
|
|
2636
2641
|
'③ 字幕 cue、dialogue_audio.offset_ms、sfx[].offset_ms 的基准都是「该镜 trim 之后的第 0 毫秒」,' +
|
|
2637
2642
|
'不是成片绝对时间。你加多少重叠转场都不用改它们——交给 compile_timeline.py 展开,别手算累加。\n' +
|
|
2643
|
+
'\n拼之前先跑 build_av_timing_contract → get_av_timing_contract(免费):逐镜实测源片时长、平台终拼的入出点/转场占用/定格补帧、人声已混入视频还是独立轨,拿它核对你的时间轴总长。' +
|
|
2638
2644
|
'\n想让平台代拼、要平台级质量闸(终拼预检/音画等长/响度母带),改用 compose_episode。', {
|
|
2639
2645
|
episode_id: z.number().int().positive(),
|
|
2640
2646
|
media_type: z.enum(['videos', 'images']).optional().describe('默认 videos;images 导出现有首尾帧并自动走本地后期流程'),
|
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 → 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.
|
|
5
|
+
"version": "0.1.212",
|
|
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.212 tool surface (operationIds match MCP tool names 1:1)."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
|
9
9
|
{
|
|
@@ -5064,6 +5064,109 @@
|
|
|
5064
5064
|
}
|
|
5065
5065
|
}
|
|
5066
5066
|
},
|
|
5067
|
+
"/episodes/{episode_id}/av-timing-contract": {
|
|
5068
|
+
"post": {
|
|
5069
|
+
"operationId": "build_av_timing_contract",
|
|
5070
|
+
"summary": "★外部 agent 自己拼片、或要核对「成片为什么是这个长度」时先跑这个,别按分镜写的秒数直接拼",
|
|
5071
|
+
"description": "★外部 agent 自己拼片、或要核对「成片为什么是这个长度」时先跑这个,别按分镜写的秒数直接拼。免费零扣费(纯 ffprobe + 本地活动检测,不调厂商,也不现场合成配音)。按集给出「音画时长契约」:逐镜 source.duration_ms(ffprobe 实测源片时长,不是分镜秒数)与 excess_over_declared_ms(厂商多给的尾段);trim.in_ms/out_ms(进成片的源片入出点,已含自动掐头与 trim_shot;无 trim 的镜 full_length=true);action(有效动作区间与推荐裁剪窗口,复用 recommend_trim_window 的活动检测);tail(freeze_pad_ms=渲染器克隆末帧补齐的定格时长,low_motion_in_cut_ms=动作结束后仍留在成片里的低运动尾段);voice.in_final_cut(mixed_into_video=人声已混在视频音轨里,不许再配音;separate_track=独立对白轨,dialogue_track 给时间轴区间;pending_synthesis/missing/unknown/double_voice 见指令);timeline(终拼时间轴起止、transition_in 重叠、transition_out_overlap_ms、exclusive 独占区间)。整集 summary 给总时长、转场重叠总和、各轨跨度与超出画面终点的轨。planned 与 compose_episode 同一套建轴;final_cut 读上次成片实际用的时间轴并实测成片时长(duration_check),drift_vs_planned 列出现在重拼会变的镜。异步:本工具只起任务,用 get_av_timing_contract 轮询到 status=done。include_activity=false 可跳过活动检测(快,但没有动作区间与低运动尾段)。",
|
|
5072
|
+
"tags": [
|
|
5073
|
+
"episodes"
|
|
5074
|
+
],
|
|
5075
|
+
"parameters": [
|
|
5076
|
+
{
|
|
5077
|
+
"name": "episode_id",
|
|
5078
|
+
"in": "path",
|
|
5079
|
+
"required": true,
|
|
5080
|
+
"schema": {
|
|
5081
|
+
"type": "integer",
|
|
5082
|
+
"exclusiveMinimum": 0
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
],
|
|
5086
|
+
"requestBody": {
|
|
5087
|
+
"required": false,
|
|
5088
|
+
"content": {
|
|
5089
|
+
"application/json": {
|
|
5090
|
+
"schema": {
|
|
5091
|
+
"type": "object",
|
|
5092
|
+
"properties": {}
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
},
|
|
5097
|
+
"responses": {
|
|
5098
|
+
"200": {
|
|
5099
|
+
"description": "StarReel envelope",
|
|
5100
|
+
"content": {
|
|
5101
|
+
"application/json": {
|
|
5102
|
+
"schema": {
|
|
5103
|
+
"type": "object",
|
|
5104
|
+
"properties": {
|
|
5105
|
+
"code": {
|
|
5106
|
+
"type": "integer"
|
|
5107
|
+
},
|
|
5108
|
+
"message": {
|
|
5109
|
+
"type": "string"
|
|
5110
|
+
},
|
|
5111
|
+
"data": {
|
|
5112
|
+
"description": "Operation result payload"
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
}
|
|
5118
|
+
},
|
|
5119
|
+
"402": {
|
|
5120
|
+
"description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
},
|
|
5124
|
+
"get": {
|
|
5125
|
+
"operationId": "get_av_timing_contract",
|
|
5126
|
+
"summary": "查 build_av_timing_contract 的进度与结果",
|
|
5127
|
+
"description": "查 build_av_timing_contract 的进度与结果。免费。status=running 时看 progress(stage=build_timeline/activity/probe);status=done 时 result.planned 是现在重拼会得到的逐镜契约,result.final_cut 是上次成片(contract=存档时间轴推出的契约,probed=成片实测,duration_check.matches=总时长在 100ms 容差内一致,drift_vs_planned=两者不同的镜;timeline_association≠verified 时存档时间轴已不是成片用的那份,contract 不提供)。★自己拼片按 shots[].timeline 排位、按 trim.in_ms/out_ms 截源片;voice.in_final_cut=mixed_into_video 的镜直接用视频原声,绝不再叠配音。result.synth_skipped_shot_ids 非空 = 这些镜配音尚未合成(成片时现场合成扣费),对白轨区间暂缺。",
|
|
5128
|
+
"tags": [
|
|
5129
|
+
"episodes"
|
|
5130
|
+
],
|
|
5131
|
+
"parameters": [
|
|
5132
|
+
{
|
|
5133
|
+
"name": "episode_id",
|
|
5134
|
+
"in": "path",
|
|
5135
|
+
"required": true,
|
|
5136
|
+
"schema": {
|
|
5137
|
+
"type": "integer",
|
|
5138
|
+
"exclusiveMinimum": 0
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
],
|
|
5142
|
+
"responses": {
|
|
5143
|
+
"200": {
|
|
5144
|
+
"description": "StarReel envelope",
|
|
5145
|
+
"content": {
|
|
5146
|
+
"application/json": {
|
|
5147
|
+
"schema": {
|
|
5148
|
+
"type": "object",
|
|
5149
|
+
"properties": {
|
|
5150
|
+
"code": {
|
|
5151
|
+
"type": "integer"
|
|
5152
|
+
},
|
|
5153
|
+
"message": {
|
|
5154
|
+
"type": "string"
|
|
5155
|
+
},
|
|
5156
|
+
"data": {
|
|
5157
|
+
"description": "Operation result payload"
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
},
|
|
5164
|
+
"402": {
|
|
5165
|
+
"description": "Insufficient prepaid balance (never overdrafts); response carries `needed` points"
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
}
|
|
5169
|
+
},
|
|
5067
5170
|
"/episodes/{episode_id}/bgm": {
|
|
5068
5171
|
"post": {
|
|
5069
5172
|
"operationId": "generate_bgm",
|
|
@@ -7199,7 +7302,7 @@
|
|
|
7199
7302
|
"get": {
|
|
7200
7303
|
"operationId": "export_handoff_pack",
|
|
7201
7304
|
"summary": "启用交付追溯后会保存不可变导出清单(不改素材)",
|
|
7202
|
-
"description": "启用交付追溯后会保存不可变导出清单(不改素材)。保留 pack_id 和 manifest_sha256,回传外部成片时引用。导出本集「素材交接包」清单:逐镜裸片 + 对白音轨 + 音效 + 配乐 + 字幕的可下载 URL,交给你在**自己那边**完成转场决策、拼接、混音、烧字幕——平台不参与终拼。免费,零扣费。\n★本地调色/第三方 AI 后期:purpose=local_color;只有图片时传 media_type=images,无需生成视频。本模式保留源文件、关闭导出包的自动 LUT、附带后期说明和本地对比页流程;不上传第三方、不收费生成、不回填平台。图片来源可能已有处理,不能把所有导出图承诺为未调色原片。\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。",
|
|
7305
|
+
"description": "启用交付追溯后会保存不可变导出清单(不改素材)。保留 pack_id 和 manifest_sha256,回传外部成片时引用。导出本集「素材交接包」清单:逐镜裸片 + 对白音轨 + 音效 + 配乐 + 字幕的可下载 URL,交给你在**自己那边**完成转场决策、拼接、混音、烧字幕——平台不参与终拼。免费,零扣费。\n★本地调色/第三方 AI 后期:purpose=local_color;只有图片时传 media_type=images,无需生成视频。本模式保留源文件、关闭导出包的自动 LUT、附带后期说明和本地对比页流程;不上传第三方、不收费生成、不回填平台。图片来源可能已有处理,不能把所有导出图承诺为未调色原片。\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拼之前先跑 build_av_timing_contract → get_av_timing_contract(免费):逐镜实测源片时长、平台终拼的入出点/转场占用/定格补帧、人声已混入视频还是独立轨,拿它核对你的时间轴总长。\n想让平台代拼、要平台级质量闸(终拼预检/音画等长/响度母带),改用 compose_episode。",
|
|
7203
7306
|
"tags": [
|
|
7204
7307
|
"episodes"
|
|
7205
7308
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starreel/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.212",
|
|
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.212",
|
|
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.212",
|
|
12
12
|
"transport": {
|
|
13
13
|
"type": "stdio"
|
|
14
14
|
},
|