ComfyUI Extension: ComfyTimeline

Authored by FemtoStudio

Created

Updated

0 stars

Run ComfyUI workflows without the setup

No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.

Maya-style global timeline for ComfyUI — a shared bottom-panel bar (scrub, range, transport, keyframe dope-sheet) + curve editor + frame-accurate viewer, with a public JS & Python API any node can connect to.

Looking for a different extension?

Custom Nodes (2)

README

ComfyTimeline — a global timeline standard for ComfyUI

A VFX-style global timeline for ComfyUI — one bottom-panel bar that owns frame state (playhead, animation range, playback range, fps, transport, loop) for the whole graph, plus a VFX Graph-Editor-style curve editor node and a frame-accurate image-sequence viewer.

It also ships ComfyTimeline, a small public API so any node or extension can connect to the timeline — read the current frame/range/fps, react to scrubbing and playback, or draw keyframe ticks on the bar — from both JS and Python.

Status: apiVersion 1.0.0. The surface listed in API_REFERENCE.md is the committed stable contract.


Install

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

Restart ComfyUI, then open the bottom panel → "Timeline" tab (the panel is hidden by default — toggle it from View → Bottom Panel, or press Alt+T to jump straight to the Timeline tab). No Python dependencies beyond ComfyUI itself.

Transport shortcuts

Defaults (rebindable in Settings → Keybindings, discoverable in the command palette under "Timeline:"):

| key | action | |---|---| | Space | play / pause | | → / ← | next / previous frame (wraps in the play range) | | Alt+T | open the Timeline panel tab |

Transport buttons on the bar: ⏮ go-to-start · ‹ step · ▶/⏸ play · › step · ⏭ go-to-end · ⟳ loop · EF play-every-frame (no frame-drop, vs real-time).

Nodes provided:

  • Timeline Animation — author animation curves (Hermite tangents, pan/zoom, marquee, key/tangent editing). Its keys show as ticks on the global bar.
  • Timeline Image Viewer — scrub/play an IMAGE batch against the global playhead.

Connect a node to the timeline

Python (read the frame/range/fps in execute())

from comfy_api.latest import io
try:
    from comfy_timeline import timeline_inputs, read_timeline
except ImportError:                      # timeline pack not installed
    timeline_inputs = lambda **k: []
    read_timeline = None

class MyNode(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="MyNode",
            inputs=[io.Image.Input("image"), *timeline_inputs()],
        )

    @classmethod
    def execute(cls, image, **tl):
        t = read_timeline(**tl)
        frame = t.current_frame          # the live playhead at queue time
        ...

timeline_inputs() adds standard current_frame / start_frame / end_frame / fps widgets. The frontend auto-binds and hides them and drives them from the bar, so the value the user set on the bar bakes into the prompt at queue time — cache-correct, no server state. Don't convert these to socket inputs (force_input); they must stay widgets.

JS (react to the timeline live — no boilerplate for "read")

If your node already declares the standard widgets (via timeline_inputs() or by hand: current_frame, or both start_frame+end_frame), the auto-bind wires them with zero JS.

To react (e.g. repaint at the new frame), subscribe to the model:

import { app } from "/scripts/app.js";

app.registerExtension({
  name: "MyExt",
  setup() {
    const T = window.ComfyTimeline;          // capability check
    if (!T || parseInt(T.apiVersion) < 1) return;
    const off = T.model.subscribe(["frame", "range"], (e) => {
      myRepaint(T.model.currentFrame);       // or e.detail.frame
    });
    // call off() to unsubscribe
  },
});

To drive the timeline (e.g. a "shot info" node sets the range):

window.ComfyTimeline.model.setAnimRange(1001, 1100);
window.ComfyTimeline.model.setPlayRange(1001, 1100);

Events

window.ComfyTimeline.model is an EventTarget. Each event's detail carries a source token — pass your own stable token to mutators and ignore events where detail.source is your own, to avoid feedback loops.

| event | detail | when | |---|---|---| | frame | { frame:int, source } | playhead moved | | range | { which:'anim'\|'play', animStart, animEnd, playStart, playEnd, source } | a range changed | | fps | { fps:float, source } | fps (or speed) changed | | play | { playing:bool, source } | transport toggled | | loop | { loop:bool, source } | loop toggled | | mode | { mode:'realtime'\|'everyframe', source } | playback mode toggled | | change (on curves) | { curveId } | a curve was registered / removed / edited |

Full method/field reference: API_REFERENCE.md. TypeScript/editor typings: web/js/comfytimeline.d.ts.


Versioning

  • window.ComfyTimeline.apiVersionsemver of the public surface. Minor = additive, major = breaking. Feature-detect with it.
  • window.ComfyTimeline.schemaVersion — integer; only the app.graph.extra.timeline serialization. Independent of apiVersion.

Migration

The pre-1.0 window.timelineAnim cross-node registry has been removed. Use window.ComfyTimeline (model/curves/events) instead.

License

MIT — see LICENSE.

Run ComfyUI workflows without the setup

No installs, no CUDA version roulette, no GPU sitting idle on your bill. Bring a workflow and run it in the browser.

Learn more