diff --git a/movie_editor/backend/bridge.py b/movie_editor/backend/bridge.py index 1003ee0b..5eb3b1e4 100644 --- a/movie_editor/backend/bridge.py +++ b/movie_editor/backend/bridge.py @@ -315,6 +315,12 @@ def _install_progress_hook(): pass +def reset_progress() -> None: + _progress["value"] = 0 + _progress["max"] = 0 + _progress["ts"] = 0.0 + + def current_progress() -> dict: _install_progress_hook() return {"value": _progress["value"], "max": _progress["max"]} diff --git a/movie_editor/frontend/store.js b/movie_editor/frontend/store.js index a9ae65fd..eded7f18 100644 --- a/movie_editor/frontend/store.js +++ b/movie_editor/frontend/store.js @@ -459,7 +459,14 @@ if (_interrupted) return; try { const pr = await API.progress(); - if (pr && pr.max > 0) updateGenProgress({ step: pr.value, maxStep: pr.max }); + if (pr && pr.max > 0) { + const head = (state.gen.msg || prefix).replace(/\s*·\s*sampling \d+\/\d+$/, ""); + updateGenProgress({ + step: pr.value, + maxStep: pr.max, + msg: `${head} · sampling ${pr.value}/${pr.max}`, + }); + } } catch (_) {} }, 700); pollTimer = setInterval(async () => { @@ -491,7 +498,7 @@ resolve(false); return; } - const step = (state.gen.maxStep > 0) ? ` · step ${state.gen.step}/${state.gen.maxStep}` : ""; + const step = (state.gen.maxStep > 0) ? ` · sampling ${state.gen.step}/${state.gen.maxStep}` : ""; updateGenProgress({ state: s.state, msg: `${prefix} ${_elapsed()}${step}` }); } } catch (e) { diff --git a/movie_editor/server.py b/movie_editor/server.py index aeb85e8b..a448e0ea 100644 --- a/movie_editor/server.py +++ b/movie_editor/server.py @@ -567,6 +567,7 @@ if web is not None and PromptServer is not None: oi = await bridge.object_info() except Exception as e: # noqa: BLE001 return web.json_response({"detail": f"Node registry unavailable: {e}"}, status=502) + bridge.reset_progress() bridge.current_progress() # ensure the sampler step-progress hook is installed active_scenes = [s for s in target.scenes if not s.excluded] active_scene_count = len(active_scenes) diff --git a/samplers.py b/samplers.py index 85143dcd..5dc3dcaf 100644 --- a/samplers.py +++ b/samplers.py @@ -2448,7 +2448,8 @@ class FunPackLTXAVSceneChainSampler: result.pop("noise_mask", None) return result - def _sample_chunk(self, model, sampler, sigmas, seed, cfg, positive, negative, latent): + def _sample_chunk(self, model, sampler, sigmas, seed, cfg, positive, negative, latent, + pbar=None, step_offset=0): if sampler is None: raise ValueError("sampler input is required.") if not isinstance(sigmas, torch.Tensor): @@ -2456,9 +2457,15 @@ class FunPackLTXAVSceneChainSampler: latent = self._clone_latent(latent) samples = latent["samples"] noise = comfy.sample.prepare_noise(samples, int(seed)) + + def _progress_cb(step, _denoised, _x, _total_steps): + if pbar is not None: + pbar.update_absolute(step_offset + int(step) + 1) + sampled = comfy.sample.sample_custom( model, noise, float(cfg), sampler, sigmas, positive, negative, samples, noise_mask=latent.get("noise_mask"), seed=int(seed), + callback=_progress_cb if pbar is not None else None, ) latent["samples"] = sampled latent.pop("noise_mask", None) @@ -2845,6 +2852,16 @@ class FunPackLTXAVSceneChainSampler: first_scene_seed = self._scene_seed(scene_conditionings[0]) if first_scene_seed is None: first_scene_seed = int(seed) + + steps_per_scene = max(1, int(len(sigmas)) - 1) + total_sampling_steps = scene_count * steps_per_scene + pbar = None + try: + pbar = comfy.utils.ProgressBar(total_sampling_steps) + pbar.update_absolute(0) + except Exception: + pass + for scene_index, scene_cond in enumerate(scene_conditionings): scene_positive = [scene_cond] scene_negative = negative @@ -2910,6 +2927,7 @@ class FunPackLTXAVSceneChainSampler: _temporal_applied = True sampled = self._sample_chunk( model, sampler, sigmas, scene_seed, cfg, scene_positive, scene_negative, chunk, + pbar=pbar, step_offset=scene_index * steps_per_scene, ) if _temporal_applied: if _temporal_prev_wrapper is not None: