@starreel/mcp 0.1.210 → 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 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
@@ -648,7 +648,10 @@ content that will be rejected.
648
648
  platform pastes only that area back onto the original, so everything outside the
649
649
  box stays pixel-identical. Face/mouth edits need `character_id`; the portrait is **not** sent
650
650
  (with it the model redraws the portrait instead of the frame), the instruction
651
- asks for the same person and the result is re-audited for identity. Results are **candidates, not the new frame**: show `composited_url`
651
+ asks for the same person and the pasted-back face is compared with the original (and the portrait):
652
+ a clear mismatch is rejected (`IDENTITY_MISMATCH`), an uncertain or unjudgeable one (side view,
653
+ looking down, small face) comes back as `warn` for a human look — relay each candidate's `reason_text`
654
+ to the customer as is, and still look at face edits yourself. Results are **candidates, not the new frame**: show `composited_url`
652
655
  from `get_local_frame_edit` to the customer, then `adopt_local_frame_edit` the one
653
656
  they pick. Each candidate is charged; candidates the platform rejects are **not
654
657
  refunded** — tell the customer that with the quote. Rolled out per drama and per `edit_kind`:
@@ -1544,7 +1547,12 @@ to close") tells the vendor to fit that entire sequence into each 3-second shot.
1544
1547
  `compose_episode` when you want the platform's finishing pipeline (pre-flight
1545
1548
  checks, A/V duration parity, loudness mastering). Call
1546
1549
  `get_capabilities_guide(section="local_postproduction")` for the full stage-by-stage
1547
- walkthrough. Three facts that will bite you: **where the voice lives is per-shot —
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 —
1548
1556
  read `shots[].voice_track.location`, never the episode-level
