ANDRO Video QC
Before a generated clip is accepted, someone has to actually check it
- images
- report
- json
- sheet
- pass
Everything else in the comfyui-vae-float32 pack asks what did our decode do. ANDRO Video QC is the odd one out and the most generally useful of the ten: it asks what did the file someone sent us actually contain - the ingest check a pipeline runs before a plate is accepted. Point it at a clip out of Runway, Seedance or Kling and it reports effective bit depth and which quantisation grid the values sit on, the share outside [0,1] and any NaN, black frames, flashes, flicker, duplicate and held frames, resolution / frame count / fps against what was ordered - and, with source_path set, codec, pixel format and the container's colour tags via ffprobe.
Because half of what it flags is a judgement call, it doesn't just hand you a verdict. It gives you a contact sheet of the flagged frames, each labelled with its number and reason, plus one of PASS / WARN / FAIL and a pass:BOOLEAN you can gate a downstream branch on without reading text.
What it catches that you'd otherwise miss
Two findings from the real clips it was built against, both 24 fps h264, are worth internalising:
WARN: untagged colour (space, transfer, primaries): readers will assume BT.601 / unknown transfer
WARN: 81 held frame(s) = 22.4% of the clip
Untagged colour is the normal state of AI-generated video, and it's the most expensive item on the list - the grade starts from the wrong primaries and nothing errors. And that period-4 held pattern means roughly one frame in four barely moves, so a 24 fps file is carrying about 6 fps of motion: it conforms, it plays, and it judders on the first pan. Both are invisible to a spot-check and obvious once measured.
Inputs that matter
images- the clip, straight off the loader. Read-only; wire the source on past this node.source_path- the file the batch came from. Only with it can ffprobe report what the container claims, which is the half of ingest QC no pixel statistic reaches. Leave empty and everything else still runs.expected_fps/expected_frames/expected_width/expected_height- what the shot list says; 0 means don't check. A generator that returns 96 frames for a 4 s 24 fps order has silently changed the edit.held_ratio(0.2),flash_jump(0.25),black_level(0.03) - the sensitivity dials. Held is deliberately relative: a frame counts as held when it moves less than a fifth as much as the clip's own median - an absolute threshold would call a locked-off shot a stall and a whip pan clean.write_json- also write the report to disk as JSON so batches of clips leave a diffable trail.
Outputs: report (Markdown, readable on canvas and pasteable into a delivery note), json (same data, complete frame lists), sheet (the contact sheet), and pass (true only on PASS - WARN and FAIL are both false, so you can branch on it without parsing text).
One caveat the node prints for itself: a yuv420p file reaches an IMAGE batch through a YUV→RGB matrix, which lands 8-bit values on a much finer grid - both test clips measured ~50–60k levels per channel. When the container states a depth, the container wins; the level count is describing the decode path.
Install
ANDRO Video QC ships in the comfyui-vae-float32 pack. ComfyUI-Manager → search comfyui-vae-float32 → install → restart, or:
cd ComfyUI/custom_nodes
git clone https://github.com/AndreiOrehov/comfyui-vae-float32
Restart, find it under ANDRO. Requires ffprobe on your PATH for the container half. If you generate clips for fun and never deliver them to anyone, most of this is overkill - but if you've ever accepted 96 frames of a 4-second order, or graded footage that turned out to be untagged colour, you're the target audience. It's the one node in this VAE-obsessed pack you'll reach for on clips that never touched a VAE at all.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | The clip to check, straight off the loader. Read only - nothing comes back out, so wire the source on to the graph directly. | |
| source_pathopt | STRING | The video file the batch was loaded from. With it, ffprobe adds what the CONTAINER claims - codec, pix_fmt, fps, and the colour tags - which is the half of ingest QC no pixel statistic can reach. Leave empty and everything else still runs. | |
| expected_fpsopt | FLOAT | 0.0000–240 | What the shot list says. 0 = do not check. Only checkable from ffprobe: an IMAGE batch has no timebase. |
| expected_framesopt | INT | 00–1000000 | Expected frame count. 0 = do not check. A generator that returns 96 frames for a 4 s 24 fps order has silently changed the edit. |
| expected_widthopt | INT | 00–16384 | Expected width in pixels. 0 = do not check. |
| expected_heightopt | INT | 00–16384 | Expected height in pixels. 0 = do not check. |
| held_ratioopt | FLOAT | 0.200.01–1 | A frame counts as HELD when its mean absolute delta to the previous frame falls below this fraction of the clip's median delta. 0.2 = 'moved less than a fifth as much as this clip normally moves'. Relative on purpose - an absolute threshold would call a locked-off shot a stall and a whip pan clean. The check switches itself off when the median delta is under 1/255, because there is then no motion to be held against. |
| flash_jumpopt | FLOAT | 0.250.01–1 | Mean luma jump between neighbouring frames, in 0..1 units, that reads as a flash or a hard cut. Those frames are also excluded from the flicker figure, so one legitimate cut does not make an otherwise steady clip look unstable. |
| black_levelopt | FLOAT | 0.0300–1 | Mean luma below which a frame is called black. Always a FAIL: a black frame in a generated clip is a dropped frame, not a creative choice. |
| write_jsonopt | BOOLEAN | false | Also write the JSON report to disk, so a batch of clips leaves a trail that can be diffed later. |
| json_prefixopt | STRING | qc/report | Path under the ComfyUI output folder, without the extension. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| report | STRING | The verdict and one line per check, as Markdown - readable on the canvas and pasteable into a delivery note. |
| json | STRING | The same measurements as JSON, with every flagged-frame list complete rather than truncated. For logging a batch of clips. |
| sheet | IMAGE | Contact sheet of the flagged frames, each labelled with its number and why it was flagged - or eight evenly spaced frames when nothing was. |
| pass | BOOLEAN | True only on PASS. WARN and FAIL are both False, so this can gate a downstream branch without reading the text. |