diff --git a/movie_editor/backend/builder.py b/movie_editor/backend/builder.py index 6352b71c..f0fd5a63 100644 --- a/movie_editor/backend/builder.py +++ b/movie_editor/backend/builder.py @@ -194,47 +194,6 @@ def _output_index(node_def: Optional[dict], out_name: str) -> int: return 0 -def _wire_live_preview_vae( - models_config: dict, - graph: dict, - slot_node_id: dict, - slot_def: dict, - report: dict, -) -> None: - """Wire models.live_preview slot VAE output -> Chain Sampler preview_vae.""" - lp = (models_config or {}).get("live_preview") or {} - if not lp.get("enabled"): - return - if "sampler" not in graph: - return - sid = lp.get("slot_id") - if not sid: - report["unsatisfied"].append( - "Live preview is enabled but no preview VAE is selected (View → Choose preview VAE…)." - ) - return - nid = slot_node_id.get(sid) - if not nid or nid not in graph: - report["unsatisfied"].append(f"Live preview VAE slot '{sid}' is not in the graph.") - return - out_name = str(lp.get("vae_output") or "VAE") - nd = slot_def.get(sid) or {} - outs = node_outputs(nd) - oidx = _output_index(nd, out_name) - if not outs or oidx >= len(outs) or outs[oidx].get("type") != "VAE": - report["unsatisfied"].append( - f"Live preview slot '{sid}' has no VAE output '{out_name}'." - ) - return - graph["sampler"]["inputs"]["preview_vae"] = [nid, oidx] - slot_lbl = next( - (s.get("label") or s.get("node_class") or sid - for s in ((models_config or {}).get("slots") or []) if s.get("id") == sid), - sid, - ) - report["wired"].append(f"{slot_lbl}.{out_name} -> sampler.preview_vae (live preview)") - - def build(object_info: dict, models_config: dict, params: dict, media: dict | None = None) -> tuple[dict, dict]: """Return (graph, report). `params`: prompt, negative_prompt, seed, num_frames_per_scene, frame_rate, width, height. `models_config`: {"slots":[...], @@ -466,8 +425,6 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No report["unsatisfied"].append( f"core override {cid}.{inp}: '{source}' could not be resolved.") - _wire_live_preview_vae(models_config, graph, slot_node_id, slot_def, report) - # 5. auto-wire remaining unbound typed inputs by unique producer. producers = _producers(graph, slots, slot_node_id, slot_def, object_info) # Drop producers whose node isn't in the graph (core producers vanish when the built-in @@ -490,9 +447,6 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No for src in (ovs or {}).values(): if isinstance(src, str) and src.startswith("out:"): active_slots.add(src.split(":", 2)[1]) - lp_sid = ((models_config or {}).get("live_preview") or {}).get("slot_id") - if lp_sid: - active_slots.add(lp_sid) _autowire(graph, slots, slot_node_id, slot_def, object_info, producers, report, active_slots) for msg in pipeline_wiring.validate_models_wiring(models_config): diff --git a/movie_editor/backend/live_preview.py b/movie_editor/backend/live_preview.py deleted file mode 100644 index 1d0aedf9..00000000 --- a/movie_editor/backend/live_preview.py +++ /dev/null @@ -1,79 +0,0 @@ -"""In-process live preview state for Cutting Room generation runs. - -The Chain Sampler writes per-scene preview frames here; the Movie Editor server -serves them while a job is running. -""" -from __future__ import annotations - -import json -import threading -import time -from pathlib import Path -from typing import Any, Optional - -_lock = threading.Lock() -_state: dict[str, Any] = { - "active": False, - "dir": "", - "scene_index": -1, - "scene_count": 0, - "updated_at": 0.0, - "frame_path": "", -} - - -def reset(preview_dir: str, scene_count: int = 0) -> None: - with _lock: - _state["active"] = True - _state["dir"] = str(preview_dir or "") - _state["scene_index"] = -1 - _state["scene_count"] = int(scene_count or 0) - _state["updated_at"] = time.time() - _state["frame_path"] = "" - if preview_dir: - Path(preview_dir).mkdir(parents=True, exist_ok=True) - - -def clear() -> None: - with _lock: - _state["active"] = False - _state["dir"] = "" - _state["scene_index"] = -1 - _state["scene_count"] = 0 - _state["updated_at"] = time.time() - _state["frame_path"] = "" - - -def publish(scene_index: int, frame_path: str, scene_count: Optional[int] = None) -> None: - with _lock: - if not _state.get("active"): - return - _state["scene_index"] = int(scene_index) - if scene_count is not None: - _state["scene_count"] = int(scene_count) - _state["frame_path"] = str(frame_path or "") - _state["updated_at"] = time.time() - - -def snapshot() -> dict[str, Any]: - with _lock: - return dict(_state) - - -def parse_config(raw: Any) -> dict[str, Any]: - if not raw: - return {} - if isinstance(raw, dict): - return raw - try: - out = json.loads(str(raw)) - return out if isinstance(out, dict) else {} - except Exception: - return {} - - -def models_enabled(models: Any) -> bool: - if not isinstance(models, dict): - return False - lp = models.get("live_preview") or {} - return bool(lp.get("enabled") and lp.get("slot_id")) diff --git a/movie_editor/frontend/api.js b/movie_editor/frontend/api.js index 5e9bc797..9097e843 100644 --- a/movie_editor/frontend/api.js +++ b/movie_editor/frontend/api.js @@ -134,7 +134,6 @@ j("POST", API(`/projects/${id}/generate`), { only_scene: onlyScene || null, scene_ids: sceneIds || null, reset_session: !!resetSession }), status: (id, promptId) => j("GET", API(`/projects/${id}/status/${promptId}`)), progress: () => j("GET", API("/progress")), - livePreview: () => j("GET", API("/live-preview")), ratingLabels: () => j("GET", API("/rating-labels")), log: (limit) => j("GET", API("/log" + (limit ? `?limit=${limit}` : ""))), interrupt: () => j("POST", API("/interrupt")), diff --git a/movie_editor/frontend/menubar.js b/movie_editor/frontend/menubar.js index 58b428ac..3941ef29 100644 --- a/movie_editor/frontend/menubar.js +++ b/movie_editor/frontend/menubar.js @@ -156,42 +156,6 @@ }); } - async function _pickPreviewVae(st) { - closeAll(); - const slots = st.models?.slots || []; - const options = []; - for (const s of slots) { - let spec; - try { spec = await window.MovieEditorAPI.nodeSpec(s.node_class); } catch (_) { continue; } - const vaeOuts = (spec.outputs || []).filter((o) => o.type === "VAE"); - if (!vaeOuts.length) continue; - const outName = vaeOuts.find((o) => o.name === "VAE")?.name || vaeOuts[0].name; - options.push({ - value: `${s.id}\x1f${outName}`, - label: s.label || s.node_class || s.id, - hint: `${outName} · ${s.node_class}`, - }); - } - if (!options.length) { - alert("No Models slot outputs VAE.\nAdd a VAE loader in Settings → Models (e.g. taehv for fast preview)."); - return; - } - const cur = st.models?.live_preview || {}; - if (!window.SlotPicker) return; - window.SlotPicker.open({ - title: "Choose preview VAE", - options: options.map((o) => { - const [sid, out] = o.value.split("\x1f"); - const isCur = sid === cur.slot_id && out === (cur.vae_output || "VAE"); - return { ...o, hint: isCur ? "current" : o.hint }; - }), - onPick: (val) => { - const [slotId, outName] = val.split("\x1f"); - S.setLivePreviewSlot(slotId, outName); - }, - }); - } - let _gitStatus = null; function promptNewProject() { @@ -251,13 +215,6 @@ View: [ { label: "Refresh Preview", action: () => S.refreshPreview() }, { sep: true }, - { label: st.models?.live_preview?.enabled ? "Disable live preview ✓" : "Enable live preview", - disabled: !hasProject() || !chainOn, - action: () => S.setLivePreviewEnabled(!st.models?.live_preview?.enabled) }, - { label: "Choose preview VAE…", - disabled: !hasProject() || !chainOn, - action: () => _pickPreviewVae(st) }, - { sep: true }, { label: "Reset Layout", action: () => { const r = document.documentElement; r.style.removeProperty("--media-w"); r.style.removeProperty("--timeline-h"); } }, ], Help: window.__FUNPACK_TOUR__ ? [ diff --git a/movie_editor/frontend/player.js b/movie_editor/frontend/player.js index 00c7688a..c9351839 100644 --- a/movie_editor/frontend/player.js +++ b/movie_editor/frontend/player.js @@ -877,7 +877,6 @@ let _tcEl = null; // timecode span (updated live, not rebuilt) let _playBtnEl = null; // play/pause button (glyph updated live, not rebuilt) let _genMsgEl = null, _genBarEl = null, _genFillEl = null; // gen readout, updated live - let _livePreviewEl = null, _livePreviewBadgeEl = null; // Update the generation readout in place on progress ticks (no store re-render, so the // editor stays interactive — frames can be saved, effects added — while a job runs). @@ -890,14 +889,6 @@ _genFillEl.style.width = Math.min(100, Math.round((g.step / g.maxStep) * 100)) + "%"; } } - if (_livePreviewEl && g.livePreviewUrl) { - if (_livePreviewEl.src !== g.livePreviewUrl) _livePreviewEl.src = g.livePreviewUrl; - _livePreviewEl.style.display = ""; - } - if (_livePreviewBadgeEl && g.liveScene >= 0 && g.liveSceneCount > 0) { - _livePreviewBadgeEl.textContent = `Scene ${g.liveScene + 1}/${g.liveSceneCount}`; - _livePreviewBadgeEl.style.display = ""; - } }); // Lightweight per-tick update: never rebuilds the buttons (rebuilding mid-click ate the @@ -1112,7 +1103,6 @@ // store render; live progress ticks update it in place (see the gen-progress listener), // so the rest of the editor isn't rebuilt while a generation runs. _genMsgEl = _genBarEl = _genFillEl = null; - _livePreviewEl = _livePreviewBadgeEl = null; if (["queuing", "running", "pending", "error"].includes(gen.state)) { const busy = gen.state !== "error"; const ro = el("div", "gen-readout" + (gen.state === "error" ? " error" : "")); @@ -1131,22 +1121,6 @@ } canvas.append(ro); } - if (["queuing", "running", "pending"].includes(gen.state) && S.get().models?.live_preview?.enabled) { - const lpLayer = el("div", "pm-live-preview"); - _livePreviewEl = el("img", "pm-live-preview-img"); - _livePreviewEl.alt = "Live preview"; - _livePreviewEl.draggable = false; - if (gen.livePreviewUrl) _livePreviewEl.src = gen.livePreviewUrl; - else _livePreviewEl.style.display = "none"; - _livePreviewBadgeEl = el("div", "pm-live-preview-badge"); - if (gen.liveScene >= 0 && gen.liveSceneCount > 0) { - _livePreviewBadgeEl.textContent = `Scene ${gen.liveScene + 1}/${gen.liveSceneCount}`; - } else { - _livePreviewBadgeEl.style.display = "none"; - } - lpLayer.append(_livePreviewEl, _livePreviewBadgeEl); - canvas.append(lpLayer); - } if (previewAsset?.kind === "image") { const layer = el("div", "pm-media-preview"); const label = el("div", "pm-media-preview-label", previewAsset.name || "Media preview"); diff --git a/movie_editor/frontend/store.js b/movie_editor/frontend/store.js index d1ab0e2b..2b578d80 100644 --- a/movie_editor/frontend/store.js +++ b/movie_editor/frontend/store.js @@ -25,7 +25,7 @@ notice: "", saving: false, unsaved: false, - models: { slots: [], live_preview: { enabled: false, slot_id: null, vae_output: "VAE" } }, // pluggable node config (shared with Models modal) + models: { slots: [] }, // pluggable node config (shared with Models modal) mediaBin: [], // uploaded assets [{id,name,kind,...}] mediaPreviewId: null, // transient image preview in the player (not scene assignment) shortcuts: [], // prompt shortcut library @@ -2689,22 +2689,6 @@ }); } } catch (_) {} - if (state.models?.live_preview?.enabled) { - try { - const lp = await API.livePreview(); - if (lp?.ready && lp.url) { - const sceneNote = (lp.scene_index >= 0 && lp.scene_count > 0) - ? ` · scene ${lp.scene_index + 1}/${lp.scene_count}` - : ""; - updateGenProgress({ - livePreviewUrl: lp.url, - liveScene: lp.scene_index, - liveSceneCount: lp.scene_count, - msg: `${(state.gen.msg || prefix).replace(/\s*·\s*scene \d+\/\d+$/, "")}${sceneNote}`, - }); - } - } catch (_) {} - } }, 700); pollTimer = setInterval(async () => { if (_interrupted) { @@ -2814,7 +2798,7 @@ async function _generateRun(sceneIds, onlyScene, prefix, resetSession) { _interrupted = false; _markGenInFlight(sceneIds); - set({ gen: { state: "queuing", promptId: null, media: [], msg: `${prefix}: queuing…`, step: 0, maxStep: 0, livePreviewUrl: null, liveScene: -1, liveSceneCount: 0 } }); + set({ gen: { state: "queuing", promptId: null, media: [], msg: `${prefix}: queuing…`, step: 0, maxStep: 0 } }); try { const r = await _retryOnTunnel( () => API.generate(state.project.id, onlyScene || null, onlyScene ? null : sceneIds, !!resetSession), @@ -3277,12 +3261,6 @@ // ── pluggable models / exposed controls ────────────────────────────────────── async function loadModels() { try { state.models = await API.getModels(state.project?.id); } catch (_) { state.models = { slots: [] }; } - state.models.live_preview = { - enabled: false, - slot_id: null, - vae_output: "VAE", - ...(state.models.live_preview || {}), - }; if (state.project) { state.project.models = JSON.parse(JSON.stringify(state.models || { slots: [] })); } @@ -3322,44 +3300,6 @@ catch (e) { console.error("saveModels failed", e); } } - async function setLivePreviewEnabled(enabled) { - state.models.live_preview = { - enabled: false, - slot_id: null, - vae_output: "VAE", - ...(state.models.live_preview || {}), - enabled: !!enabled, - }; - if (state.project) { - state.project.models = JSON.parse(JSON.stringify(state.models)); - } - notify(); - try { - state.models = await API.saveModels(state.project?.id, state.models); - notify(); - } catch (e) { console.error("saveModels failed", e); } - } - - async function setLivePreviewSlot(slotId, vaeOutput = "VAE") { - state.models.live_preview = { - enabled: false, - slot_id: null, - vae_output: "VAE", - ...(state.models.live_preview || {}), - slot_id: slotId, - vae_output: vaeOutput || "VAE", - enabled: true, - }; - if (state.project) { - state.project.models = JSON.parse(JSON.stringify(state.models)); - } - notify(); - try { - state.models = await API.saveModels(state.project?.id, state.models); - notify(); - } catch (e) { console.error("saveModels failed", e); } - } - // ── media bin + libraries ───────────────────────────────────────────────────── async function loadMedia() { try { state.mediaBin = (await API.listMedia()).media || []; } catch (_) { state.mediaBin = []; } notify(); } async function uploadMedia(files) { @@ -3631,7 +3571,7 @@ isOverlayAudioTrack, isSeparatedAudioTrack, resizeScene, setSceneGapAfter, splitScene, snapFrames, snapFramesFloor, snapFramesCeil, sceneEffFrames, sceneEffFps, setSourceTrim, trimSceneLeft, slipScene, applyEnginePreset, ENGINE_PRESETS, undo, redo, - refreshPreview, syncFromPreview, applyGlobalPromptQuiet, scheduleGlobalPromptApply, buildGlobalPromptFromTimeline, syncGlobalPromptFromTimeline, generate, generateMontage, generateSelected, selectedSceneCount, renderFinal, exportSelected, saveSelectedToMediaBin, clipSaveableToMediaBin, interrupt, loadModels, loadImageTargets, setModelInput, setModelLink, setLivePreviewEnabled, setLivePreviewSlot, clearNotice, + refreshPreview, syncFromPreview, applyGlobalPromptQuiet, scheduleGlobalPromptApply, buildGlobalPromptFromTimeline, syncGlobalPromptFromTimeline, generate, generateMontage, generateSelected, selectedSceneCount, renderFinal, exportSelected, saveSelectedToMediaBin, clipSaveableToMediaBin, interrupt, loadModels, loadImageTargets, setModelInput, setModelLink, clearNotice, setConditioningSlot, setSamplerSlot, setSamplerInput, setSamplerInputNow, unsetSamplerInput, setStudioInput, setStudioInputNow, loadMedia, uploadMedia, deleteMedia, deleteMediaMany, renameMedia, previewMedia, clearMediaPreview, assignMediaToScene, exportMediaAsset, loadShortcuts, saveShortcut, deleteShortcut, importShortcuts, diff --git a/movie_editor/frontend/styles.css b/movie_editor/frontend/styles.css index 770e646a..f7dc1844 100644 --- a/movie_editor/frontend/styles.css +++ b/movie_editor/frontend/styles.css @@ -448,9 +448,6 @@ input[type=number] { font-family: var(--font-mono); } .gen-readout .pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--amber); animation: pulse 1s infinite; flex: 0 0 auto; } .gen-readout.error { color: var(--danger); } .gen-readout.error .pulse { background: var(--danger); animation: none; } -.pm-live-preview { position: absolute; inset: 12px 12px 52px 12px; display: flex; align-items: center; justify-content: center; pointer-events: none; z-index: 1; } -.pm-live-preview-img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 6px; border: 1px solid var(--line); box-shadow: 0 8px 32px #000a; background: #0006; } -.pm-live-preview-badge { position: absolute; top: 8px; left: 8px; font-family: var(--font-mono); font-size: 11px; color: var(--amber-2); background: #000b; border: 1px solid var(--line); padding: 3px 8px; border-radius: 12px; } .gen-bar { width: 90px; height: 5px; background: #ffffff22; border-radius: 3px; overflow: hidden; flex: 0 0 auto; } .gen-bar-fill { height: 100%; background: var(--amber); border-radius: 3px; transition: width .2s; } .gen-interrupt { font-family: var(--font-mono); font-size: 11px; color: var(--danger); background: transparent; border: 1px solid color-mix(in srgb, var(--danger) 50%, var(--line)); border-radius: 12px; padding: 2px 9px; cursor: pointer; flex: 0 0 auto; } diff --git a/movie_editor/server.py b/movie_editor/server.py index 317b1796..b334f398 100644 --- a/movie_editor/server.py +++ b/movie_editor/server.py @@ -19,7 +19,7 @@ except Exception: # pragma: no cover - only available inside ComfyUI web = None PromptServer = None -from .backend import bridge, builder, config, git_update, live_preview, media, nodes, pipeline_caps, pipeline_deps, pipeline_wiring, projects, workflow_import +from .backend import bridge, builder, config, git_update, media, nodes, pipeline_caps, pipeline_deps, pipeline_wiring, projects, workflow_import from .backend.nle_effects import zoompan_z_expr from .backend.nle_overlays import build_overlay_video_filter, prepare_overlay_export from .backend.timeline import ( @@ -1434,20 +1434,6 @@ if web is not None and PromptServer is not None: sampler_inputs = _run_sampler_inputs(target, active_scene_count, full=p, models=models_cfg) if caps["chain_sampler"]: _attach_scene_anchors(sampler_inputs, media_pack, target) - import json as _json - import tempfile as _tempfile - if live_preview.models_enabled(models_cfg): - lp_dir = _tempfile.mkdtemp(prefix="funpack_live_") - live_preview.reset(lp_dir, active_scene_count) - sampler_inputs = dict(sampler_inputs or {}) - sampler_inputs["funpack_live_preview"] = _json.dumps({ - "dir": lp_dir, - "width": 384, - "max_frames": 24, - "decode_tile_size": 256, - }) - else: - live_preview.clear() graph, report = builder.build(oi, models_cfg, { "prompt": prompt, "seed": _resolve_run_seed(target), "num_frames_per_scene": effective_frames, @@ -1534,35 +1520,6 @@ if web is not None and PromptServer is not None: async def _progress(_req): return web.json_response(bridge.current_progress()) - @routes.get(UI_PREFIX + "/api/live-preview") - async def _live_preview_status(_req): - import os - snap = live_preview.snapshot() - if not snap.get("active"): - return web.json_response({"active": False}) - frame_path = snap.get("frame_path") or "" - ready = bool(frame_path and os.path.isfile(frame_path)) - payload = { - "active": True, - "ready": ready, - "scene_index": snap.get("scene_index", -1), - "scene_count": snap.get("scene_count", 0), - "updated_at": snap.get("updated_at", 0), - } - if ready: - ts = int(float(snap.get("updated_at") or 0) * 1000) - payload["url"] = f"{UI_PREFIX}/api/live-preview/frame?t={ts}" - return web.json_response(payload) - - @routes.get(UI_PREFIX + "/api/live-preview/frame") - async def _live_preview_frame(_req): - import os - snap = live_preview.snapshot() - path = snap.get("frame_path") or "" - if not path or not os.path.isfile(path): - raise web.HTTPNotFound() - return web.FileResponse(path, headers={"Cache-Control": "no-store"}) - @routes.get(UI_PREFIX + "/api/log") async def _log(req): try: diff --git a/movie_editor/tests/test_builder.py b/movie_editor/tests/test_builder.py index 12cf0d9e..3ce4a549 100644 --- a/movie_editor/tests/test_builder.py +++ b/movie_editor/tests/test_builder.py @@ -211,29 +211,3 @@ def test_movie_editor_scene_ratings_in_studio_settings(): {"index": 2, "rating": "Missing action"}, ] assert "_movie_editor_scene_ratings" not in graph["studio"]["inputs"] - - -def test_live_preview_wires_preview_vae_separately(): - models = { - "full_control": True, - "live_preview": {"enabled": True, "slot_id": "pv", "vae_output": "VAE"}, - "slots": [ - {"id": "u", "node_class": "UnetLoader", "inputs": {}, "wires": {"MODEL": "port:FunPackStudio.model"}}, - {"id": "c", "node_class": "ClipLoader", "inputs": {}, "wires": {"CLIP": "port:FunPackStudio.clip"}}, - {"id": "v", "node_class": "VaeLoader", "inputs": {}, "wires": { - "VAE": ["port:FunPackLTXAVSceneChainSampler.vae", "port:LTXVAudioVAEDecode.audio_vae"], - }}, - {"id": "pv", "node_class": "VaeLoader", "inputs": {"vae_name": "taehv.safetensors"}, "wires": {}}, - {"id": "ip", "node_class": "ImgProc", "inputs": {}, - "wires": {"latent": "port:FunPackStudio.latent", - "Latent": "port:LTXVConcatAVLatent.audio_latent", - "output_image": "port:FunPackStudio.source_image"}, - "input_sources": {"vae": "out:v:VAE"}}, - {"id": "li", "node_class": "LoadImage", "inputs": {}, "wires": {"IMAGE": "node:ip:image"}}, - ], - } - graph, report = builder.build(OI, models, PARAMS) - assert graph["sampler"]["inputs"]["vae"] == ["slot_v", 0] - assert graph["sampler"]["inputs"]["preview_vae"] == ["slot_pv", 0] - assert any("preview_vae" in w for w in report["wired"]) - assert report["blocking"] == [] diff --git a/samplers.py b/samplers.py index 97fda2af..b26166f3 100644 --- a/samplers.py +++ b/samplers.py @@ -2095,14 +2095,6 @@ class FunPackLTXAVSceneChainSampler: "multiline": True, "tooltip": "Optional media_ref → filename map for image-type i2v guides in custom guide stacks.", }), - "preview_vae": ("VAE", { - "tooltip": "Optional fast preview VAE (e.g. taehv). When wired, Movie Editor can show per-scene animated previews while the chain runs.", - }), - "funpack_live_preview": ("STRING", { - "default": "", - "multiline": False, - "tooltip": "Internal JSON from Movie Editor: preview output directory and encode options.", - }), }, "hidden": { "unique_id": "UNIQUE_ID", @@ -3305,7 +3297,6 @@ class FunPackLTXAVSceneChainSampler: refinement_key_input="", funpack_scene_guides="", funpack_scene_anchors="", funpack_scene_media_refs="", - preview_vae=None, funpack_live_preview="", unique_id=None, prompt=None): if not isinstance(positive, list) or not positive: raise ValueError("positive conditioning must contain at least one scene entry.") @@ -3328,15 +3319,11 @@ class FunPackLTXAVSceneChainSampler: carry_i2v_guides, mid_scene_guide, mid_scene_guide_strength, embed_guidance, embed_guidance_strength, transition_duration, decode_tile_size, refinement_key_input, embed_guidance_source, - preview_vae=preview_vae, funpack_live_preview=funpack_live_preview, ) max_scene_count = max(1, int(max_scenes)) scene_conditionings = positive[:max_scene_count] scene_count = len(scene_conditionings) - live_cfg = self._parse_live_preview_config(funpack_live_preview) - if preview_vae is not None and live_cfg.get("dir"): - self._init_live_preview(live_cfg, scene_count) time_scale = self._time_scale(vae) video_frames = self._validate_template_length(latent_template, num_frames_per_scene, time_scale) video_overlap = self._overlap_frames(latent_template, frame_overlap, time_scale) @@ -3537,10 +3524,6 @@ class FunPackLTXAVSceneChainSampler: f"Scene {scene_index + 1}: seed={scene_seed}, text={scene_meta['text']}" + (f" | encode≠text" if scene_meta["encode_text"] != scene_meta["text"] else "") ) - if preview_vae is not None and live_cfg.get("dir"): - self._emit_live_scene_preview( - preview_vae, sampled, scene_index, scene_count, live_cfg, - ) del scene_cond, scene_positive, scene_negative, scene_conditionings, chunk, sampled @@ -3668,58 +3651,6 @@ class FunPackLTXAVSceneChainSampler: return None return [groups[k] for k in sorted(groups)] - def _parse_live_preview_config(self, raw): - if not raw: - return {} - if isinstance(raw, dict): - return raw - try: - import json as _json - out = _json.loads(str(raw)) - return out if isinstance(out, dict) else {} - except Exception: - return {} - - def _init_live_preview(self, cfg, scene_count): - try: - from movie_editor.backend import live_preview as _lp - except Exception: - try: - from .movie_editor.backend import live_preview as _lp # type: ignore - except Exception: - return - _lp.reset(cfg.get("dir", ""), scene_count) - - def _emit_live_scene_preview(self, preview_vae, scene_latent, scene_index, scene_count, cfg): - import os - import shutil - try: - from movie_editor.backend import live_preview as _lp - except Exception: - try: - from .movie_editor.backend import live_preview as _lp # type: ignore - except Exception: - return - preview_dir = str(cfg.get("dir") or "") - if not preview_dir: - return - os.makedirs(preview_dir, exist_ok=True) - tile = int(cfg.get("decode_tile_size") or 256) - decoded = self._decode_for_preview(preview_vae, scene_latent, decode_tile_size=tile) - path = os.path.join(preview_dir, f"scene_{int(scene_index):03d}.webp") - current = os.path.join(preview_dir, "current.webp") - if self._save_batch_preview( - decoded, - path, - max_frames=int(cfg.get("max_frames") or 24), - width=int(cfg.get("width") or 384), - ): - try: - shutil.copy2(path, current) - except Exception: - current = path - _lp.publish(int(scene_index), current, scene_count) - def _save_batch_preview(self, decoded, path, max_frames=16, width=256): """Save a decoded video tensor [T,H,W,C] in 0..1 as a downscaled animated webp.""" try: @@ -3769,8 +3700,7 @@ class FunPackLTXAVSceneChainSampler: latent_template, num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed, carry_i2v_guides, mid_scene_guide, mid_scene_guide_strength, embed_guidance, embed_guidance_strength, transition_duration, - decode_tile_size, refinement_key_input, embed_guidance_source="relative", - preview_vae=None, funpack_live_preview=""): + decode_tile_size, refinement_key_input, embed_guidance_source="relative"): """Sample one chain per Studio-packed variant entry (seed + index), persisting each result (latent + preview + per-entry cond + manifest) under ComfyUI temp for rating in Studio. Reuses sample() per entry with only the seed changed, so each entry is a clean generation.""" @@ -3811,7 +3741,6 @@ class FunPackLTXAVSceneChainSampler: embed_guidance_strength=embed_guidance_strength, embed_guidance_source=embed_guidance_source, transition_duration=transition_duration, decode_tile_size=decode_tile_size, refinement_key_input=key, - preview_vae=preview_vae, funpack_live_preview=funpack_live_preview, unique_id=None, prompt=None, ) last = out