1549
1557
  `audio_contract.mode`** (that field is a declaration; fully-offscreen narration
1550
1558
  shots get their voice mixed into the raw clip on our side, so even a `mode="tts"`
@@ -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.",
@@ -251,9 +253,9 @@
251
253
  "update_acceptance_criterion": "Edit a customer acceptance item (free). Changing the text / category / shot / stages resets all three states of that item (the requirement changed, old conclusions no longer count); re-run the review to check it. To delete, use delete_acceptance_criterion. item_id comes from get_acceptance_criteria. (Editing customer acceptance items requires explicit client consent in every execution mode, including autopilot.)",
252
254
  "confirm_acceptance_criterion": "Record the **customer's own** confirmation of an acceptance item (free): accepted / rejected / pending (undo). Only the client state is written; the platform auto review and your QC conclusion are untouched. ★Call it only after the customer has explicitly stated their decision, with confirm_client_statement:true; you must never confirm on the customer's behalf, autopilot included.",
253
255
  "delete_acceptance_criterion": "Delete a customer acceptance item (free; its three states go with it). item_id comes from get_acceptance_criteria. (Deleting customer acceptance items requires explicit client consent in every execution mode, including autopilot.)",
254
- "quote_local_frame_edit": "Quote: how many credits a **local edit of one frame of one shot** costs (fix a face, close a mouth, clean halo edges, remove a stray object). Zero cost. Use it when the customer only wants a small part of the image changed — do not edit it in an outside image tool and upload_shot_frame it back: outside tools also change details the customer already approved, bypass the identity anchors and audits, and break the provenance chain. To redraw the whole frame use quote_shot_frame / generate_shot_frame. The platform pastes the edited area back onto the original through a feathered mask: pixels outside the boxes stay unchanged. ★Billing: each candidate is charged as one image (unit_points × candidates; the image count is fixed, so estimated_points is the actual charge and equals typical_points). **Candidates rejected by the platform checks (visible seam, edit not applied, identity audit failed) are never refunded** — tell the customer this together with the points. Vendor failures are refunded automatically. ★Rolled out per drama and per edit_kind; otherwise 403 LOCAL_FRAME_EDIT_UNAVAILABLE, whose open_kinds lists the kinds open to every drama (e.g. object_remove) — for any other kind fall back to generate_shot_frame. Face edits without character_id → 400 CHARACTER_REQUIRED; face edits do not send the character portrait (only the cropped base), identity is checked by the audit after pasting back.",
256
+ "quote_local_frame_edit": "Quote: how many credits a **local edit of one frame of one shot** costs (fix a face, close a mouth, clean halo edges, remove a stray object). Zero cost. Use it when the customer only wants a small part of the image changed — do not edit it in an outside image tool and upload_shot_frame it back: outside tools also change details the customer already approved, bypass the identity anchors and audits, and break the provenance chain. To redraw the whole frame use quote_shot_frame / generate_shot_frame. The platform pastes the edited area back onto the original through a feathered mask: pixels outside the boxes stay unchanged. ★Billing: each candidate is charged as one image (unit_points × candidates; the image count is fixed, so estimated_points is the actual charge and equals typical_points). **Candidates rejected by the platform checks (visible seam, edit not applied, identity audit failed) are never refunded** — tell the customer this together with the points. Vendor failures are refunded automatically. ★Rolled out per drama and per edit_kind; otherwise 403 LOCAL_FRAME_EDIT_UNAVAILABLE, whose open_kinds lists the kinds open to every drama (e.g. object_remove) — for any other kind fall back to generate_shot_frame. Face edits without character_id → 400 CHARACTER_REQUIRED; face edits do not send the character portrait (only the cropped base), after pasting back the face is compared against the original to check it is still the same person (a clear mismatch is rejected, an uncertain one is left for a human; both are charged as usual).",
255
257
  "local_frame_edit": "After confirmation, **locally edit one frame of one shot** (async, charged). Parameters must equal the quote_local_frame_edit call exactly (any change to regions / instruction / kind / character / candidates / base image → 409 QUOTE_PARAMS_CHANGED; re-quote for free). Returns edit_id; poll with get_local_frame_edit. ★**It never replaces the current frame automatically**: show the candidates (composited_url) to the customer first; only adopt_local_frame_edit writes the chosen one into the shot and marks the shot video as needing regeneration. ★Rejected candidates are charged and not refunded. Requires the review_storyboards review_token (and asset_review_token on rollout dramas), same as generate_shot_frame.",
256
- "get_local_frame_edit": "Check progress and results of a local frame edit (free). status=running → keep waiting; when completed, look at each candidate: composited_url (the result pasted back onto the original — show this to the customer), verdict (pass = usable; warn = usable but needs a human look — REGION_SEAM_WARN possible seam / EDIT_NOT_APPLIED barely changed / IDENTITY_AUDIT_UNAVAILABLE identity not audited; reject = not recommended — REGION_MISALIGNED misaligned paste / IDENTITY_AUDIT_REJECTED frame audit failed), metrics, identity_audit, identity_compare (face edits: advisory only — a low score adds IDENTITY_COMPARE_LOW and downgrades to warn). ★Always check face edits with your own eyes: the platform's identity comparison cannot tell look-alikes apart. Download and look at composited_url yourself, tell the customer, then adopt_local_frame_edit the one they pick. Rejected candidates are not refunded.",
258
+ "get_local_frame_edit": "Check progress and results of a local frame edit (free). status=running → keep waiting; when completed, look at each candidate: composited_url (the result pasted back onto the original — show this to the customer), verdict (pass = usable; warn = usable but needs a human look — REGION_SEAM_WARN possible seam / EDIT_NOT_APPLIED barely changed / IDENTITY_NEEDS_REVIEW face-comparison score in the grey zone, not auto-approved / IDENTITY_NOT_JUDGEABLE side view, looking down or face too small to judge / IDENTITY_COMPARE_LOW legacy identity score low / IDENTITY_AUDIT_UNAVAILABLE identity not audited; reject = not recommended — REGION_MISALIGNED misaligned paste / IDENTITY_MISMATCH face comparison says it became a different person / IDENTITY_AUDIT_REJECTED frame audit failed, possibly a composition issue already present in the original frame), reason_text (a plain-language explanation of each reason — relay it to the customer as is), metrics, identity_audit, identity_compare (face edits: with method=face_embedding the decision uses face embeddings — similarity to the same face before the edit ≥0.75 passes, <0.60 is a mismatch, in between goes to a human; vlm_reference is recorded only. Without a method field it is the legacy advisory score). ★Still check face edits with your own eyes — the platform cannot judge side views, downward-looking or small faces. Download and look at composited_url yourself, tell the customer, then adopt_local_frame_edit the one they pick. Rejected candidates are not refunded (including those failing the frame audit because of an issue already in the original frame).",
257
259
  "adopt_local_frame_edit": "Adopt a local-edit candidate as this shot's current frame (free). Same follow-up as an uploaded frame: the shot video is marked for regeneration and adjacent seams are repaired. ★Only after the customer has looked and chosen. If the frame changed after the edit → 409 BASE_FRAME_CHANGED (start a new edit on the current frame); verdict=reject candidates are refused by default (409 CANDIDATE_REJECTED) — pass accept_rejected:true only when the customer insists after seeing it.",
258
260
  "audit_seam_directions": "Pre-video seam direction check (advisory, never blocks any step): for every pair of adjacent shots, compares \"previous shot's last frame (or first frame if none) ↔ this shot's first frame\" for the same subject jumping left/right sides of the frame (a reversed eyeline is noted with it), reversed motion direction, and shot-size jumps (same subject, extreme long ↔ close-up). Eyeline alone and key-light direction are observed but not reported: production replay showed too many false positives on those (better to miss than to nag). Shots that look fine alone but jump when cut together usually fail on these. Complements audit_seams, which checks identity/wardrobe/props/object state but not direction or position. ★When: after first frames (and needed last frames) are done and chain_frames has run, before review_frames / generate_videos. Scene changes, declared jump cuts, insert shots and title/black cards are skipped automatically. ★dry_run first (default, free): returns the seams to observe plus estimated_points (upper bound) and typical_points (usual cost); tell the client both — billed by actual text tokens. After the client agrees, pass dry_run=false + confirm_usage_billing=true. Execution is asynchronous and returns immediately; read results with get_seam_directions a minute or two later. Seams already observed with unchanged frames are not billed again.",
259
261
  "get_seam_directions": "(Free) Read audit_seam_directions results: each issue gives the seam shot numbers, the problem (subject jumps sides, reversed motion, shot-size jump) and fixes (chain_shot_from mode=reframe to regenerate this shot's first frame anchored on the previous last frame / update_shot to state facing then generate_shot_frame / align_prev_shot when the previous shot already has video / split_shot to insert an intermediate shot size). Results are advisory: explain them to the client and let the client pick which to fix; intentional reverse shots, turns or impact cuts can be ignored. not_audited>0 = seams not observed yet or frames changed since; running=true = still running. review_frames' seam_direction_advisory is a summary of the same results. This tool only looks at pre-video still frames; after videos are generated, check seams with preview_seams (free short-clip previews)."
@@ -55,9 +55,9 @@
55
55
  "generate_frames": "确认后批量出帧:后台异步。用 get_storyboards 轮询,first_frame_image/last_frame_image 逐镜填充即完成。★图片生成较慢——每张几十秒到数分钟(尤其高清模型),整集可能十几分钟。轮询看 frame_status:**pending=还在生成(继续耐心等,别当失败、别重复调 generate_frames,重复触发=白花钱)**、ready=完成、failed=才是真失败。别因为「等了一会儿还没出」就判定生成失败或重试。出视频前必须先出帧,否则视频会退化成无一致性锚点的画面。★**出帧前先把两个锚备齐**:定妆图(generate_portraits_and_sheets,锚人)与空景基板(generate_scene_images,锚景)。缺基板不会拦你,但每个场景的第一镜会没有背景锚(平台的兜底补图只惠及同场景后续镜),背景从首镜起就开始漂。用 get_pipeline_status 的 generate_scene_images 步核对 completed/total。frame_type 要与 quote_frames 用的一致(默认 first_frame)。★这是**批量补缺帧**:只给「缺该帧」的镜出图,已有首帧的镜会跳过——这是正常设计、不是\"系统拒绝重出\"。要**重出/重画某一镜已有的帧**(如换了定妆图要让新图生效),用 generate_shot_frame(单镜重生,平台带身份锚),不是这个工具、更不是自制图 upload_shot_frame。★**本工具默认只出首帧,出帧是两趟**:首帧出完必须调一次免费的 tail_frame_plan——它按平台自己的判据告出哪几镜需要独立尾帧(末态≠首态:大运镜/物体脱手/状态改变,占比约三成,你自己猜不出来),再用 frame_type=last_frame 批量补。生产实测 32 集里 30 集整集只出了首帧、其中 23 集一路出完视频:那些镜出视频时只有首帧一个锚,末态由模型自由发挥。本工具响应体里的 shots_needing_last_frame 就是这个数,不为 0 就别直接去 review_frames。★响应里的 frames_planned 是**计划数,不是已成功数**——本接口在后台派发循环开跑之前就返回了。真实进度只看 get_storyboards 的 first_frame_image / get_jobs 的逐条生成记录;余额不足(402)会中止整批,此时轮询再久也不会有结果,应去查余额而不是继续等。★★**个别镜反复出不来、其余镜都好了**:那不是等得不够久,是这几镜被内容闸连拒。看 get_storyboards 的 fail_reason(repeat_rejected / diagnosis_required / needs_content_fix / contract_rejected),若 diagnosis_required,先按 fail_hint 诊断已有候选;审计错误先重审,证据不足先复核,不要原样重抽或擅自改剧本。然后**跑 run_precheck**(事后也能跑,会告诉你是哪种矛盾),按它的提示用 update_shot 改景别/描述/绑定角色再重生。别继续 generate_frames 空转,也别把客户支去外部工具做图——外部图绕开身份锚与帧审计,人脸服装画风必漂。\n★【分镜审查硬闸·免费】本步前必须先调 review_storyboards:把返回的 findings 逐条原样告诉客户(每条带 code=问题类型、shots=命中镜号、action=该调哪个工具修),再把 review_token 传进本工具。未审查会被 400 拒。审查后又改了内容 → token 自动失效,复审一次即可(仍免费)。单镜工具(generate_shot_frame / regenerate_shot_video)只核本镜与相邻镜:改了别的镜不用复审;本镜、相邻镜或共享资产变了才 400——这时带 base_review_token=<旧 token> 调审查工具做增量复审,只重审变更镜,质检也只看变更镜。有 error 时默认拦截;客户知情并坚持照现状继续,才带 acknowledge_review:true——带病推进大概率产出废片且照常扣费(acknowledge_review 只代表客户本人同意,任何执行模式都须客户明确同意,托管也不例外);托管环节只有白名单内的启发式 error 能带 auto_acknowledge:true 放行(平台判,不在白名单回 400 AUTOPILOT_MUST_ASK:must_ask_customer=true 才暂停问客户,false = 按 action 自修复审)。★灰度剧还要你(agent)亲自质检:按 review_storyboards 返回的 agent_qc 逐件看素材、逐条答清单,调 submit_qc_report 提交后这张 token 才能用(没交 → 400 agent_qc_required;报告有问题 → 与 error 同样默认拦)。\n★【资产层硬闸·免费】还要先跑 review_assets(角色形象图/场景图/道具图),把它的 review_token 作为 asset_review_token 传进来(与分镜层的 review_token 并存,两张都要;灰度剧缺了会 400 并点名 review_assets)。出完帧不会让资产 token 失效。⚠️ 批量报价确认流程:先调对应的 quote_* 工具拿 estimated_points 与 quote_id,再用 quote_id 调本工具(报价确认按执行模式:专业模式 / 未确认方案 / 本环节由客户把控 → 讲给客户、客户同意才做,别替客户决定;本环节托管 → 不问客户,照 get_production_plan 的 autopilot.default_decisions 执行,平台记进 get_autopilot_log)。托管环节报价须在预算硬上限内,被 402 BUDGET_WOULD_EXCEED 拦下就停下问客户。★**出图类报价(定妆图/场景图/出帧)给的是两个数**:estimated_points 是**上界**(按该模型参考图张数上限估,保证你不会按低了的预算开跑、出到一半 402 中断);同一响应里的 typical_points 是**通常花费**。两个都要告诉客户——只说上界会让人以为贵得多(满参考图的上界可达通常值的 2 倍以上),只说通常值又会让余额准备不足。固定价模型下两者相等,这时说一个数就行。出视频的报价与扣费同函数,不存在这个区间。",
56
56
  "quote_shot_frame": "报价:重画/补出**某一镜的某一帧**要多少点(一帧=一张图)。返回 estimated_points、quote_id。零扣费。客户说「第 N 镜画错了/要改」时用它,而不是拿别的图像平台出图再 upload_shot_frame。★响应带 billing_kind/unit_points(实际计费档与单价);要在 generate_shot_frame 里换模型,报价时传同一个 image_model。★两边必须传同一个 image_model —— 不一致会被直接拒(400 IMAGE_MODEL_MISMATCH),因为模型决定计费档,不同源就是「预估≠扣费」。两边都不传也算一致(用该剧设定的模型)。已下架的型号(FLUX 全系列)一律拒收,别再传。★返回两个数:estimated_points 是**上界**(按参考图张数上限估,拿它准备余额就不会中途 402),typical_points 是**通常花费**。两个都告诉客户,只报上界会让人以为贵得多。固定价模型(如 Nano Banana 系列)下两者相等。",
57
57
  "generate_shot_frame": "确认后给**某一镜**出帧或重生该帧(异步)。这是修某一镜画面的正路:平台会带上该镜的角色身份锚、场景/道具参考图、画风锚与帧审计,重生出的图与全片一致;用外部工具出图再上传会绕开这整条链路,人物/服装/画风必漂。重生会覆盖该帧现有图(含此前上传的),并把本镜视频标为待重生。尾帧需本镜首帧已就绪(否则先用 first_frame 或 both)。完成判据:轮询 get_storyboards 看该镜 first_frame_image/last_frame_image 变化。\n★继承重开(reopen_pair_id):get_storyboards 某镜带这个字段时,原样传进来可以只重掷首尾帧里真正有问题的那一侧——另一侧此前已经生成好的候选原样保留,不重新生成、不重新计费。传了它就不用再传 frame_type(会被忽略,由平台判定该重哪一侧);quote_id 仍要用 quote_shot_frame 报价(frame_type 传 first_frame 或 last_frame 均可,单帧同价)。若该镜没有 reopen_pair_id 字段(不满足继承条件),这个参数不要传,走常规 frame_type 重试。\n★★出尾帧若被拦下并提示「此镜标为状态改变,但没有任何地方说明结束时是什么样子」:正解是用 update_shot 把结束状态写进 last_frame_prompt 再出,**别直接加 allow_missing_terminal 硬上**——这类镜照现状出尾帧的历史成功率只有 9.4%(有终态描述的 40.2%),硬上多半是白扣一次钱。只有确认这一镜就是要「几乎不变」的画面时才带那个参数。\n★一键出片托管环节:同一镜重出帧最多 3 轮(一次调用算一轮,both 也算一轮),到上限回 **409 AUTOPILOT_SHOT_REGEN_LIMIT**(未派发、未扣费,quote 未消耗)——别换个工具绕,停下问客户:客户同意再出就照常报价、生成时带 customer_approved_more_regens:true(再给一份上限);客户接受现状就往下走,下一步收费调用带 acknowledge_review:true 记下确认。专业模式与客户把控的环节不受这个上限约束。\n★【分镜审查硬闸·免费】本步前必须先调 review_storyboards:把返回的 findings 逐条原样告诉客户(每条带 code=问题类型、shots=命中镜号、action=该调哪个工具修),再把 review_token 传进本工具。未审查会被 400 拒。审查后又改了内容 → token 自动失效,复审一次即可(仍免费)。单镜工具(generate_shot_frame / regenerate_shot_video)只核本镜与相邻镜:改了别的镜不用复审;本镜、相邻镜或共享资产变了才 400——这时带 base_review_token=<旧 token> 调审查工具做增量复审,只重审变更镜,质检也只看变更镜。有 error 时默认拦截;客户知情并坚持照现状继续,才带 acknowledge_review:true——带病推进大概率产出废片且照常扣费(acknowledge_review 只代表客户本人同意,任何执行模式都须客户明确同意,托管也不例外);托管环节只有白名单内的启发式 error 能带 auto_acknowledge:true 放行(平台判,不在白名单回 400 AUTOPILOT_MUST_ASK:must_ask_customer=true 才暂停问客户,false = 按 action 自修复审)。★灰度剧还要你(agent)亲自质检:按 review_storyboards 返回的 agent_qc 逐件看素材、逐条答清单,调 submit_qc_report 提交后这张 token 才能用(没交 → 400 agent_qc_required;报告有问题 → 与 error 同样默认拦)。\n★【资产层硬闸·免费】还要先跑 review_assets(角色形象图/场景图/道具图),把它的 review_token 作为 asset_review_token 传进来(与分镜层的 review_token 并存,两张都要;灰度剧缺了会 400 并点名 review_assets)。出完帧不会让资产 token 失效。⚠️ 批量报价确认流程:先调对应的 quote_* 工具拿 estimated_points 与 quote_id,再用 quote_id 调本工具(报价确认按执行模式:专业模式 / 未确认方案 / 本环节由客户把控 → 讲给客户、客户同意才做,别替客户决定;本环节托管 → 不问客户,照 get_production_plan 的 autopilot.default_decisions 执行,平台记进 get_autopilot_log)。托管环节报价须在预算硬上限内,被 402 BUDGET_WOULD_EXCEED 拦下就停下问客户。★**出图类报价(定妆图/场景图/出帧)给的是两个数**:estimated_points 是**上界**(按该模型参考图张数上限估,保证你不会按低了的预算开跑、出到一半 402 中断);同一响应里的 typical_points 是**通常花费**。两个都要告诉客户——只说上界会让人以为贵得多(满参考图的上界可达通常值的 2 倍以上),只说通常值又会让余额准备不足。固定价模型下两者相等,这时说一个数就行。出视频的报价与扣费同函数,不存在这个区间。",
58
- "quote_local_frame_edit": "报价:**局部修改某一镜的某一帧**(修脸、闭嘴、去毛边、擦掉多出来的东西)要多少点。零扣费。客户只想改画面的一小块时用它,别拿外部图像工具改完再 upload_shot_frame——外部工具会连带改动已确认的其他细节、绕开身份锚与审计、来源链也断了;要整张重画才用 quote_shot_frame / generate_shot_frame。平台把修改区按羽化蒙版贴回原图:框外像素原样保留,只有框内被改。★计费:每个候选按一次出图扣(unit_points × candidates;张数固定,estimated_points 就是实扣,typical_points 与它相等)。**被平台判据拒掉的候选(贴回有接缝、没改到、身份审计不过)一律不退点**——报价时就要把这一点和点数一起讲给客户。厂商出图失败由平台自动退。★按剧、按类型灰度开放:没开放回 403 LOCAL_FRAME_EDIT_UNAVAILABLE,响应里 open_kinds 列出已对所有剧开放的类型(如 object_remove);要改的类型不在里面就只能 generate_shot_frame 整张重画。修脸类不带 character_id 回 400 CHARACTER_REQUIRED;修脸不送定妆图(只送底图局部),贴回后靠身份审计判是不是同一个人。",
58
+ "quote_local_frame_edit": "报价:**局部修改某一镜的某一帧**(修脸、闭嘴、去毛边、擦掉多出来的东西)要多少点。零扣费。客户只想改画面的一小块时用它,别拿外部图像工具改完再 upload_shot_frame——外部工具会连带改动已确认的其他细节、绕开身份锚与审计、来源链也断了;要整张重画才用 quote_shot_frame / generate_shot_frame。平台把修改区按羽化蒙版贴回原图:框外像素原样保留,只有框内被改。★计费:每个候选按一次出图扣(unit_points × candidates;张数固定,estimated_points 就是实扣,typical_points 与它相等)。**被平台判据拒掉的候选(贴回有接缝、没改到、身份审计不过)一律不退点**——报价时就要把这一点和点数一起讲给客户。厂商出图失败由平台自动退。★按剧、按类型灰度开放:没开放回 403 LOCAL_FRAME_EDIT_UNAVAILABLE,响应里 open_kinds 列出已对所有剧开放的类型(如 object_remove);要改的类型不在里面就只能 generate_shot_frame 整张重画。修脸类不带 character_id 回 400 CHARACTER_REQUIRED;修脸不送定妆图(只送底图局部),贴回后比对人脸判是不是同一个人(判不像的拒、拿不准的交人看,都照常计费)。",
59
59
  "local_frame_edit": "确认后**局部修改某一镜的某一帧**(异步,扣费)。参数必须与 quote_local_frame_edit 那次完全相同(区域 / 指令 / 类型 / 角色 / 候选数 / 底图任一项变了 → 409 QUOTE_PARAMS_CHANGED,重新报价即可,免费)。返回 edit_id,之后用 get_local_frame_edit 轮询。★**不会自动替换当前帧**:候选出完先给客户看(composited_url),客户选定后调 adopt_local_frame_edit 才写进这一镜、并把本镜视频标为待重生;不采用,当前帧就不变。★被判据拒掉的候选照常计费、不退点。\n★【分镜审查硬闸·免费】本步前必须先调 review_storyboards:把返回的 findings 逐条原样告诉客户(每条带 code=问题类型、shots=命中镜号、action=该调哪个工具修),再把 review_token 传进本工具。未审查会被 400 拒。审查后又改了内容 → token 自动失效,复审一次即可(仍免费)。单镜工具(generate_shot_frame / regenerate_shot_video)只核本镜与相邻镜:改了别的镜不用复审;本镜、相邻镜或共享资产变了才 400——这时带 base_review_token=<旧 token> 调审查工具做增量复审,只重审变更镜,质检也只看变更镜。有 error 时默认拦截;客户知情并坚持照现状继续,才带 acknowledge_review:true——带病推进大概率产出废片且照常扣费(acknowledge_review 只代表客户本人同意,任何执行模式都须客户明确同意,托管也不例外);托管环节只有白名单内的启发式 error 能带 auto_acknowledge:true 放行(平台判,不在白名单回 400 AUTOPILOT_MUST_ASK:must_ask_customer=true 才暂停问客户,false = 按 action 自修复审)。★灰度剧还要你(agent)亲自质检:按 review_storyboards 返回的 agent_qc 逐件看素材、逐条答清单,调 submit_qc_report 提交后这张 token 才能用(没交 → 400 agent_qc_required;报告有问题 → 与 error 同样默认拦)。\n★【资产层硬闸·免费】还要先跑 review_assets(角色形象图/场景图/道具图),把它的 review_token 作为 asset_review_token 传进来(与分镜层的 review_token 并存,两张都要;灰度剧缺了会 400 并点名 review_assets)。出完帧不会让资产 token 失效。⚠️ 批量报价确认流程:先调对应的 quote_* 工具拿 estimated_points 与 quote_id,再用 quote_id 调本工具(报价确认按执行模式:专业模式 / 未确认方案 / 本环节由客户把控 → 讲给客户、客户同意才做,别替客户决定;本环节托管 → 不问客户,照 get_production_plan 的 autopilot.default_decisions 执行,平台记进 get_autopilot_log)。托管环节报价须在预算硬上限内,被 402 BUDGET_WOULD_EXCEED 拦下就停下问客户。★**出图类报价(定妆图/场景图/出帧)给的是两个数**:estimated_points 是**上界**(按该模型参考图张数上限估,保证你不会按低了的预算开跑、出到一半 402 中断);同一响应里的 typical_points 是**通常花费**。两个都要告诉客户——只说上界会让人以为贵得多(满参考图的上界可达通常值的 2 倍以上),只说通常值又会让余额准备不足。固定价模型下两者相等,这时说一个数就行。出视频的报价与扣费同函数,不存在这个区间。",
60
- "get_local_frame_edit": "查一次局部返修的进度与结果(免费)。status=running 继续等;completed 后逐个候选看:composited_url(贴回原图后的成品,给客户看的就是它)、verdict(pass=可直接用;warn=能用但要人看——REGION_SEAM_WARN 边界可能有接缝 / EDIT_NOT_APPLIED 基本没改到 / IDENTITY_AUDIT_UNAVAILABLE 身份没审成;reject=不建议用——REGION_MISALIGNED 贴回错位 / IDENTITY_AUDIT_REJECTED 帧审计不过)、metrics、identity_audit、identity_compare(修脸类身份比对:只作参考,分偏低记 IDENTITY_COMPARE_LOW 降为 warn)。★修脸结果一定要亲眼看是不是同一个人——平台的身份比对分不开长相接近的人。下载 composited_url 亲眼看过、讲给客户,客户选定后用 adopt_local_frame_edit。被拒候选不退点。",
60
+ "get_local_frame_edit": "查一次局部返修的进度与结果(免费)。status=running 继续等;completed 后逐个候选看:composited_url(贴回原图后的成品,给客户看的就是它)、verdict(pass=可直接用;warn=能用但要人看——REGION_SEAM_WARN 边界可能有接缝 / EDIT_NOT_APPLIED 基本没改到 / IDENTITY_NEEDS_REVIEW 修脸后人脸比对分在灰区、平台不自动放行 / IDENTITY_NOT_JUDGEABLE 侧脸·低头·脸太小等判不了 / IDENTITY_COMPARE_LOW 旧版身份比对分偏低 / IDENTITY_AUDIT_UNAVAILABLE 身份没审成;reject=不建议用——REGION_MISALIGNED 贴回错位 / IDENTITY_MISMATCH 修脸后人脸比对判定换了人 / IDENTITY_AUDIT_REJECTED 帧审计不过,可能是原帧本身的构图问题)、reason_text(每条原因的中文说明,原样讲给客户)、metrics、identity_audit、identity_compare(修脸类身份比对:method=face_embedding 时按人脸特征向量判——与修改前同一张脸相似度 ≥0.75 放行、<0.60 判不像、中间交人看,vlm_reference 只记录;没有 method 字段的是旧版,只作参考)。★修脸结果仍要亲眼看是不是同一个人——侧脸、低头、小脸时平台判不了。下载 composited_url 亲眼看过、讲给客户,客户选定后用 adopt_local_frame_edit。被拒候选不退点(包括原帧本身问题导致帧审计不过的)。",
61
61
  "adopt_local_frame_edit": "采用某个局部返修候选:把它写成这一镜的当前帧(免费)。与上传帧同一套后续处理:本镜视频标为待重生、相邻接缝跟着修。★只在客户看过并选定后调用。返修之后这一帧若已换过 → 409 BASE_FRAME_CHANGED(对当前帧重新发起);verdict=reject 的候选默认拒绝采用(409 CANDIDATE_REJECTED),客户看过坚持要用才带 accept_rejected:true。",
62
62
  "quote_videos": "报价:给某一集批量出视频要多少点(与实际扣费同函数,较准)。返回 estimated_points、quote_id。零扣费。⚠️ 视频是大额花费,务必把点数清楚告诉用户并等其确认。\n★**只计这次真会派发的镜**:已出视频的镜整集续跑时跳过,不重出不计价——shots_total = 这次计价的镜数(不是集内总镜数),shots_already_done = 已出视频、这次跳过的镜数。例外:场景组只要有一镜没出视频就**整组重出**,组内已出的镜同样计价。全部已出视频 ⇒ 400(没有可派发的镜,要重出某镜用 regenerate_shot_video)。报价之后若有镜的视频被清空(变成待出视频),generate_videos 会 409 要求重新报价,不按报价外的镜扣费。\n★**按镜指定视频引擎**:传 shot_engines={分镜id: 引擎} 即只为这些镜、这一次整集生成换引擎(不改剧级 video_engine,其余镜照旧)。报价按它逐镜计价,quote_id 把引擎钉死,generate_videos 派发的就是它——要改引擎必须重新报价。回执 shot_engines = 实际生效的映射;shot_engines_ungrouped = 因含指定镜而**拆成逐镜出**的场景组(场景组的时长与多关键帧是按剧引擎规划的,换引擎整组出会丢镜/丢关键帧;拆组后组内其余镜仍按剧引擎逐镜出,失去组内一次成片的连贯性,所以别为一镜去拆一个好好的组——先看 get_scene_group_plan)。所选引擎必须支持本剧分辨率(seedance-2.5 只有 480p/720p;hailuo-3 只有 720p/1080p;wan3.0/wan3.0-prime 480p~1080p),且镜头时长不超过该引擎单镜上限(seedance-2.5/wan3.0 30s,hailuo-3 15s),否则 400(超长会被厂商截断,要么换引擎要么 split_shot)。指定引擎的镜必须这次会出视频:已出视频的镜整集生成不会重出,给它指定引擎 ⇒ 400;拆成逐镜的场景组只计没出视频的组员。只想重出个别已出过视频的镜,用 quote_regenerate_shot_video 的 model 更省。",
63
63
  "generate_videos": "确认后批量出视频:一条后台链跑完整集,余额不足会自动中止整链(防重复扣)。要求本集已出首帧(未出会被拒)。用 get_storyboards 轮询 video_url 逐镜填充即完成。\n★【镜头图片审查硬闸·免费】本步前必须先调 review_frames:把返回的 findings 逐条原样告诉客户(每条带 code=问题类型、shots=命中镜号、action=该调哪个工具修),再把 review_token 传进本工具。未审查会被 400 拒。审查后又改了内容 → token 自动失效,复审一次即可(仍免费)。单镜工具(generate_shot_frame / regenerate_shot_video)只核本镜与相邻镜:改了别的镜不用复审;本镜、相邻镜或共享资产变了才 400——这时带 base_review_token=<旧 token> 调审查工具做增量复审,只重审变更镜,质检也只看变更镜。有 error 时默认拦截;客户知情并坚持照现状继续,才带 acknowledge_review:true——带病推进大概率产出废片且照常扣费(acknowledge_review 只代表客户本人同意,任何执行模式都须客户明确同意,托管也不例外);托管环节只有白名单内的启发式 error 能带 auto_acknowledge:true 放行(平台判,不在白名单回 400 AUTOPILOT_MUST_ASK:must_ask_customer=true 才暂停问客户,false = 按 action 自修复审)。★灰度剧还要你(agent)亲自质检:按 review_frames 返回的 agent_qc 逐件看素材、逐条答清单,调 submit_qc_report 提交后这张 token 才能用(没交 → 400 agent_qc_required;报告有问题 → 与 error 同样默认拦)。⚠️ 批量报价确认流程:先调对应的 quote_* 工具拿 estimated_points 与 quote_id,再用 quote_id 调本工具(报价确认按执行模式:专业模式 / 未确认方案 / 本环节由客户把控 → 讲给客户、客户同意才做,别替客户决定;本环节托管 → 不问客户,照 get_production_plan 的 autopilot.default_decisions 执行,平台记进 get_autopilot_log)。托管环节报价须在预算硬上限内,被 402 BUDGET_WOULD_EXCEED 拦下就停下问客户。★**出图类报价(定妆图/场景图/出帧)给的是两个数**:estimated_points 是**上界**(按该模型参考图张数上限估,保证你不会按低了的预算开跑、出到一半 402 中断);同一响应里的 typical_points 是**通常花费**。两个都要告诉客户——只说上界会让人以为贵得多(满参考图的上界可达通常值的 2 倍以上),只说通常值又会让余额准备不足。固定价模型下两者相等,这时说一个数就行。出视频的报价与扣费同函数,不存在这个区间。\n★【完成后质检·免费】出完等它落库(get_storyboards 看 video_status / get_pipeline_status),再调 review_videos 审视频与配音层(缺视频 / 旧帧出的视频 / 片尾硬切 / 意外人声 / 配音缺失 / 音色未分配 / 原声声线过期),修完复审;再按 agent_qc 亲自逐镜看视频、听配音(动作到位、不变脸、台词念全、嗓音对),submit_qc_report 提交;compose_episode 要带它的 review_token。",
@@ -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
  }
