Nodes/comfyui-obvpm-timeline/H3 MCtx Timeline (obvpm)
ComfyUI Node

H3 MCtx Timeline (obvpm)

Meet the Timeline node

By chanon·Created 21 days ago·Updated 2 days ago· 33
H3 MCtx Timeline (obvpm)
    • pin_specs
    • length
    • sequence
    • upscaling
    sequence
    base_folderproject1
    preview_filenameobvpm_h3_preview
    export_filename_prefixfull
    crf19
    auto_addfalse
    pin_state
    restore_groups[restore]
    skip_restore_nodes[skip]
    snap_cuts_to_gridtrue
    level_locktrue
    level_lock_frames12
    level_lock_flickertrue
    crossfadetrue
    crossfade_frames0
    audio_declicktrue
    level_lock_localtrue
    duration_seconds8.0
    upscalingfalse

    MiniMax H3 generates 4–15 second clips with native stereo audio, which is genuinely great - and completely useless if all you can do with them is watch them one at a time in a folder. The Timeline node is the answer to that: a mini video editor where the takes you generate sit as blocks you can drag, cut and preview, and - the part that actually matters - it's also where you say which clip the next generation continues from.

    It's the hub of the pack. Everything else in comfyui-obvpm-timeline is either feeding it, saving into it, or refining what comes out of it.

    The thing it's really for

    Every take this pack saves is a clip pair: an MP4 plus a .mctx.safetensors sidecar holding the clip's full raw video+audio latents and its lineage (who its parent was, at which raw frame the join sits). Because the latents are stored, extending a clip continues from those, not from re-encoded pixels - which is why joins don't flicker or change grade. The Timeline is where you arrange those takes and pick the extension point.

    Pin a clip's head or tail in the widget and the pin_specs output becomes the source of your next generation. That's the loop the whole pack is built around: generate a clip, look at it, add it to the timeline, extend from it, and redo any part you don't like later.

    The inputs that matter

    You'll touch three things on this node.

    sequence is the cut, one clip per line, as output-relative paths - the same values the loaders list. You can edit it by hand (pencil button) and everything the strip can express is expressible here:

    clip_00007.mp4                       # a whole clip
    clip_00012.mp4 @ 22                  # enters at delivered frame 22
    clip_00012.mp4 @ 22..250             # enters at 22, exits at 250
    ~ 40                                 # a 40-frame gap
    # lines starting with a hash are comments
    clip_00019.mp4 [crossfade=off]       # per-seam override
    loop                                 # make the cut a ring
    

    base_folder scopes the clip picker and holds the preview and exports. Give each Timeline node its own preview_filename if you run more than one, or they'll overwrite each other's preview.

    duration_seconds is how long the next generation should be - you think in seconds, the model takes frames, and the length output does the conversion and snapping for you. That snap depends on the pin: a masked or both-mode arrival needs the shared AV grid (39/90/141/192/243 frames) so the audio ticks come out whole; anything else takes H3's ordinary 17k+5 ladder.

    Then the seam-repair widgets, all of which only affect the preview and export, never a generation: level_lock (a take opens at a different luma level than the clip it continues - measured at ~6x the frame-to-frame noise - and this corrects it and decays to nothing over level_lock_frames), level_lock_flicker for the joins that oscillate instead of stepping, crossfade across the overlap both takes rendered, audio_declick, and snap_cuts_to_grid, which keeps cuts on H3's 17-frame grid so extending from them stays exact.

    Outputs: pin_specs (wire to H3 MCtx Apply Pins), length (wire to the empty AV latent's length), sequence (wire to H3 Join Latents for the refine pass), and upscaling - a boolean on a wire, so a Mute If gate can decide whether a Run generates or refines.

    One warning: previewing and exporting are button-driven. They never run as part of a workflow queue.

    Install

    cd ComfyUI/custom_nodes
    git clone https://github.com/chanon/comfyui-obvpm-timeline
    

    Restart ComfyUI. No Python dependencies - the pyproject deliberately declares none, since torch, safetensors and av already ship with ComfyUI. You need a ComfyUI from 2026-08-13 or later for the H3 nodes in core. In ComfyUI Manager, search the pack title comfyui-obvpm-timeline; every node in it ends in (obvpm), so typing obvpm in the node menu finds the whole set at once.

    The bundled workflow also uses companion packs (comfyui-obvpm, ComfyUI-KJNodes, the MiniMax H3 latent upscaler, the H3 turbo LoRA loader, Spectrum, rgthree). Manager's Install Missing Custom Nodes resolves those.

    Where people get burned

    • Clips that don't match resolution can't be in one chain. Latents can't be resized, so the timeline rejects a mismatched drop up front rather than producing something weird.
    • Export ≠ the widget. Export "promotes" the preview: it rebuilds it if stale, then copies it under export_filename_prefix. It's byte-identical to the preview by construction.
    • Level lock is not a fix for everything. It's a small decaying correction at joins. If your take drifts for 20 frames, that's a generation problem, not a seam problem.
    • Segments that look processed in the wrong order: the [...] overrides belong to the join on that clip's left. Unknown keys are ignored (a wrong setting degrades to the default), but a malformed gap fails loudly - content loss is treated differently from a bad preference.
    Categoryobvpm/h3

    Inputs (19)

    NameTypeDefaultDescription
    sequenceSTRINGOne clip per line, in playback order, as output-relative paths (the same values the H3 loaders list), e.g. h3/clip_00001.mp4. Append ' @ N' to force a clip to enter at delivered frame N instead of the derived seam. Lines starting with # are ignored.
    base_folderSTRINGproject1Output-relative folder this node works in: scopes the clip picker and seam suggestions, and holds the preview file and exports. Empty = the output root.
    preview_filenameSTRINGobvpm_h3_previewName (no extension) of the single preview file, written into base_folder and overwritten on every full build. Give each Timeline node its own name if you use several.
    export_filename_prefixSTRINGfullFilename prefix for the export button; written into base_folder with the usual counter, like core save nodes.
    crfINT190–51H.264 quality for re-encoded seam bridges and the export (lower = better, bigger). Mostly a FALLBACK: a bridge is written at the crf its own clip was encoded at, so it splices without a quality step (see source_crf). This applies where that cannot be read.
    auto_addBOOLEANfalseDeprecated and unused. New takes are offered by the Result Preview's '+ add to timeline' button instead.
    pin_stateoptSTRING
    restore_groupsoptSTRING[restore]Keyword for the 'load settings' button on the selected clip: settings are applied only to nodes inside GROUPS whose name contains this text (case-insensitive; comma-separate for several). Tag a group by putting [restore] in its title. Empty restores nothing, so the button cannot fire by accident. Widget values and bypass/mute state only -- nothing is rewired or created, top-level nodes only, and result previews are always skipped.
    skip_restore_nodesoptSTRING[skip]Nodes whose TITLE contains this text are left alone by 'load settings', even inside a matching group (case-insensitive; comma-separate for several). Rename a node to include [skip] to pin its current values. Empty skips nothing.
    snap_cuts_to_gridoptBOOLEANtrueHow a dragged seam behaves. ON: the cut snaps to H3's 17-frame latent grid (~0.7s steps), so extending or prepending from it is always exact. OFF: frame-accurate cuts for playback and export; a pin taken from an off-grid cut is shifted to the nearest latent-grade frame and the log says where the seam landed.
    level_lockoptBOOLEANtrueMatch each clip's opening to the clip it continues. A take opens at a different level than its parent -- measured at ~6x the frame-to-frame noise -- and settles back over about half a second; the step is what you see. This corrects the opening and decays to nothing by level_lock_frames, so nothing accumulates down a chain. Affects the full preview and the export.
    level_lock_framesoptINT122–120Frames the correction spans (24 = 1s). The default 12 is where measured transients have decayed. Longer flattens real lighting changes; shorter reads as a ramp.
    level_lock_flickeroptBOOLEANtrueAlso straighten a wobble in those opening frames. Some joins do not step at all but oscillate for ~6 frames, which reads as flicker; this puts them on the trend line they should have followed.
    crossfadeoptBOOLEANtrueFade the join across the overlap both takes rendered, instead of cutting at it. A take re-renders its parent's last frames before continuing; when that re-render was saved (see the Save node's untrimmed_images) the join becomes a handover between two renderings of the SAME moment rather than a cut between two different ones. Takes saved without it are simply not crossfaded.
    crossfade_framesoptINT00–2400 = fade across the whole stored overlap, which is what the measurements support: the two renderings disagree almost as much at the overlap's start as its end, so a short fade ramps part of the step and cuts the rest. Lower only to taste.
    audio_declickoptBOOLEANtrueAt every boundary the audio crossfade does NOT cover, taper 5ms out of the outgoing clip and 5ms into the incoming one. On a MASKED or BOTH route that is every boundary in the sequence: only a guided window is re-rendered, so only a guided join has a second recording of the same moment to fade across (see nodes_save.guided_sides). Even on a guided route it is still most of them -- a cut moves the join away from where the overlap was rendered, and butt joins and the edges of empty space never had one. It removes the CLICK -- the one-sample jump at the join -- but cannot hide a change in room tone, which needs real overlapping material. The cut's own start and end are left alone.
    level_lock_localoptBOOLEANtrueLet the level lock correct different parts of the picture by different amounts, instead of the whole frame by one. Needs the crossfade, because the per-region measurement only exists where both takes rendered the same moment: the fade measures it there and the lock carries it forward, fading to whole-frame across level_lock_frames. Measured on the one join whose takes both carry the data, it removed 39% of the per-region difference at the first delivered frame and 31% at the second, without moving the overall level (0.085 luma) or softening detail. Joins with nothing stored to measure from are simply corrected whole-frame, as before.
    duration_secondsoptFLOAT8.00.2–150How long the NEXT generation should be. Wire the node's `length` output to the empty AV latent (or the r2v node's length): seconds is the unit you think in, frames is what the model takes, and the node converts and snaps between them. The snap depends on the pin: a masked or both arrival needs the shared AV grid (39/90/141/192/243 frames = 1.63/3.75/5.88/8.00/10.13 s), the only lengths whose audio ticks come out whole -- off it a prepend clicks. Anything else takes the ordinary 17k+5 ladder (~0.7 s steps).
    upscalingoptBOOLEANfalseWhich half of the workflow this Run is for. Off: generate the next take from the pin. On: refine the whole timeline as one piece and render the full sequence. Flipped from the strip's upscale toggle; comes out on the upscaling output for the Mute If gates.

    Outputs (4)

    NameTypeDescription
    pin_specsOBVPM_H3_PINSPECSPin spec for the clip pinned in the timeline widget (extend/prepend); empty when nothing is pinned. Wire to H3MCtxApplyPins to generate the next clip straight from the timeline.
    lengthINTduration_seconds as a frame count, snapped to the grid THIS pin needs -- the shared AV grid when the arrival masks (so the audio join stays exact), the ordinary 17k+5 ladder otherwise. Wire to the empty AV latent's length.
    sequenceSTRINGThe clip list exactly as the widget holds it, so a pass that walks the timeline reads the SAME list the cut is built from rather than a copy that has to be kept in step. Wire to H3 Join Latents' sequence. Verbatim, comments and cut markers included -- readers strip what they do not need.
    upscalingBOOLEANThe toolbar's upscale toggle, on a wire: true when a Run should refine and render the timeline, false when it should generate the next take. Drive a Mute If gate on each branch from here (the generation branch muted when this is true, the refine branch when it is false, through a Not node) so one switch decides and the branches cannot disagree.