Nodes/AM VFX Tools/AM Read Video
ComfyUI Node

AM Read Video

Decode anything, skip the decode when you can

By am-pipeline-prod·Created 4 months ago·Updated 3 months ago· 3
AM Read Video
    • image
    • mask
    • audio
    • resolved_path
    • info
    • width
    • height
    • frame_rate
    • frame_count
    • video
    file_path
    frame_modeall
    first_frame1
    last_frame-1
    beforehold
    afterhold
    input_colorspaceDisplay/Gamma 2.2 Rec.709 - Display
    raw_datafalse
    working_colorspaceDisplay/sRGB - Display
    reformat_modeoff
    scale1.00
    presetWidth/Height
    target_width1920
    target_height1080
    resize_typefit
    filtercubic
    output_dtypefp16
    show_previewtrue

    Stock LoadVideo in ComfyUI is a thin thing: mp4 and h264, a limited frame range, no alpha. AM Read Video is the version that reads like a studio I/O node - PyAV under the hood, so it decodes h264, h265, ProRes, DNxHR, VP9 and friends, seeks frame-accurately, handles alpha-bearing codecs like ProRes 4444, and color-manages the result. And its video output socket is the pack's signature trick: wire only that and the decode never even happens.

    It's part of comfyui-am-vfx-tools ("AM VFX Tools" category), Adrian Meyer's 13-node VFX toolkit. This is the node to pair with AM Write Video (PyAV on both ends) for a real video round-trip that stock nodes can't do.

    How it works

    PyAV wraps libav, so you get the whole codec zoo instead of the h264-only special. Frame-accurate seek means a range read seeks to the first frame you want rather than decoding everything up to it. Alpha-bearing pixel formats decode losslessly when the codec supports them - ProRes 4444 and 4444 XQ being the big ones - and the alpha lands on the mask socket per the stock convention (mask = 1 - alpha).

    The widgets are the pack's standard I/O set: frame_mode (single/range/all) with before/after edge policies (hold/loop/bounce/black), input_colorspaceworking_colorspace via OCIO (with raw_data to bypass), and an optional reformat block. The default input space is Display/Gamma 2.2 Rec.709 - the studio dailies standard - because most video containers don't carry a reliable colorspace tag, so an explicit value is usually more correct than trusting the file.

    The trick: the video socket

    This is the one worth understanding. The video output is a VideoFromFile that references the source on disk - it doesn't hold pixels. Wire only that socket downstream (say, into an AM Write Image or an API node that takes a file) and PyAV never decodes the frames; peak RAM stays at file-handle level. Wire both image and video in the same graph and you can run a color-managed IMAGE branch for inference and a raw VIDEO branch for an API call that wants the original file - parallel branches, both valid.

    Caveat, straight from the author's tooltip: the video passthrough is raw - source colorspace, resolution, codec, untouched by the node's OCIO/Reformat settings. Those apply to image only. If you need the color transform, you either use the image branch or chain an AM transform node on the video.

    Outputs

    image, mask, audio (or a silent stub), resolved_path, info (dimensions/codec/pixfmt/fps/count), width, height, frame_rate (native, from the PyAV header), frame_count, and video.

    Installing it

    cd ComfyUI/custom_nodes
    git clone https://github.com/am-pipeline-prod/comfyui-am-vfx-tools.git
    cd comfyui-am-vfx-tools
    pip install -r requirements.txt
    

    Restart ComfyUI, or search comfyui-am-vfx-tools in ComfyUI Manager. The av>=14.0.0 (PyAV) dependency - and note the alpha support needs PyAV ≥ 11, which the pin covers - comes from that pip line.

    Where people get burned

    The raw-video surprise: people wire video, run a color transform, and see no change because the video socket is passthrough. That's by design. And if a Linux distro forces a system-FFmpeg link (pip install --no-binary av), verify ffmpeg -h encoder=prores_ks lists the 4444 profiles or alpha encode will silently fall apart. Default decode of a long clip also slurps the whole thing into RAM via the image branch - use the video socket when you only need the file.

    CategoryAM VFX Tools

    Inputs (18)

    NameTypeDefaultDescription
    file_pathSTRINGAbsolute path to the video container. Use the 📂 Browse button to populate from the native dialog.
    frame_modeCOMBOallWhich frames to decode from the container. single = only `first_frame`. range = `first_frame`..`last_frame` inclusive (with `before`/`after` policy outside the container). all = every frame in the container.
    first_frameINT1-999999–999999Frame index in single mode; lower bound in range mode (1-based). Ignored in all mode. The 🔍 Detect Range button auto-fills this.
    last_frameINT-1-1–999999Range upper bound (inclusive, 1-based). -1 = auto = container's frame count. The 🔍 Detect Range button reads it from the PyAV header.
    beforeCOMBOholdEdge policy below the container's frames when `frame_mode=range` and the requested range starts before frame 1. hold = clamp to first; loop = wrap; bounce = ping-pong; black = synthesize black.
    afterCOMBOholdEdge policy above the container's frames when the requested range extends past the last frame. Mirrors `before`.
    input_colorspaceCOMBODisplay/Gamma 2.2 Rec.709 - DisplaySource colorspace of the container (HD video typically Gamma 2.2 Rec.709 in studio dailies, Rec.1886 Rec.709 for broadcast). The OCIO transform converts from this to `working_colorspace`. Pick `raw` to skip — but most video containers don't carry a reliable colorspace tag, so an explicit value is usually correct.
    raw_dataBOOLEANfalseWhen On, skip the OCIO transform — pixels pass through unchanged. `input_colorspace` and `working_colorspace` are ignored.
    working_colorspaceCOMBODisplay/sRGB - DisplayTarget colorspace for the IMAGE output — the space downstream nodes will see.
    reformat_modeCOMBOoffReformat mode. off = bypass, output matches input. scale = uniform scale by `scale` (other widgets ignored). to_box = resize/crop to a target W×H from `preset` or `target_width`/`target_height`.
    scaleFLOAT1.000.01–16Uniform scale factor. Used when `reformat_mode=scale`; ignored otherwise. Output dimensions are round(input × scale).
    presetCOMBOWidth/HeightNamed output format. Used when `reformat_mode=to_box`. `Width/Height` = use the `target_width` / `target_height` widgets below. Any other entry overrides those widgets with the preset's resolution.
    target_widthINT19201–16384Target output width in pixels. Used when `reformat_mode=to_box` AND `preset=Width/Height`; ignored when a named preset is selected.
    target_heightINT10801–16384Target output height in pixels. Used when `reformat_mode=to_box` AND `preset=Width/Height`; ignored when a named preset is selected.
    resize_typeCOMBOfitHow input maps into the target box. Used when `reformat_mode=to_box`. width/height = scale uniformly to match that edge. fit = scale to fit inside the box (letterbox; black where the box exceeds the scaled image). fill = scale to cover the box (crops the overflow). distort = scale W and H independently to exactly match the box (changes aspect). none = no scale; place input centered in the box (crop if larger, pad if smaller). Cropped-away/padded regions are TRANSPARENT — RGB sources are promoted to RGBA with alpha=0 in the padded area so downstream compositing is clean.
    filterCOMBOcubicPixel filter for resampling. impulse = nearest-neighbor (mask passes, exact pixel preservation). linear = bilinear (cheap, smooth). cubic = bicubic (default; the safe Nuke-equivalent). Lanczos4 = sharpest; for high-quality stills / final delivery. area = best for downscaling — anti-aliased mean pooling, softer but artifact-free.
    output_dtypeCOMBOfp16Output tensor dtype. fp32 = ComfyUI default (4 bytes/sample). fp16 = half memory + half VRAM (2 bytes/sample). EXR-native precision; fits the [0,1] LDR + scene-linear range with headroom up to ~65504. Some downstream nodes assume fp32 — flip back to fp32 if you hit dtype errors.
    show_previewBOOLEANtrueShow a thumbnail of the first decoded frame on the node.

    Outputs (10)

    NameTypeDescription
    imageIMAGEDecoded frames as IMAGE (N×H×W×3 float in [0,1]). RGB only — alpha is split out to the `mask` socket.
    maskMASKAlpha channel as MASK (N×H×W float in [0,1]). MASK CONVENTION (stock ComfyUI): mask = 1 - alpha white (1.0) = 'area to inpaint' (source was transparent) black (0.0) = 'keep' (source was opaque) empty mask = all zeros (source has no alpha = fully visible) This is the SD-inpainting convention every stock ComfyUI mask-using node expects (LoadImage, MaskComposite, SetLatentNoiseMask, ImpactPack mask pipeline, etc.). Drop-in compatible with all of them. If you want NUKE-STYLE natural alpha (mask = alpha, where 1.0 = opaque), wire a MaskInvert node between this socket and your downstream consumer. Populated when the source carries alpha: * Image: EXR / PNG / TIFF with α channel * Video: ProRes 4444 / 4444 XQ, QuickTime RLE, FFV1 (yuva*) Otherwise emits the empty mask (zeros = fully opaque source).
    audioAUDIOAudio track from the container, or a silent stub if absent.
    resolved_pathSTRINGResolved on-disk container path.
    infoSTRINGHuman-readable summary: dimensions, codec, pixel format, fps, frame count.
    widthINTFrame width in pixels.
    heightINTFrame height in pixels.
    frame_rateFLOATContainer's native frame rate (read from the PyAV header).
    frame_countINTNumber of frames decoded into the IMAGE batch.
    videoVIDEO`VideoFromFile` referencing the source on disk. Wiring ONLY this (not `image`) skips the PyAV decode — peak RAM stays at file-handle level. ⚠️ Raw passthrough: source colorspace, source resolution, source codec. OCIO/Reformat apply to `image` only. Both sockets can be wired in the same graph for parallel branches.