@@ -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
  ];
@@ -1013,15 +1013,16 @@ export function registerProduceTools(server, client) {
1013
1013
  '★计费:每个候选按一次出图扣(unit_points × candidates;张数固定,estimated_points 就是实扣,typical_points 与它相等)。' +
1014
1014
  '**被平台判据拒掉的候选(贴回有接缝、没改到、身份审计不过)一律不退点**——报价时就要把这一点和点数一起讲给客户。厂商出图失败由平台自动退。' +
1015
1015
  '★按剧、按类型灰度开放:没开放回 403 LOCAL_FRAME_EDIT_UNAVAILABLE,响应里 open_kinds 列出已对所有剧开放的类型(如 object_remove);要改的类型不在里面就只能 generate_shot_frame 整张重画。' +
1016
- '修脸类不带 character_id 回 400 CHARACTER_REQUIRED;修脸不送定妆图(只送底图局部),贴回后靠身份审计判是不是同一个人。', LOCAL_EDIT_ARGS, async ({ storyboard_id, ...rest }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/frame/local-edit/quote`, rest)));
1016
+ '修脸类不带 character_id 回 400 CHARACTER_REQUIRED;修脸不送定妆图(只送底图局部),贴回后比对人脸判是不是同一个人(判不像的拒、拿不准的交人看,都照常计费)。', LOCAL_EDIT_ARGS, async ({ storyboard_id, ...rest }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/frame/local-edit/quote`, rest)));
1017
1017
  server.tool('local_frame_edit', '确认后**局部修改某一镜的某一帧**(异步,扣费)。参数必须与 quote_local_frame_edit 那次完全相同(区域 / 指令 / 类型 / 角色 / 候选数 / 底图任一项变了 → 409 QUOTE_PARAMS_CHANGED,重新报价即可,免费)。' +
1018
1018
  '返回 edit_id,之后用 get_local_frame_edit 轮询。' +
1019
1019
  '★**不会自动替换当前帧**:候选出完先给客户看(composited_url),客户选定后调 adopt_local_frame_edit 才写进这一镜、并把本镜视频标为待重生;不采用,当前帧就不变。' +
1020
1020
  '★被判据拒掉的候选照常计费、不退点。' + REVIEW_GATE_HINT('review_storyboards', '分镜') + ASSET_GATE_HINT + CONFIRM_HINT, { ...LOCAL_EDIT_ARGS, quote_id: z.string().describe('来自 quote_local_frame_edit'), ...REVIEW_ARGS, ...ASSET_REVIEW_ARG }, async ({ storyboard_id, ...rest }) => jsonResult(await client.producePost(`/storyboards/${storyboard_id}/frame/local-edit/generate`, rest)));
