Repository navigation
v1.0.37
Added
-
codex model dropdowns follow the official catalog. The codex model lists (top bar and per-agent model dialog) are now read from
codex debug modelsvia the newGET /api/v1/engines/codex/models(models withvisibility=list, sorted by official priority, cached for 5 minutes), so newly released codex models appear automatically once the installed codex CLI is new enough to receive them. If the CLI is missing or the call fails, the dropdown falls back to a built-in list and its tooltip shows the error. Smart Assignment tiers are unchanged and still set by hand. -
Checked fancy-text types are now expected to appear. In manual caption mode a checked type used to mean "allowed, not required", so the caption agent could silently drop a whole type (fengshen3 ep07: a creature appeared on screen but no creature name card was made although 「生物 / 妖兽名牌」 was checked). Now every checked type that has matching content in the episode (a creature's first appearance, a prop's first reveal, a faction's first showing, …) is expected to get at least one caption; if a type is left out, the reason must be written per type in the new top-level
type_skipsfield ofcaptions.json(e.g.{"creature_card": "no creature appears in this episode"}). A missing template is not a valid reason: the agent creates one or reuses a similar template. New design-stage machine checkcaption_types_covered(manual mode only; types unavailable for the project are not counted). The caption SOUL, the caption-styling skill, WORKFLOW §9A and the injected caption prompt are updated. Existing episodes are not re-run and will fail this check until their caption design is redone. -
Insert-friendly numbering for new projects. Projects created from now on number scenes, generation groups and shots in steps of ten —
S010,S020… /grp0010,grp0020… /sh0010,sh0020… — so a scene, group or shot can later be inserted between two existing ones (S011,grp0011,sh0011) without renumbering anything downstream. The scheme is stamped into the project'ssettings.json#numbering(step: 10) at creation and cannot be changed afterwards; the host injects a 「编号制:预留插入位」 section into every agent's run prompt for these projects only,genmedia'sgrpsh_id_3digitscheck expects four digits there, andcheck_generation_groups.py --proposenumbers its draft groups accordingly (newmodules/id_scheme.py). Existing projects have nonumberingblock and keepS01/grp001/sh001unchanged. -
Project music library. Every project now keeps a library of its score under
assets/audio/library/music/(index.json+MUS-NNNN.<ext>, newmodules/music_library.py). When the music workstation (09-audio/music,p8-music) delivers an episode, the tracks it added — newly generated ones and the user-provided ones it picked — are copied into the library together with their data (mood, genre, tempo, instruments, duration, generation prompt, license chain), and each cue of the episode is recorded as a use of its track (episode, cue, in/out, scene). Later episodes look in the library first: the workstation lists / searches it with the new host CLIcode/music_library.py(list,search,use,sync,annotate,add,check), reuses a fitting track withuse(the cue's license becomessource: library+track_idand carries the original license chain) and generates new music only when nothing fits, stating the reason per cue in the cue sheet'smusic_library_report. Selection order is now user music inrefs/music/→ project library → new generation. A track its source episode no longer uses after a rework is marked replaced and is not offered any more (the file stays). New machine checkmusic_library_syncedonp8-music; episodes delivered before this feature are backfilled and only get a reminder. Project settings → 「音乐库」 opens the new/music-librarypage: all tracks with playback, their data, source episode and the episodes / cues using them, filters by episode, source and keyword, and 「同步入库」 to backfill projects that already have scores (nothing is generated). New endpointsGET /api/v1/projects/{project}/previews/music-libraryandPOST /api/v1/projects/{project}/music-library/sync({ep?}); music and copyright SOULs, WORKFLOW Phase 8 andworkflow.yamlupdated; UI strings translated in all 11 dictionaries. Requires a service restart and a hard refresh. -
「+」attachment button on the console chat box and the preview pages' 「修改」 popup. A round 「+」 at the top-right corner of the input (the voice-input microphone, when enabled, sits below it at the bottom-right) (
apps/web/static/file-attach.js, shared byindex.htmlandedit-popup.js) lets the user pick local files to send along with the message. Nothing is uploaded: the agent runs on the same machine as the browser, so only the absolute paths are appended to the message as a trailing 「附件(本机文件绝对路径,请直接读取):\n- /abs/path」 paragraph (header follows the UI language) and the agent reads the files itself. Paths come from ① the desktop client's native open dialog (videoagentsDesktop.pickFiles, newdesktop:pick-filesIPC), ② in the browser, a native dialog opened by the web server on the host (POST /actions/pick-files: macOS osascript, Windows PowerShell, Linux zenity/kdialog; 409 while one is open, 501 when no dialog is available), or ③ a manual paste box (one absolute path per line; also via Shift+click, and used automatically for a remote backend or when no dialog can be shown). Selected files show as removable chips above the input; the console keeps them with the draft (localStoragewebui_draft_files) and hands them back if sending fails; the popup clears them on every open. Requires a service restart (new web-server endpoint) and a hard refresh; the desktop bridge takes effect with the next desktop build. -
Script preview: 「🗒 注释」 buttons and 「📨 提交注释」. Every 「✏️ 反馈」 button on the script preview page (whole episode, scene, each action / dialogue / narration / transition block, narration entries, hooks, event cards, pacing, trim suggestions, episode plan) now has a 「🗒 注释」 button next to it that opens the same note dialog as the storyboard page; notes are stored in
story/episodes/epNN/script_notes.jsonkeyed by a stable object id (*,S01,S01/b3,nar:ID,hook:ID,event:ID,pacing:S01,trim:S01/n,plan) together with the button's location text (object description + file path), shown in yellow next to the object and in the button tooltip. 「📨 提交注释」 at the right end of the episode row sends all of the episode's notes to the reviser as one revision order (kind=script, files = the episode's screenplay / narration / hooks / pacing / breakdown plusstory/events.json/story/episode_plan.jsonwhen referenced) and clears the file once the run is dispatched. New endpointsPOST /api/v1/projects/{project}/script/{ep}/notes({key, text, label?}, empty text deletes) andPOST …/script/{ep}/notes/submit; the reviser SOUL lists the file as must-read for script-layer objects. Requires a service restart and a hard refresh. -
Storyboard page and storyboard preview: 「📨 提交注释」 submits every note of the episode to the reviser in one go. A new button at the right end of the episode row (both pages) collects all of the episode's notes — the storyboard page's whole-episode / scene / draft-shot notes from
directing/epNN/storyboard_notes.json, or the storyboard preview's per-group director notes fromassets/notes/epNN/<grp>.json— and the host composes a single revision order for the reviser (kind=storyboardorkind=group), listing each note together with the object it is attached to (scene number / location / scene id; draft shot order, content summary and final shot ids; for groups: scene, shot ids, duration, beat and the group-prompt path; stale notes whose object is gone are marked as such). Once the run is dispatched the notes are cleared: the storyboard notes file is deleted and each group note is removed the same way as 「清空保存」 (its injectedDirector's notesentence leaves thevideo_prompt). The button shows the note count, is disabled when the episode has none, asks for confirmation, and the order text follows the UI language (Chinese / English). New endpointsPOST /api/v1/projects/{project}/board/{ep}/notes/submitandPOST /api/v1/storyboard/notes/submit({project, ep}); reviser SOUL and WORKFLOW §5 updated. Requires a service restart and a hard refresh. -
Transition design stage 2: the
07-directing/transition-designworkstation and generated establishing shots. A new per-episode DAG nodep6-transition-design(conditiontransition_design_enabled= effective transition mode ≠ minimal; after shot-planning / continuity / plates, feeding the H3A gate) runs the host CLIcode/transition_design.py proposeand then reviews every group boundary: it checks the change class against the director's transition list and the screenplay's 「转场:」 lines, fixes inferred card text withcard, and picks between candidates with the newdesign --alt N | --design '<json>' --note …verb — proposals stayproposed; accepting is the user's decision on the storyboard preview's transition cards, and signing H3A now accepts every remaining proposal intoshot_list(the sign card shows the pending count, same precedent as H3W). Boundaries decided by the user or the director's list are locked against the workstation (feedbackonly), and re-runningproposeno longer overwrites a workstation design. When the mode allows generative transitions (cinematic / custom), 「换场景 → 定场空镜 + 叠地点字幕」 is now a real image-to-video establishing clip (source.mode = i2v): the host renders the first-frame still from the scene panorama anchor or the first shot's plate (clips --prepare) and composes the prompt; because this involves video generation, the clip itself is produced in Phase 7 by the new per-episode nodep7-transition-clips(video-generation, conditiontransition_clips_requested, after H3A and before H3B) intoassets/transitions/epNN/<B-id>.establishing.mp4, checked byclips --check(transition_clips_ready);render_transitions.py buildconsumes the file (trim to the insert length, location overlay composited as before) and reportsmissing/inserts_builtFAIL instead of silently falling back to a panorama sweep. Classic / minimal modes keep the ffmpeg pan / Ken Burns establishing and never dispatch the clip node. Also:transition_design.py checkgainstransition_design_generative_allowedand atransition_clips_readywarning,accept-allfor re-running the H3A acceptance, the storyboard transition card shows 「🤖 工位建议」, the first-frame still and a 已生成/待生成 chip for i2v inserts, and the agent system prompt states the project's transition mode and whether the two nodes are instantiated. Design note:docs/transition_design.md. -
Transition design stages 3 and 4: generative bridges, paired camera moves and audio leads (J-cut). In modes that allow generative transitions, entering a flashback / dream / imagination block now proposes a
bridgeinsert (2 s image-to-video morph from the previous group's last frame to the next group's first frame, host-composed prompt keyed by block kind, white-dip fallback kept as a candidate; leaving a block offers a "bridge back" candidate; custom mode'sbridgeoption is honoured). The two stills are extracted bytransition_design.py clips --preparefrom the adjacent group clips, sop7-transition-clipsnow also depends on the episode'sp7-video; the contract requiresfileand forbids combining withmotion_pair. Paired camera moves are a newtransition_in.motion_pair {out, in, speed}field with a fixed legal-pair table (MOTION_PAIRS, checkmotion_pair_valid); they are realised as two host-written sentences in the adjacent group prompts —code/sync_motion_pairs.py --writeappends 「【运镜对接】…带出画面…」 to the previous group's lastShot N:paragraph and 「【运镜对接】…接入…」 to the next group'sShot 1:paragraph (EnglishMotion pair:sentences for non-Chinese UIs), idempotently, andmotion_pair_boundjoins the p7-prompt acceptance list. Audio leadaudio_lead_s(0–1 s) is now enforced (transition_audio_lead_valid: hard_cut / dissolve only, no inserts or black hold, not on the first group) and surfaced to the mixer:mix_basis.py sourceslists boundaries that only carry a lead, includes the lead in the boundary fingerprint (unchanged for projects without leads) and prints aLEADline; the audio-mixing SOUL specifies the J-cut (next group's native track startsleadearly with crossfades, BGM / narration / total length unchanged), andmix_basis_currentreports a lead-only change as stale with a dedicated message. Cinematic mode offers "paired move + audio lead" and "hard cut + audio lead" candidates and puts a 0.5 s lead on the overlay-only scene-change option; custom mode gainsmotion_pairandj_cutoptions in the 视频节奏 dialog; the storyboard transition card shows the paired move, the lead and bridge first/last stills. Classic / minimal modes never propose any of these. -
Scene preview: the per-scene 「背景图模式」 selector shows 「全景图」 and 「世界模型」 again. The scene-level plate-mode dropdown on the scene preview page now lists all three modes (跟随全局 / 九宫格 / 全景图 / 世界模型) instead of hiding the panorama and world-model entries; the project-level Output-settings dropdowns still expose only 九宫格 and the default stays
grid. Static file only (no service restart), hard-refresh the page. -
Shot plate 「✏️ 修改」 now regenerates from the current plate instead of re-deciding the camera. Sending feedback on a shot plate from the storyboard preview goes to the reviser as before (
kind=shot_plate), but the reviser now runs the new host CLIcode/revise_shot_plate.py --shot <shNNN> --role <start|end> --change "<change in English>" --note "<user's words>": the shot's current plate is passed as[Image 1](authoritative for camera, framing, layout, dressing and light; only the requested change may differ), one new plate is generated into the scene library as<key>_rev<N>(revised: true,pano_ref.kind = revisionwith the source key/kind and the request) and only this shot's start/end entry indirecting/<ep>/shot_plates.jsonis pointed at it (reuse: revised,revised_fromkept), followed by the usual group-prompt sync. The original image and its library entry are never touched — whether it came from a nine-grid tile, a nine-grid fill-in, a panorama/world-model capture, a master plate or a hand capture — and other shots that reference the same original are unaffected; 「🔁 换图」 can swap back at any time. Non---forcerender_shot_plateskeeps the revised plate (fresh record) and--statusdoes not flag it as legacy. The storyboard preview labels it 「起点 · 按修改意见重出」, the scene preview and the swap dialog show 「按修改意见重出(基于 <原 key>)」, and the feedback popup now carries the current file plus this command instead ofrender_shot_plates.py --force. shot-plates / reviser SOULs anddocs/shot_plates.mdupdated. Requires a service restart (preview data) and a hard refresh. -
Scene preview 3D whitebox: the orbit view is now driven like the world-model viewer. The 「旋转视角」 viewport on the scene preview page no longer orbits around the scene centre: drag turns the view in place (yaw/pitch), the wheel moves forward/back along the line of sight, and after clicking the view W/S move along the line of sight, A/D strafe, Q/E raise/lower and Shift triples the speed (movement scales with the scene's framing radius). The initial framing is unchanged. The focused viewport shows a blue inset ring, and the hint line under the toolbar lists the controls. Static files only (no service restart), hard-refresh the page.
-
/stop allchat command. Sending/stop allto any agent from any channel (web chat, Feishu, WeChat, WhatsApp) is intercepted inapi_chatthe same way as/auto— no run is dispatched and no engine quota is used — and stops every running/queued agent task across all projects via the same entry point as the run panel's 「⏹ 停止」 button (api_stop_all, so downstream sees 「已被用户手动停止」 rather than an error)./stopalone reports the current running/queued counts; the reply follows the UI language and warns when the project's auto-run watchdog is still on (it would dispatch again once idle — send/auto offto stop it for good). Requires a service restart. -
Hand-captured plates from the panorama and world-model viewers. The scene preview page's 360° panorama viewer gets a 「💾 背景图」 button (top right, with a dashed frame showing the saved area) and the world-model (Spark) viewer gets the same button in its toolbar; the world viewer now starts with the whitebox wireframe off and no longer draws the panorama-camera marker sphere. Pressing the button renders the current view off-screen at the project plate size and posts it with the derived whitebox camera (position / target / fov) to the new
POST /projects/<p>/scenes/<sid>/plates/manual, which validates the aspect, computes the camera facts, storesplates/<plate_key>_hand<stamp>.png(+ sidecar) and adds amaster/manuallibrary entry (pano_ref.kind = pano_manual | world_manual, view angles recorded). The plate shows up in the scene's plate list (caption 「手工截取(全景 A3)」 / 「手工截取(世界模型)」) and in the storyboard 「换图」 candidates. Requires a service restart. -
Plate mode defaults to nine-grid; panorama and world-model options hidden.
output.plate_modenow defaults togridfor new projects, the two Output-settings dropdowns and the per-scene selector on the scene preview page show only 「九宫格」 (thepano/worldoptions remain as hidden entries so existing projects and scenes keep displaying and running their stored value; the backend still accepts all three). Grid tiles split from the 3×3 sheet are now stored as JPEG under their.pngnames like every other library plate (was true PNG, ~1.8 MB per tile; existing tiles converted in place). Requires a service restart for the new-project default. -
Nine-grid plate mode now includes automatic per-shot fallback plates (default;
render_shot_plates.py --no-grid-fallbackdisables). Inplate_mode = grideach shot still picks the nearest of the nine tiles first; when that tile is unsuitable (bearing delta > 30°, pitch delta > 20°, camera distance > 6 m or height class delta ≥ 2 —GRID9_FIT) the script now renders a dedicated plate for that shot's own whitebox camera, using the scene's top-down plan as[Image 1]and the whole 3×3 grid sheet as[Image 2](build_grid9_fallback_prompt), stored in the scene plate library asgrid9_fallbackentries (<plate_key>_g9fb) and reused for nearby cameras (GRID9_FB_TOLERANCE). The episode index recordsreuse = grid9_fallbackplus the rejected tile's scores and the reasons; shots already indexed on an unsuitable tile are re-decided on the next run (the grid sheet itself is never re-rendered;--forceredoes only the targeted fallback plates,--no-grid-fallback --forcere-renders the grid).code/grid9_compare.py --project <slug> --ep <ep> --scene <sid>writes a comparison pageqa/grid9_compare/<sid>.html(whitebox frame / picked tile with scores / fallback plate side by side). Also fixes the plate-prompt inventory counting an object's parts (*_top,*_leg_1…) as several objects ("the glass table (4 of them)"). -
NSFW mode (Settings → Advanced → 「NSFW 模式」, default off). When enabled, the user configures three fallback models — a language model (engine + model), an image model (provider + model) and a video model (provider + model) — plus an 「审核拒收后自动切换」 failover switch (default on). Routing is three-layered and host-side (members never pick engines or providers for it): ① explicit marks first —
p5-breakdownwritesevents[].content_flags(nudity / sex / gore / extreme_violence / drugs / self_harm,[]when none) intoscript_breakdown.json, shot-planning derives a per-groupnsfw: true|falseinshot_list.jsonfrom the scene's events, the user can toggle 🔞 on a group card in the storyboard page (assets/group_settings/epNN/grpNNN.json#nsfw,nsfw_reason: manual, overriding the derived value), and the orchestrator dispatches flagged work with the new optional work-order fieldnsfw: true/dispatch.py --nsfw— the host then runs that member on the fallback language model and setsVIDEOAGENTS_NSFW=1so its genmedia image/video calls use the fallback providers (genmedia.py info --groupshows 「🔞 NSFW 路由」); ② moderation-rejection failover — when a provider rejects a call for content (Ark*SensitiveContentDetected, MiniMax 1026, Fal/OpenRouter content_policy) genmedia re-submits once on the fallback provider within the same call, logs[genmedia] NSFW 兜底, recordsmeta.json#nsfw_route, and this switch does not count toward the user's retry budget; a language-model refusal is not switched in-process — the host marks the runnsfw_suspect, records the task_id inruns/nsfw_suspect.json, and the second dispatch of the same task_id automatically uses the fallback language model (「第二次尝试才换」); ③ stickiness — a successful failover writesnsfw: true, nsfw_reason: failoverto the group's settings so later re-rolls go straight to the fallback provider. A configured-but-unusable fallback (no key) raises instead of silently falling back to the primary model; a missing fallback for a modality only warns. New WARN-only checknsfw_hint(code/check_nsfw_hint.py) flags groups whose shot text / prompt looks sensitive but is not marked, and the storyboard page shows a yellow hint on them. The mode only decides which models are used; content-safety and platform-rating checks are unchanged, and agents must not fabricatecontent_flags. All agent clauses are conditional on the injected 「## NSFW 模式:开启」 section (WORKFLOW §5/§6/§9, timeline-story and shot-planning SOULs,docs/script_breakdown.md,code/README.md). Requires a service restart. -
Upscale settings: Agentics tab. The "🔍 Upscale" card on the Generation Models page gains an Agentics tab (
genconfig.upscale.provider = agentics,upscale.agentics.profile_code, defaultscale-seedvr2). It reads the same Agentics media catalogue as the video section, filtered tomedia_type = Scaleprofiles, and bills the signed-in desktop account.genmedia.py upscaleuploads the source clip to the profile's declared video slot, submits the target tier / size / seed through the fixed profile fields, rescales a mismatched output to the delivery size, and recordsupscale{provider: agentics, model, params.task_id…}in the clip's meta.json; the task is a long task like video, so an interrupted process resumes withreclaim --task-id <UUID>instead of resubmitting. No fallback, as with the other upscale providers. -
Episode close: the cut no longer stops dead on its last frame. A new episode close setting (Video Rhythm dialog and new-project wizard,
settings.json#transitions.episode_close, present in every transition mode) decides how the main body ends: fade to black (default: 1.0 s fade + 0.5 s hold), fade to white, cut to black (cut_black, a sudden blackout for suspense: no fade, the last frame cuts straight to a black hold and the audio stops at the same instant), cut to white, or none (hard_cut, the previous behaviour). An episode can override the project value from the new "⏹ Episode end" row in the storyboard page's transition summary bar (POST /transitions/<ep>/close, written to a new optional top-levelshot_list.episode_closefield; the director's transition list may also name it and shot-planning writes the field; machine checktransition_close_valid).render_transitions.py planappends oneto_group: nullentry totimeline.transitions[];renderre-encodes only the last group (fade-out / cut +tpadcolour hold; a cut's own audio track is faded or cut at the same moment and padded with silence);checkfolds the hold intoduration_as_planned/audio_stream_intact, verifies the last frame and hold midpoint (for cuts also that the last content frame was left untouched), whitelists the window forno_black_framesand recordstransitions_render.json#episode_close {fade_end_s, end_s};preview --boundary episode_closerenders a tail preview (also from the storyboard row). The hold sits after the end of the picture, so it is deliberately not a timemap op and not part of the mix boundary layer (mix manifests and stamped fingerprints are unaffected);finalize_episode.py assemblereads the ledger and applies the matchingafade(fade types) or a 20 ms de-click cut (cut types /hold_audio: mute) to the external mix at the moment the picture is fully black, pads the hold with silence, and no longer warns about the picture being longer than the audio by exactly the hold. Existing projects pick up the default fade on their next transition render (cut_v2 is now produced even for an all-hard-cut episode unless the close is set to none). WORKFLOW §9C, transition / shot-planning / director / audio-mixing SOULs, all 11 UI dictionaries. Requires a service restart. -
Third background-plate mode: nine-grid (
output.plate_mode = grid). Next to Panorama and World model (project output settings and the per-scene override on the scene preview page) a whitebox project can now haverender_shot_plates.pygenerate, per scene and lighting scheme, one 3×3 grid image from the nine semantic camera positions in the scene's layout pack (layout.json#views, tile 1..9) with the top-down plan and a host-made blank tiling template as references, split it into nine background plates filed in the plate library (pano_ref.kind = grid9; tiles are not master plates and never go through homography derivation), and then pick for every shot the tile whose synthesised camera (landmark → whitebox coordinates, height byangle, field of view bysize) is closest to the shot's whitebox camera (bearing, horizontal distance, height class, pitch, field of view; the score breakdown is logged per shot and stored in the episode index). The grid sheet<scheme>_grid9.png(+.jsonledger) is never attached to video prompts and deliberately avoids the retiredgrid_9views*file prefix that three checks blacklist; a layout without nine views raisesGrid9LayoutError.docs/shot_plates.md"背景图模式", shot-plates SOUL, WORKFLOW §9. Requires a service restart. Exercised once for real on fengshen3 ep06 SCN-0121 (38 shots → one grid, nine tiles). -
Fancy text (花字) gets its own section on the Post-production page, with an auto/manual mode and a per-type picker. The packaging layer used to have one row of five switches ("Intro/outro / caption switches"); it is now two rows: "Intro/outro" (intro, outro, teaser, subtitle burn-in) and "Fancy text" (enable switch, a mode dropdown and a type picker). Mode
auto(default) lets the caption agent choose usage types by genre;manualopens a dialog grouped by four categories (narrative info labels, structure and pacing, emotion and variety-show style, functional and compliance) with select-all/clear per category and a "suggest by genre" preset menu, and only the checked types may appear in the episode's captions (checked = allowed, not required). Switching to manual opens the dialog first and falls back to auto if it is cancelled; saving manual with nothing checked is refused by the server. Types that a project cannot use are greyed out with the reason: in audio-to-video projects every caption must come from the master transcript and align to speech, so types that are usually not spoken (inner voice, onomatopoeia, footnotes, disclaimers, …) are unavailable, and types that need upstream data (cultivation table, prop/creature cards, dictionary, world bible) are unavailable until that data exists. The single source is the newmodules/caption_catalog.json(35 usage types; the seven legacytypevalues remain valid; four usages are listed as excluded for now: show-title corner bug/watermark because it persists across groups, teaser hook and credits which belong to the title station, transition cards which belong to the transition station). New settingsoutput.caption_mode/output.caption_types; captions.json gains an optional per-captiontier(headline/keyword/label size band, inferred from the type when absent,em_pctis now validated per band) and a top-levelcaption_policystamp; new design-stage machine checkscaption_types_allowed(manual mode: every type is in the checked set) andcaption_policy_fresh(stamp equals current settings, so changing the selection without re-running the design fails); new CLIrender_captions.py policy [--stamp]prints the effective policy and writes the stamp. The caption prompt injected into caption/edit agents was also brought up to date (it still described schema v2 with ≤4 style presets and the old density rules) and now lists the allowed types. WORKFLOW §9A, workflow.yaml, the caption SOUL and the caption-styling skill are updated; all 11 UI dictionaries gain the catalog labels. -
Issue feedback reports now carry the user's client version. The metadata block at the end of an auto-filed GitHub issue (Settings → Advanced → Diagnostics, "Issue feedback") gains a "客户端" line next to the server version: the desktop app passes its own version and build (
VIDEOAGENTS_CLIENT_VERSION,VIDEOAGENTS_CLIENT_BUILD= channel/buildHash) to the Python service it launches, so a report reads e.g.desktop 1.0.36 (release/abc123); a browser talking to a locally started service recordsweb. Stored asclient_versionin the local ledger; the prefilled "submit in browser" link includes it too. Requires a service restart (and a rebuilt desktop app for the desktop value). -
Claude Opus 5.5 in the language-model menus (
claude-opus-5-5, listed asopus-5.5right aboveopus-5in both the top-bar model dropdown and the per-agent model dialog). Thesonnetalias is now labelled "sonnet (latest)" and sits directly below "opus (latest)", followed by a new "fable (latest)" entry (the CLIfablealias, currently resolving to claude-fable-5-1); model ids are unchanged, so existing settings keep working. Not a default: Smart Assignment still maps to opus / sonnet.
Changed
- Sample-reel dialogue now keeps pace with the shots. Dialogue voiced by the dialogue TTS library ran long in the whitebox reel and the animatic — seed-audio speaks at about 3 characters per second and pauses 0.6–1.7 s at punctuation, while shots are timed from
est_duration_s(about 4.2 characters per second) — so lines dragged into the next shot and talked over the next line. The library now also writes a paced copy of every line that runs longer than itsest_duration_s(assets/audio/voice/<ep>/tts/_paced/, per-linepace{file,duration_s,tempo,pause_s,target_s}intts_manifest.json): pauses inside the line are first shortened to 0.3 s, and only what still does not fit is sped up without changing pitch, capped by the new project settingoutput.dialogue_tts_max_tempo(1.0–2.0, default 1.5; 1.0 turns pacing off; CLIcode/dialogue_tts.py --max-tempo). This is post-processing of audio already synthesized — no TTS call is made and nothing is billed. Both sample reels use the paced copies (line_audio(paced=True)); the library files themselves stay at natural speed, and dubbing (code/dub_group.py) keeps using those. When a line still runs past its shot, the first line of the next shot now waits for it to finish (delay_s, at most half of that shot) instead of overlapping. Reels built before this change are rebuilt with the 「白模样片」/「动态样片」 button.
Fixed
-
Network fail-fast message now follows the interface language. The run error, the chat entry and the live run log written when an agent run is killed after
Connection dropped/ connection timeout (🔌net_error) were Chinese-only; with a non-Chinese interface language they now read in English (Network interruption: … (CLI api_retry #n, error: …) (network fail-fast after Ns of running)), same rule as other server-side texts. The 🔌 marker and thenet_errorfield, which the orchestrator relies on, are unchanged. Requires a service restart. -
Fancy-text (花字) rendering now follows the post-production timeline (
code/render_captions.py, newmodules/caption_timeline.py; fengshen3 ep06 DEF-ep06-caption-0001). The caption tool only knew the v0 master clips and the original rough-cut timeline: copies were burned from the masters (adopted post versions such as graded or cut-down groups were lost) and the SFX track / speech alignment used the rough-cut clock, so a final with cut-outs (−19.24 s) and title-card inserts (+10 s) drifted. One resolver now answers "which file, which second" for every caption: the burn source is each group's current adopted version (master when none),local_start/local_endstay in master-v0 coordinates and are mapped through the version'stime_ops(a caption inside a deleted range is dropped with a warning), episode-levelstart/end,word_track.jsonandspeech-lookupare all on the cut basis (adopted versions + boundary layer, no intro), and the final composition adds the intro offset fromfinal_layout.json. The caption-final work order becomes one host command,render_captions.py final: it composites all stickers onto the cleanfinal.mp4(same EDL by construction, so post versions, transition cards and intro line up), builds the SFX track on the same clock, muxes a:0 premix / a:1 archive, and writesedit/epNN/final_caption.json(idempotent;sfx-track+mux --videoremain as the legacy path).speech-alignfolds the transcript and the mixed audio to the same clock before aligning (the mix may or may not already carry the boundary layer, decided by the mix manifest exactly asfinalize_episode.pydoes, whoseload_timemapmoved tomodules/timemap_layers.py);speech-snapwriteslocalback in v0 coordinates.check_captions.py:caption_time_consistentnow verifies the cut-basisstartand fails captions that fall inside a deleted range, render receipts are keyed to the adopted version (src_version), and a new final-stagecaption_final_freshcompares the receipt with the current clean final / captions / audio / timeline. Newrender_captions.py timelineprints the resolved axis. WORKFLOW §9A, the caption and edit SOULs, workflow.yaml and the injected caption prompt describe the new contract. Existing projects: re-runspeech-align→speech-snap→render→final(old copies and word tracks are reported stale). -
BGM no longer drops out inside transition inserts (title cards / establishing shots / holds). The mix (
assets/audio/final/epNN.wav) was built on the pre-transition timeline andfinalize_episode.pythen cut it open at every boundary insert and filled the gap according to the op's audio policy (mute→ digital silence;sustainonly loops the last second), so the music stopped for the whole card (fengshen3 ep06: −91 dB for 2.5 s under cue 04 and cue 06). The boundary layer is now part of the mix basis:code/mix_basis.py sourcesderives it fromshot_list.transition_in(freeze / hold / inserts, frame-quantised likerender_transitions.pad_ops) and reports it asboundaries[]with each group'scum_start_salready including the preceding boundaries, so the mixing agent lays BGM and narration on the final, transition-inclusive timeline (a cue spanning an insert keeps playing) while the native track leaves a gap per the boundary's audio policy;stamprecords the boundary fingerprint and checks wav length = Σgroups + Σboundaries.finalize_episode.pycompares that fingerprint with the cut'stransitions_render.json#timemap: when they match the external audio is no longer remapped through the boundary layer (subtitles still are); a mismatch, an old manifest without the layer while the cut has inserts, or a mix with the layer but a cut that never went throughrender_transitionsfailsmix_basis_current/ refuses to assemble and asks for ap8-mixrerun. WORKFLOW §8B ④ and the audio-mixing SOUL describe the new placement rule. -
Transition title cards and overlay captions now use the project's fonts (
refs/fonts/, WORKFLOW §2 rule 9).render_transitions.py buildused to pick the first usable face from the globaldata/fonts/directory or the system, ignoring fonts uploaded on the Reference Files page, andsettings.json#transitions.card_stylewas never read at render time. The host now resolves the card font once per project (modules/transition_design.py resolve_card_font): the optional newtransitions.card_fontsetting (a project-relative path such asrefs/fonts/huiwen.ttf, or a caption-engine id such asproj:Huiwen-mincho) wins, otherwise the first file inrefs/fonts/is used for every card and overlay in the project, otherwise the previous global/system order applies. The font file (name / size / mtime) is part of the build fingerprint whenever a boundary renders text, so a changed or newly uploaded font rebuilds the affected cards on the nextbuild/renderwithout--force; projects without project fonts keep their existing fingerprints. Build logs andmeta.jsonrecord the face that was used.render_transitions.py checkgains a staleness test insideinserts_built: a segment or overlay built after the transition cut was rendered (e.g.buildafter a font change withoutrender) now fails with "构建晚于成片(重跑 render)" instead of passing and showing the boundary as rendered on the storyboard page.