Document the uninterruptible core video decode

This commit is contained in:
ausboss
2026-08-13 22:58:46 -05:00
parent 404e215ddd
commit dc7abd5100
2 changed files with 32 additions and 3 deletions
+20 -1
View File
@@ -24,7 +24,8 @@ exactly one saved-video surface per node.
`audio` output.
- **video** (optional): A core `VIDEO` handle, e.g. Load Video's `video`
output. Connecting it supersedes the `frames` and `audio` inputs — the
video's own frames and track are encoded instead.
video's own frames and track are encoded instead. Core decodes it in one
uninterruptible step; see *The one phase Cancel cannot reach* below.
## Frame rate precedence
@@ -47,3 +48,21 @@ while a long batch is written. The node fills its progress bar frame by frame,
and **Cancel** stops the encode within a frame instead of at the end of the
batch. Muxed audio is encoded after the picture, so the bar restarts and counts
that track's AAC frames; **Cancel** stops there within one of them.
### The one phase Cancel cannot reach
Connecting `video` adds a step this node does not control. ComfyUI core reads
the whole file — every frame, decoded to float32 — before the first frame
reaches this pack, and the core call that does it, `VideoInput.get_components()`,
takes no progress or interrupt argument to pass one in. So that phase shows no
progress and does not stop on **Cancel**: the cancel is registered right away
(the decode runs off the execution thread, so the server still answers) and
takes effect at the first frame of the encode, once control returns here.
How long that lasts scales with pixels, not with file size. Measured on one
desktop: ten seconds of 832x480 at 16 fps came back in about 0.6 s, while ten
seconds of 1080p at 30 fps took about 7 s and held 7.5 GB of frames in memory.
Minutes of high-resolution footage are the case to avoid — RAM runs out before
the wait does. Wiring Load Video's `frames` output instead of `video` keeps the
whole run interruptible and progress-tracked, since that decode is this pack's
own loop.
+12 -2
View File
@@ -87,7 +87,11 @@ class AusBossSaveVideo:
"Optional core VIDEO. When connected, its frames and audio "
"supersede the frames and audio inputs, and its own frame "
"rate wins over the fps widget; a differing widget rate is "
"logged once to the console."
"logged once to the console. ComfyUI core decodes the whole "
"file into memory before this node sees a frame: that phase "
"reports no progress and does not stop on Cancel, which "
"takes effect once the encode starts. Wiring frames instead "
"keeps the whole run interruptible."
),
},
),
@@ -105,7 +109,13 @@ class AusBossSaveVideo:
if folder_paths is None:
raise RuntimeError("Save Video requires ComfyUI's folder_paths at runtime.")
# Pulling a VIDEO's components decodes the whole file, so it joins the
# encode on a worker thread rather than stalling the event loop.
# encode on a worker thread rather than stalling the event loop. It is
# the one phase here that cannot be instrumented: core declares
# VideoInput.get_components() with no arguments (comfy_api/latest/
# _input/video_types.py) and its VideoFromFile implementation runs a
# bare demux loop, so there is no progress or interrupt seam to pass.
# A Cancel raised during it is only noticed by the first frame check
# inside encode_video, once control comes back.
source = await asyncio.to_thread(video_components, video)
if source is not None:
# A connected VIDEO supersedes the frames/audio inputs and keeps