1021
1021
  server.tool('get_local_frame_edit', '查一次局部返修的进度与结果(免费)。status=running 继续等;completed 后逐个候选看:' +
1022
- 'composited_url(贴回原图后的成品,给客户看的就是它)、verdict(pass=可直接用;warn=能用但要人看——REGION_SEAM_WARN 边界可能有接缝 / EDIT_NOT_APPLIED 基本没改到 / IDENTITY_AUDIT_UNAVAILABLE 身份没审成;' +
1023
- 'reject=不建议用——REGION_MISALIGNED 贴回错位 / IDENTITY_AUDIT_REJECTED 帧审计不过)、metrics、identity_audit、identity_compare(修脸类身份比对:只作参考,分偏低记 IDENTITY_COMPARE_LOW 降为 warn)。★修脸结果一定要亲眼看是不是同一个人——平台的身份比对分不开长相接近的人。' +
1024
- '下载 composited_url 亲眼看过、讲给客户,客户选定后用 adopt_local_frame_edit。被拒候选不退点。', { storyboard_id: z.number().int().positive(), edit_id: z.string().describe('来自 local_frame_edit') }, async ({ storyboard_id, edit_id }) => jsonResult(await client.produceGet(`/storyboards/${storyboard_id}/frame/local-edit/${encodeURIComponent(edit_id)}`)));
1022
+ 'composited_url(贴回原图后的成品,给客户看的就是它)、verdict(pass=可直接用;warn=能用但要人看——REGION_SEAM_WARN 边界可能有接缝 / EDIT_NOT_APPLIED 基本没改到 / IDENTITY_NEEDS_REVIEW 修脸后人脸比对分在灰区、平台不自动放行 / IDENTITY_NOT_JUDGEABLE 侧脸·低头·脸太小等判不了 / IDENTITY_COMPARE_LOW 旧版身份比对分偏低 / IDENTITY_AUDIT_UNAVAILABLE 身份没审成;' +
1023
+ 'reject=不建议用——REGION_MISALIGNED 贴回错位 / IDENTITY_MISMATCH 修脸后人脸比对判定换了人 / IDENTITY_AUDIT_REJECTED 帧审计不过,可能是原帧本身的构图问题)、reason_text(每条原因的中文说明,原样讲给客户)、metrics、identity_audit、' +
1024
+ 'identity_compare(修脸类身份比对:method=face_embedding 时按人脸特征向量判——与修改前同一张脸相似度 ≥0.75 放行、<0.60 判不像、中间交人看,vlm_reference 只记录;没有 method 字段的是旧版,只作参考)。★修脸结果仍要亲眼看是不是同一个人——侧脸、低头、小脸时平台判不了。' +
1025
+ '下载 composited_url 亲眼看过、讲给客户,客户选定后用 adopt_local_frame_edit。被拒候选不退点(包括原帧本身问题导致帧审计不过的)。', { storyboard_id: z.number().int().positive(), edit_id: z.string().describe('来自 local_frame_edit') }, async ({ storyboard_id, edit_id }) => jsonResult(await client.produceGet(`/storyboards/${storyboard_id}/frame/local-edit/${encodeURIComponent(edit_id)}`)));
1025
1026
  server.tool('adopt_local_frame_edit', '采用某个局部返修候选:把它写成这一镜的当前帧(免费)。与上传帧同一套后续处理:本镜视频标为待重生、相邻接缝跟着修。' +
1026
1027
  '★只在客户看过并选定后调用。返修之后这一帧若已换过 → 409 BASE_FRAME_CHANGED(对当前帧重新发起);' +
1027
1028
  'verdict=reject 的候选默认拒绝采用(409 CANDIDATE_REJECTED),客户看过坚持要用才带 accept_rejected:true。', {
@@ -2382,6 +2383,11 @@ export function registerProduceTools(server, client) {
2382
2383
  + 'left/right(两侧在源视频上的入出点 src_in_ms/src_out_ms,已含 trim)、preview_url(短片)、'
2383
2384
  + 'compare_image_url(左=前镜转场开始前最后一帧,右=后镜转场结束后第一帧)、audit(audit_seams 结论;not_enabled=本剧未开启)。'
2384
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`)));
2385
2391
  server.tool('audit_seam_directions', '出视频前的接缝方向对照(提示性,不拦任何步骤):对每对相邻镜拿「上一镜尾帧(没有就首帧)↔ 本镜首帧」看五件事——' +
2386
2392
  '同一主体在画面左右跳边(视线也反了会一并写明)、运动方向反了、景别跳级(同一主体大远景↔特写)。单镜都对、拼起来跳,多半是这几维。' +
2387
2393
  '视线单独反向、主光方向会被观察但不报:生产回放里这两维误报率太高(宁漏不扰)。' +
@@ -2634,6 +2640,7 @@ export function registerProduceTools(server, client) {
2634
2640
  '首尾形变帧一起拼进去。\n' +
2635
2641
  '③ 字幕 cue、dialogue_audio.offset_ms、sfx[].offset_ms 的基准都是「该镜 trim 之后的第 0 毫秒」,' +
2636
2642
  '不是成片绝对时间。你加多少重叠转场都不用改它们——交给 compile_timeline.py 展开,别手算累加。\n' +
2643
+ '\n拼之前先跑 build_av_timing_contract → get_av_timing_contract(免费):逐镜实测源片时长、平台终拼的入出点/转场占用/定格补帧、人声已混入视频还是独立轨,拿它核对你的时间轴总长。' +
2637
2644
  '\n想让平台代拼、要平台级质量闸(终拼预检/音画等长/响度母带),改用 compose_episode。', {
2638
2645
  episode_id: z.number().int().positive(),
2639
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.210",
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.210 tool surface (operationIds match MCP tool names 1: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
  ],
@@ -13902,7 +14005,7 @@
13902
14005
  "get": {
13903
14006
  "operationId": "get_local_frame_edit",
13904
14007
  "summary": "查一次局部返修的进度与结果(免费)",
13905
- "description": "查一次局部返修的进度与结果(免费)。status=running 继续等;completed 后逐个候选看:composited_url(贴回原图后的成品,给客户看的就是它)、verdict(pass=可直接用;warn=能用但要人看——REGION_SEAM_WARN 边界可能有接缝 / EDIT_NOT_APPLIED 基本没改到 / IDENTITY_AUDIT_UNAVAILABLE 身份没审成;reject=不建议用——REGION_MISALIGNED 贴回错位 / IDENTITY_AUDIT_REJECTED 帧审计不过)、metrics、identity_audit、identity_compare(修脸类身份比对:只作参考,分偏低记 IDENTITY_COMPARE_LOW 降为 warn)。★修脸结果一定要亲眼看是不是同一个人——平台的身份比对分不开长相接近的人。下载 composited_url 亲眼看过、讲给客户,客户选定后用 adopt_local_frame_edit。被拒候选不退点。",
14008
+ "description": "查一次局部返修的进度与结果(免费)。status=running 继续等;completed 后逐个候选看:composited_url(贴回原图后的成品,给客户看的就是它)、verdict(pass=可直接用;warn=能用但要人看——REGION_SEAM_WARN 边界可能有接缝 / EDIT_NOT_APPLIED 基本没改到 / IDENTITY_NEEDS_REVIEW 修脸后人脸比对分在灰区、平台不自动放行 / IDENTITY_NOT_JUDGEABLE 侧脸·低头·脸太小等判不了 / IDENTITY_COMPARE_LOW 旧版身份比对分偏低 / IDENTITY_AUDIT_UNAVAILABLE 身份没审成;reject=不建议用——REGION_MISALIGNED 贴回错位 / IDENTITY_MISMATCH 修脸后人脸比对判定换了人 / IDENTITY_AUDIT_REJECTED 帧审计不过,可能是原帧本身的构图问题)、reason_text(每条原因的中文说明,原样讲给客户)、metrics、identity_audit、identity_compare(修脸类身份比对:method=face_embedding 时按人脸特征向量判——与修改前同一张脸相似度 ≥0.75 放行、<0.60 判不像、中间交人看,vlm_reference 只记录;没有 method 字段的是旧版,只作参考)。★修脸结果仍要亲眼看是不是同一个人——侧脸、低头、小脸时平台判不了。下载 composited_url 亲眼看过、讲给客户,客户选定后用 adopt_local_frame_edit。被拒候选不退点(包括原帧本身问题导致帧审计不过的)。",
13906
14009
  "tags": [
13907
14010
  "storyboards"
13908
14011
  ],
@@ -14185,7 +14288,7 @@
14185
14288
  "post": {
14186
14289
  "operationId": "quote_local_frame_edit",
14187
14290
  "summary": "报价:**局部修改某一镜的某一帧**(修脸、闭嘴、去毛边、擦掉多出来的东西)要多少点",
14188
- "description": "报价:**局部修改某一镜的某一帧**(修脸、闭嘴、去毛边、擦掉多出来的东西)要多少点。零扣费。客户只想改画面的一小块时用它,别拿外部图像工具改完再 upload_shot_frame——外部工具会连带改动已确认的其他细节、绕开身份锚与审计、来源链也断了;要整张重画才用 quote_shot_frame / generate_shot_frame。平台把修改区按羽化蒙版贴回原图:框外像素原样保留,只有框内被改。★计费:每个候选按一次出图扣(unit_points × candidates;张数固定,estimated_points 就是实扣,typical_points 与它相等)。**被平台判据拒掉的候选(贴回有接缝、没改到、身份审计不过)一律不退点**——报价时就要把这一点和点数一起讲给客户。厂商出图失败由平台自动退。★按剧、按类型灰度开放:没开放回 403 LOCAL_FRAME_EDIT_UNAVAILABLE,响应里 open_kinds 列出已对所有剧开放的类型(如 object_remove);要改的类型不在里面就只能 generate_shot_frame 整张重画。修脸类不带 character_id 回 400 CHARACTER_REQUIRED;修脸不送定妆图(只送底图局部),贴回后靠身份审计判是不是同一个人。",
14291
+ "description": "报价:**局部修改某一镜的某一帧**(修脸、闭嘴、去毛边、擦掉多出来的东西)要多少点。零扣费。客户只想改画面的一小块时用它,别拿外部图像工具改完再 upload_shot_frame——外部工具会连带改动已确认的其他细节、绕开身份锚与审计、来源链也断了;要整张重画才用 quote_shot_frame / generate_shot_frame。平台把修改区按羽化蒙版贴回原图:框外像素原样保留,只有框内被改。★计费:每个候选按一次出图扣(unit_points × candidates;张数固定,estimated_points 就是实扣,typical_points 与它相等)。**被平台判据拒掉的候选(贴回有接缝、没改到、身份审计不过)一律不退点**——报价时就要把这一点和点数一起讲给客户。厂商出图失败由平台自动退。★按剧、按类型灰度开放:没开放回 403 LOCAL_FRAME_EDIT_UNAVAILABLE,响应里 open_kinds 列出已对所有剧开放的类型(如 object_remove);要改的类型不在里面就只能 generate_shot_frame 整张重画。修脸类不带 character_id 回 400 CHARACTER_REQUIRED;修脸不送定妆图(只送底图局部),贴回后比对人脸判是不是同一个人(判不像的拒、拿不准的交人看,都照常计费)。",
14189
14292
  "tags": [
14190
14293
  "storyboards"
14191
14294
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starreel/mcp",
3
- "version": "0.1.210",
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.210",
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.210",
11
+ "version": "0.1.212",
12
12
  "transport": {
13
13
  "type": "stdio"
14
14
  },