Wire ProgressBar callbacks through scene-chain _sample_chunk so denoise
steps update the global progress hook the frontend polls. Reset progress
when a new generation is queued; label the readout as sampling N/M.
Audio blocks were growing past their scene duration because flex
content and min-width: 48px overrode the inline width. Match video
clip sizing, cap max-width, and let the volume slider shrink inside.
Split Project settings into Output, Prompt, and Engine bands with
collapsible FunPack Studio and Chain Sampler cards. Group chain knobs
by timing, continuity, guidance, decode, and experimental; add
transition_duration, use_same_seed, and decode_tile_size. Show engine
context strip in scene view, non-default badges, and locked carry hint
for multi-scene runs.
Replace the old audio-only "Clips" gutter with a shared sticky left column
that labels the video track and each audio lane without offsetting clip
positions — video and audio still share the same time origin.
Add Media/Preview/Settings toggles above the timeline that preserve the
canonical layout when all three are on and collapse panels intelligently
when toggled off. Move per-clip volume and inserted audio tracks from the
inspector onto NLE-style lanes below the video track.
A long generation polled progress every ~700ms via set()->notify(), rebuilding the whole
editor (inspector/timeline/player) each time — so saving rendered frames to the bin or
adding effects got interrupted constantly. Progress ticks now use updateGenProgress(),
which mutates state.gen quietly and fires a 'funpack-gen-progress' DOM event; the player
updates the readout/progress bar in place. Full notify() is reserved for state transitions
(queued/done/error), so the editor stays interactive throughout a run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Models: 'Disable built-in pipeline' toggle (config.disable_core). When off, the build
skips the entire fixed FunPack core graph and runs only user-wired nodes.
- Global editor outputs: wire any slot's IMAGE output to 'global:video' and AUDIO to
'global:audio' (shown at the top of the wire-target list). The builder synthesizes a
VHS_VideoCombine on demand that muxes the wired frames WITH the wired audio into one
video+audio file, which the editor previews from history. Works with core on or off.
- Guards: skip core producers/targets that don't exist when core is disabled; non-blocking
warning if nothing is wired to the global video output with core off. _project_models
recognizes a project whose only config is disable_core.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A non-finite video.volume throws on assignment; since _applyFx runs every rAF tick, that
threw inside the tick and froze playback. Volume is now sanitized (finite, clamped) and
the assignment is try-guarded, and the tick's _applyFx call is wrapped so no effect error
can ever stop the playback loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Per-clip original audio gain (Scene.audio_volume; inspector slider, 0 mutes). Applied at
render (volume filter) and in the live preview (video.volume, clamped 0..1).
- Project-level audio mix: keep_original_audio toggle (drop the generated LTXAV audio) and
audio_tracks[] — inserted background tracks from the Media bin, each with start offset +
volume. Mixed at render via adelay + amix(normalize=0), trimmed to the montage length.
- All four cases work: lower/boost a clip, add one or many tracks, keep original + tracks,
original-only, tracks-only, or fully silent (no audio stream mapped).
- Render route resolves track media to files (media.path_for), appends them as inputs after
the clips, and maps [aout] only when there's audio. renderFinal flushes the project save
first (tracks/toggle live on the project, read from disk at render).
- Inspector: per-scene Clip audio + Project Audio section (toggle, track cards, add-from-bin).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render failed two ways: clips with different native sizes (768x768 vs 576x768) broke
xfade (needs matching size/fps/sar), and zoom physically rescaled the frame.
- Every clip is now normalized to one canvas (scale+pad to clips[0] size, fixed fps,
setsar=1) before effects, so xfade/concat always match.
- Zoom is virtual via zoompan: output stays fixed at canvas size, only the content scales
within it (in 1.0->1.2, out 1.2->1.0). No frame-size change -> renders cleanly.
- Preview matches (zoom-out now pulls back 1.2->1.0); labels updated (push / pull back).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'✨ Effects ▾' on the selected clip: pick an effect/transition (zoom in/out, blur, fade
in/out, crossfade/fade-to-black/wipe → next), enter its value (strength/seconds/frames)
where relevant, and Add. Writes onto the scene, so the inspector's per-scene controls
remain the single source of truth and reflect whatever is added here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
If the playhead is at/past the end (or on no clip) when Play is pressed, reset it to 0 and
play from the top instead of doing nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grab a clip body and drag left/right to reorder; an amber insertion line shows the drop
spot, and rebuilds are hard-blocked mid-drag so the dragged clip doesn't vanish. A 5px
threshold keeps plain clicks as select; trim handle and action buttons opt out. store
gains moveSceneTo(id, index). The tiny ‹/› move buttons stay as a fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Post-decode pixel ops (never re-encode to latent):
- Per-scene effects: gaussian blur, fade in/out, Ken-Burns zoom in (crop) / out (pad).
Stored in Scene.effects {blur, fade_in, fade_out, zoom}.
- Seam video transitions: crossfade/dissolve, fade-through-black, wipe L/R via
Scene.video_transition + transition_frames (length). Crossfade overlaps the clips
(xfade + audio acrossfade) so the montage shortens by the transition length.
- Render route builds a filter_complex (_build_render_filter): per-clip effect chain via
scale eval=frame + crop/pad/gblur/fade, then a left-fold of xfade/concat seams.
- Live preview approximates per-clip fx (CSS blur/scale/opacity, playhead-synced so it
tracks scrubbing); crossfades preview as hard cuts but render correctly.
- Inspector exposes all of it per scene.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The playhead listener called _renderTransport on every rAF tick, clearing and recreating
the play/pause/stop buttons ~60x/sec. A click's mousedown/mouseup straddled a rebuild, so
the button was destroyed mid-click and no click fired. Playhead ticks now update only the
timecode text and play-button glyph in place; full rebuilds happen only on discrete
transport actions and store changes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The transport frame capture no longer requires a scene: with the selector left on
'— save to Media bin —' it just saves the frame as an asset (usable as an anchor later
via drag/pick); pick a scene to apply it as that scene's i2v anchor. Button label
adapts (Save frame / Use as anchor).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scene export -> <project>_sceneN_YYYYMMDD-HHMMSS.mp4; final render ->
<project>_final_YYYYMMDD-HHMMSS.mp4, so repeated saves never collide.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Single-scene runs are one segment already, so no 'scene N' marker is injected (avoids
leaking it into the prompt). Multi-scene runs inject before every scene lacking a leading
trigger (incl. the first, which Studio would otherwise fold into the anchor).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- #1 Multi-scene run only generated scene 1: a carry scene has no leading transition so
Studio merged it. Generation prompt now injects a separator before any scene lacking a
leading trigger (transition_to_next or generic 'scene N') so Studio splits into exactly
one conditioning per scene. Display/global prompt stays verbatim (for_generation flag).
- #2 'Use as anchor' no longer defaults to scene 1 — requires picking a scene, so it can't
silently overwrite an existing image. Anchor capture drops the obsolete target.
- #3 Export uses unique names: <project>_sceneN.mp4.
- #4 A scene whose image was deleted falls back to carry (i2v guides) in run splitting.
- #5 Timeline/inspector edit-guard could stick (focused control removed w/o focusout),
freezing refresh. Guards now self-clear when focus is actually gone.
- #6 Expose Studio refiner toggles (vision conditioning, reference injection, prompt
repair) in the inspector -> studio_settings.refiner.
- #7 Embed-guidance mode (relative/absolute) now selectable (sampler combo knob).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace whole-run renderedSegments with per-scene sceneRenders {media, inSec}. The
player builds per-scene clips (media, startSec, durationSec, inSec) and plays each clip's
[inSec, inSec+dur] window via a rAF tick (precise out-points, so deleted parts aren't
played; contiguous split seams continue seamlessly). Split sets the second half's inSec;
delete drops the mapping. Render route takes per-clip in/dur and concatenates only kept
clips (ffmpeg -ss/-t + concat filter, audio preserved). Export/hasRender use sceneRenders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Boot-error overlay ignored resource-load errors: a pooled <video> failing to fetch
fired a capture-phase 'error' with empty message -> scary 'load error' banner. Now
skip events targeting an element (resource errors); only real script errors show.
- Split now assigns the new half a client id and adds it to the rendered segment's
sceneIds, so the existing video spans BOTH halves (one source, like a real NLE) and
the monitor keeps playing across the cut instead of going blank.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking 'Reset Studio session' again now cancels a pending reset (mis-click safe);
the menu shows 'armed — click to cancel' when set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
.log-panel had display:flex, which overrode the [hidden] attribute's display:none, so
the close/toggle never hid it. Add .log-panel[hidden] { display:none }.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- _prepare_media no longer requires a per-scene 'target' (removed earlier) — it now
injects the scene image as a LoadImage from just the asset ref. Fixes 'ImageTransformKJ
.image (IMAGE): no node outputs IMAGE' for image-anchored scenes/runs: the image is
now materialised and auto-wires to IMAGE inputs.
- Boot-error overlay gets a ✕ dismiss button so it can't permanently block the top menu
(so you can still reach Restart ComfyUI etc.).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a 'Log' button in the menubar (next to Saved) that opens a collapsible panel
streaming ComfyUI's real backend log. The bridge tees stdout/stderr into a ring buffer
(writes still pass through to the terminal; captures print + logging); /api/log returns
the tail. Panel polls every 1.5s while open, auto-scrolls (pauses if you scroll up),
with Copy. Far more informative than our wrapped error messages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- FunPack menu 'Reset Studio session' arms reset_session for the FIRST run of the next
generation (false for the rest); shows ✓ armed. reset_session lives in
studio_settings.refiner, set explicitly per run by the builder.
- Unrated scenes now send '-Just forget it-' to Studio instead of its default learning
rating, so the system never learns from a rating the user didn't give (esp. after a
reset / first generation).
- Timeline no longer rebuilds while a control (rating/transition dropdown, trim input)
is focused, so autosave/progress notifies can't close the rating dropdown mid-pick;
it re-syncs on blur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CRITICAL: split_by_transitions is NOT a top-level FunPackStudio input; Studio reads it
from studio_settings.refiner.split_by_transitions (default False). The builder set it
as a top-level input, which ComfyUI silently dropped (not in INPUT_TYPES) — so Studio
ran in single-scene mode and every scene got the whole prompt (scene 2's action bled
into scene 1). Now force split_by_transitions=True INSIDE the studio_settings JSON
(after user overrides), so Studio actually splits the combined prompt per scene.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rating:
- Scene.rating (V2_RATING_LABELS); /api/rating-labels exposes the options.
- Timeline toolbar 'Scene N ★' dropdown, shown ONLY when FunPack Studio is the
conditioning provider AND the selected clip has a render. Rates that clip's own
scene (cut-aware by scene id).
- Generation injects the run's rating into Studio (studio_inputs.rating) so Studio
refines the next generation from it; empty = use Studio's default.
Wiring:
- Widget inputs (INT/FLOAT/STRING/BOOLEAN, e.g. EmptyLatentVideo.width/height) are
now offered as manual wire destinations (ComfyUI converts a widget to an input when
wired) — so ImageTransform's width/height outputs can drive them. They're still not
auto-wired, so widget values aren't clobbered unless wired explicitly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ComfyUI calls PROGRESS_BAR_HOOK with extra args (e.g. node_id), which broke our chained
hook -> VHS_VideoCombine errored. Accept *args/**kwargs and forward them to the previous
hook (falling back to the 3-arg form if it can't take them).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
COMFY_DYNAMICCOMBO_V3 options are dicts {key, inputs}, not strings — we were using the
whole object as the value (showing '[object Object]' and feeding ImageTransformKJ an
object). Normalise combo choices/defaults to the selectable key string everywhere
(_combo_choices/_combo_default), and coerce any stale '[object Object]'/dict slot value
back to the default at build. Combos now show real options and emit valid values.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaced the single <video> (which reloaded src per clip -> black flash crossing a
cut) with a pool of preloaded <video>, one per clip file, in a persistent stage; only
the active clip is shown. Scrubbing/playing across cut boundaries swaps which video is
visible — already loaded, so the frame appears immediately. The pool is rebuilt as the
timeline changes (cuts/regens) and segment positions recompute live; _currentSeg is
refreshed each render so edits stay in sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Generation progress: chain ComfyUI's global ProgressBar hook (no clobber) to record
sampler current/total steps; /api/progress exposes it; the store polls it ~700ms and
shows 'step X/Y' + a progress bar in the program monitor.
- Interrupt button (player overlay) -> /api/interrupt -> ComfyUI /interrupt; the run
loop stops and shows 'Interrupted.'
- Fix ImageTransform (V3) blocking: keep_proportion/extra_padding/invert_crop are
COMFY_DYNAMICCOMBO_V3 dropdown widgets, not sockets. Recognize any 'COMBO'-containing
type as a widget across connection_inputs / widget_inputs / ports / widget defaults,
so they're configured as combos and never reported as missing connections.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking the timeline background (not a clip, seam, or the ruler) now deselects the
current scene, switching the inspector back to Project view.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The program monitor stalled after the first segment: on segment-end (and on play) it
called video.play() immediately after setting a new src + load(), so the play promise
rejected (media not ready) and the chain stopped — looking like it only plays the one
(highlighted) scene. Added a _playPending flag: when loading a different file, playback
starts on loadedmetadata; same-file seeks play immediately; pause cancels pending
autoplay. Segments now play through seamlessly, skipping ungenerated gaps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Routing is owned by Models/nodes now (the scene image is an auto-wiring IMAGE
producer; a node can pull it via Input source = Timeline). The per-scene target was
a confusing second place to wire the same thing. Removed it from the image and
generated-frame sources; the scene just supplies the anchor image and the pipeline
routes it (default → Studio source image). Existing explicit targets still honored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A node's IMAGE input (e.g. ImageTransform) reported 'no node outputs IMAGE' because
the scene image comes from the timeline (injected as a LoadImage), not a node output.
Now the builder always materialises the scene image as media_load and registers it as
an IMAGE producer, so unwired IMAGE inputs auto-wire to the scene image. The Models
modal no longer flags required IMAGE inputs (the timeline can always feed them).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Inspector preserves scrollTop across rebuilds, so editing no longer jumps the panel
to the top.
- Autosave only persists to disk; it doesn't need to fire every keystroke. Free-text/
number fields now debounce 6s and are FLUSHED on blur (flushSave); discrete actions
save ~1.2s after the last change. generate()/generateMontage() flush first so the
server has the latest edits before reading the project.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>