The morning after costs more than the GPU hours. You queue 40 takes of a shot overnight, and current models being what they are, a handful survive. Finding that handful means scrubbing every clip by hand: the black renders, the frozen frames, the take where a hand grows a sixth finger at the exact moment it grips the cup. An hour of scrubbing to find 6 minutes of footage.
That hour is now a command. dailies is an open-source tool we built and released this month (MIT, Python, zero dependencies beyond ffmpeg) that reviews a batch the way a strict assistant editor would: it kills the dead takes, with timestamped reasons, and ranks the survivors so you watch the best candidates first. Before any setup, watch it work.
Watch it work first
4 takes from MiniMax Hailuo 2.3, a current budget model. The prompt is built from things every video model still gets wrong: finger work (cracking an egg one-handed), object contact (a stirring spoon), and a chalkboard sign that must read exactly “HAPPY HOUR 5-7PM”. Play any take and watch the sign:
The exact prompt
A close-up of a bartender's hands cracking an egg one-handed over a cocktail shaker while the other hand stirs with a spoon, then both hands lift a chalkboard sign with the handwritten text "HAPPY HOUR 5-7PM", busy bar background, one continuous shot
One command kills all 4, with the evidence:
$ dailies review ./bar --vlm https://api.gmi-serving.com/v1 \
--vlm-model google/gemini-3.5-flash-lite \
--rubric bar-rubric.json --samples 3
reviewed 4 takes, killed 4
kill #1 take-001.mp4
text.legibility severity 4 at 2.333s: The sign reads
'HAPPY HOUR 5-5PM' instead of 'HAPPY HOUR 5-7PM'
kill #3 take-003.mp4
artifact.morphing severity 4 at 3.875s: The object being held
instantly morphs from a white egg into a black chalkboard sign
No take got the sign right: 2 read “7-5PM”, 2 “5-5PM”. With a price file the batch’s report opens with its own autopsy, verbatim: $0.22: no usable takes. Tonight’s fix is the prompt or the model, not another 40 seeds, and it cost 22 cents to learn that instead of an hour.
Second batch, Seedance 2 on an easy prompt. 3 takes survive and rank; the judge kills the fourth for a milk pitcher “popping into frame”, but 2 of its 3 samples said yes and one said no, so the sidecar records confidence 0.67. Step through take-001’s opening frames yourself:
The exact prompt
Close-up: a barista pours latte art into a white ceramic cup, steam rising, warm morning window light, shallow depth of field, slow push-in, one continuous shot, no cuts
We disagreed with that kill: the pitcher enters edge-first, the way a held object would. So we overruled it, recorded verdicts on all 14 takes in this page’s batches with dailies gold add, and ran dailies fit. It learned our taste from 14 rulings:
$ dailies fit ./takes
fitted 4 rule weights on 14 takes, training accuracy 93%
text.legibility +6.492
adherence.prompt +5.739
artifact.morphing +2.949
continuity.objects -0.969
Read the last line. The only continuity.objects kill in our labels was the pitcher call we overruled, so the tool pushed that rule’s weight negative: it now knows those flags do not predict our kills. Garbled signs and ignored direction, the defects we actually kill for, float to the top. That is the whole tool in 3 screens: evidence, confidence, and a judge that learns whose taste is in charge. The rest of this page is how to run it yourself.
pip install video-dailies, thendailies review ./takes. Dead takes get killed with reasons; survivors get ranked per shot.dailies reportwrites one static HTML page: hover to scrub, defects on a timeline, cost per usable take in the header.- The vision judge answers yes/no evidence questions per rule;
--samples 3asks 3 times, and a defect the samples disagree on cannot kill. dailies gold addrecords your verdicts;calibrateandfitturn them into your own kill threshold and ranking.dailies watchreviews takes as your queue produces them, so the report exists before you wake up.
Before you start
You need 3 things, and the fourth step has one optional extra:
- Python 3.9 or newer. Check with
python3 --version. - ffmpeg. Check with
ffmpeg -version. If that fails:brew install ffmpegon a Mac,sudo apt install ffmpegon Linux. - A folder of generated clips. Any mp4, mov, webm, mkv, or avi files. A ComfyUI output folder works as is.
- Optional, for step 4: access to a vision model, either an API key or a local GPU.
2 words from film sets, because the tool borrows them. A take is one generated clip, one attempt. A shot is the thing you are attempting, so 40 takes of shot 7 are 40 tries at the same moment of your film. On a film set, “dailies” is the ritual of reviewing yesterday’s footage. Same job here.
One promise before the commands: dailies does triage, not judgment. Vision models are still mediocre at fine-grained artifact detection (Artifact-Bench, May 2026), so the tool never claims a take is good. It claims that 34 of your 40 takes are definitely dead, shows you why with timestamps, and hands you the surviving 6 in ranked order. Taste stays your job; dailies takes the scrubbing.
Step 1: install it
pip install video-dailies
Check it worked:
$ dailies --version
0.2.0
Step 2: review a batch
The demo batch is the bar shot from the top of the page: 4 takes of 6 seconds, one prompt, no editing, exactly the kind of footage an overnight batch produces. Point review at the folder, with no vision model configured yet. This is the output of the run on exactly those 4 files:
$ dailies review ./takes
reviewed 4 takes, killed 0
review #1 take-001.mp4
review #2 take-004.mp4
review #3 take-003.mp4
review #4 take-002.mp4
Each line is one take. review means the take survived every check, and #1 is its rank within the shot: fewest defects, least flicker, smoothest motion first. When a take dies, the line says kill and ends with the evidence, like black for 4.8s of 5.0s or frozen for 3.1s of 5.0s; the other mechanical kill triggers are decode errors and a scene cut in the middle of what should be one continuous shot (that cut means the model glitched, not the editor). Folder names become shot names automatically.
2 of the mechanical numbers are worth knowing by name. Flicker measures luminance instability, with genuine motion masked out so intended action does not count against a take. Motion smoothness drops every other frame, has ffmpeg re-invent the missing ones from their neighbors, and compares the guess to what the model actually generated: smooth plausible motion reconstructs well, jerky or rubber-band motion does not. Take-001 here scores 0.980 of 1. Both feed the ranking; neither kills on its own.
But look at the output again: zero kills, and now play any take and watch the chalkboard. Every take in this batch has something wrong with it. The mechanical stage catches broken files, not broken content, and that is the limit of stage 1. Hold that thought; the vision pass in the last step settles it.
Where did the results go? Next to each clip there is now a small JSON file, called a sidecar, named take-031.mp4.take.json. It holds everything the review found:
{
"take_id": "sha256:9f2c81...",
"shot": "shot-07",
"review": {
"mechanical": {
"freeze": [{"start": 1.9, "end": 5.0}],
"kill_reasons": ["frozen for 3.1s of 5.0s"]
},
"verdict": "kill",
"rank_in_shot": 13
}
}
The take_id is a hash of the clip’s content, which makes re-running free: run dailies review again and already-reviewed takes are skipped, only new clips pay. The format is a published spec, so other tools can read and extend the same file.
Step 3: read the report
The terminal output is for skimming. The reviewing happens on one HTML page:
dailies report ./takes -o report.html
Open report.html in a browser, straight from disk, no server needed. You should see your takes grouped by shot, survivors first. Hover any clip and it scrubs; click and it plays. Under each take a small timeline marks where the problems are: gray spans for black video, blue for frozen, ticks for stray cuts. Under each killed take, “why killed” expands the reasons. Morning routine: open the page, watch the top 1 or 2 survivors per shot, done.
Each batch gets its own report. Here is the bar batch’s, unedited; once the vision pass has run, its defects appear as dots on the timelines, color-coded by family: red for anatomy, orange for physics, purple for morphing, yellow for text, teal for background, magenta for adherence. And once you pass a price file (2 sections down), the header states the number that actually matters: the bar batch’s 5-word autopsy from the top of the page lives there.
Step 4: leave a watcher running overnight
So far you reviewed a finished batch. The watcher reviews takes while the batch is still rendering. Start your generation queue as usual, and in a second terminal:
$ dailies watch ~/ComfyUI/output --report report.html
watching ~/ComfyUI/output every 5s; ctrl-c to stop
23:41:09 review shot-07/take-031.mp4
23:44:52 kill shot-07/take-032.mp4 black for 4.8s of 5.0s
Every time a clip lands, the watcher waits for the encoder to finish writing it, reviews it, and rebuilds the report. Go to bed. The report is current when you wake up. 2 details worth knowing: restarting the watcher never re-reviews old takes, and --json switches the output to one JSON line per take, for piping into your own scripts.
The vision judge
Everything so far ran on your CPU and caught the cheap deaths. Some failures need eyes: a hand with 6 fingers, a held object drifting off the grip, a camera direction the model ignored. For those, dailies sends frames from each surviving take to a vision model. It works with any vision model behind an OpenAI-compatible API, which in practice means 2 options.
Hosted, the fastest to try. Put your provider’s key in the environment and point --vlm at their endpoint with their model id:
export DAILIES_VLM_KEY=sk-...
dailies review ./takes --vlm https://your-provider.example/v1 --vlm-model qwen3-vl
Local, free after the download, if you have the GPU: serve Qwen3-VL with vLLM or llama.cpp, then use --vlm http://localhost:8000/v1. Either way the same flags work on watch, so the overnight run gets the vision pass too. Frames are chosen for the model, not left to chance: the mechanical stage picks the moments of sudden frame-to-frame change, where defects cluster, and adds a sparse uniform strip so no calm stretch of the clip goes unseen.
Here is the part that changed most in 0.2, and the reason to trust the verdicts more: the judge never picks a score. Vision models rank things well and pick numbers on a scale badly, so each rule is a set of yes/no evidence questions, and the severity of a yes is written in the rubric, not chosen by the model. The model only says yes or no, and points at the frame. The built-in rules cover hands, faces, limbs, object morphing, object continuity, contact physics, motion physics, background stability, text legibility, and prompt adherence; the full checklist is below. A custom rule is a few lines. Save this as film.json:
{"rules": {
"brand.no_text": {
"questions": [
{"ask": "Is any legible text or watermark visible in any frame?",
"severity": 4}
],
"fail_at": 4
},
"continuity.wardrobe": {
"questions": [
{"ask": "The character wears a red coat. Does the coat change color or vanish between frames?",
"severity": 4}
]
}
}}
Run with --rubric film.json. A rule with fail_at kills any take whose yes reaches that severity; a rule without one never kills, it only pushes a take down the ranking. Wardrobe continuity, a prop that must survive the scene, a client’s no-watermark rule: each is a question in this file, no code.
One more flag before the demo. --samples 3 asks the judge every question 3 times. When the 3 answers agree, the defect carries confidence 1.0. When they split 2 to 1, it carries 0.67, and here is the rule that protects your footage: a defect the samples disagree on cannot kill a take. It stays a finding, and the take stays in your review pile instead of the trash.
This is the run that produced the kill lines at the top of the page: Gemini 3.5 Flash Lite behind an OpenAI-compatible endpoint, this rubric (the default rules, with the sign’s required text written into the text.legibility question), --samples 3. The bar batch’s 4 mechanically clean takes all die. Beyond the sign misreads, the judge flagged “an entire chalkboard materializes from thin air in front of the bartender’s hand” and a sign whose text changes mid-shot from “HAPPY HOUR 7-5 PM” to just “7-5 PM”. Each defect lands in the sidecar with its rule, timestamp, severity, confidence, and note, and each shows up as a color-coded dot in the batch’s report. This is the yield problem in miniature: a mechanically clean batch, 4 takes deep, zero usable.
The same run on the latte batch is where confidence earns its keep. The kill line, verbatim:
kill #4 take-001.mp4
continuity.objects severity 4 at 0.333s: A milk pitcher instantly
pops into frame from the right edge between 0.0s and 0.333s
without entering naturally.
The sidecar records confidence 0.67 on that defect: 2 samples out of 3 said yes, and the rule landed in the batch’s uncertain list. The judge made a close call, told you it was close, and killed anyway because 0.67 is exactly the agreement floor. What do you do with a judge whose close calls you disagree with? You outvote it. That is the next section, and the top of this batch’s report is where your taste takes over either way.
Teach it your verdicts
Every morning you make keep/kill calls anyway. gold add records them, one command per ruling, into the same sidecars:
dailies gold add shot-01 --label pass # the pitcher enters naturally; overruled
dailies gold add shot-02 --label kill # no sign, no take
We labeled all 14 takes in this tutorial’s batches: 6 passes, 8 kills, including a pass on the latte take the judge killed. Those labels feed 2 commands. The first sets your kill threshold with an honesty guarantee, and on a gold set this small it refuses, verbatim:
$ dailies calibrate ./takes
not enough gold-pass takes: 6 labeled, 19 needed for alpha=0.05
wrote dailies-calibration.json (threshold disabled until recalibrated)
That refusal is the feature. calibrate uses a statistical method (conformal calibration) that can promise “at most 5% of automatic kills are wrong on footage like yours”, but only once you have labeled 19 passing takes, roughly one morning’s worth of rulings. Until then it declines to guess. No other triage tool we know of can print that promise at all; this one also refuses to print it before it is earned.
The second command works with any number of labels, and you already saw its output at the top of the page: fit learns which rules actually predict your kills, at 93% training accuracy on 14 rulings, and pushed continuity.objects negative because its only kill was the pitcher call we overruled. Run future reviews with --calibration dailies-calibration.json and the ranking is yours, not the default’s.
What a take costs
The judge reads its provider’s token counts into every sidecar. Give it your rates and the sidecars turn into receipts. prices.json for this tutorial is 4 lines, Gemini 3.5 Flash Lite’s list rates:
{"models": {
"google/gemini-3.5-flash-lite": {"input": 0.10, "output": 0.40}
}}
Run with --prices prices.json and every sidecar gains a cost block, and every report header ends in the number worth optimizing. Judging all 14 takes in this tutorial, 10 rules, 3 samples each, cost $0.69 total: $0.086 per usable take. The bar batch’s header reads $0.22: no usable takes, which is what a doomed prompt looks like as a line item. Add a "clip" price (your provider’s per-generation rate, or a local $/GPU-hour guess) and generation cost folds into the same number. You cannot lower a number you never see; now you see it every morning.
The rest of the morning kit
3 more commands, each one line, each running on the sidecars you already have. brief answers “why does this shot keep dying” without opening a single clip:
$ dailies brief ./takes
14 takes across 5 shots: 6 killed, yield 57%
shot-02: 4 takes, 4 killed, yield 0%
kills (rule): text.legibility 4, artifact.morphing 3, continuity.objects 3
text.legibility: 4 defects on 4 takes, mean severity 4.0, mean confidence 1.00
e.g. take-001.mp4 at 2.333s: The sign reads 'HAPPY HOUR 5-5PM'
instead of 'HAPPY HOUR 5-7PM'.
4 for 4 on the sign, at full confidence: tonight’s fix is the prompt or the model, not another 40 seeds. assemble joins the best surviving take per shot into one watchable file, with a CSV mapping every cut back to its source take:
$ dailies assemble ./takes -o cut.mp4
cut 4 takes from 4 shots into cut.mp4 (21.7s, 360x640 @ 24.0 fps)
wrote cut.csv
And verdict is the one-take, one-answer form for your own scripts: it prints one JSON object and answers in the exit code, 0 for keep or review, 3 for kill. review --ndjson streams the same shape one line per take. Anything that can read a JSON line and an exit code, a shell script, a CI job, an agent, can now sit on top of dailies; the shapes are published, run dailies schema take to see them.
Closing the loop overnight
Everything above triages what the night produced. Version 0.2 can also act on it: watch --regen resubmits a killed take with a fresh seed through a driver you point it at, caps the spend, stops when a shot has enough passing takes or proves futile (the same rule killing 3 takes on 3 different seeds is the prompt’s fault, not the seed’s), and records the whole retry chain in the sidecars. It refuses to run unattended until you have a healthy judge on record, and a slice of its automatic passes always lands back in your morning pile for audit, so a judge fooling itself gets caught by you. It needs a driver executable for your generation backend, a short script whose contract is documented; start with --dry-run, which prints what it would resubmit and spends nothing. A worked overnight-loop example deserves its own tutorial, and it will get one.
What the funnel checks for
The full checklist, with what each check found in this tutorial’s own batches. Stage 1 is always on; stage 2’s rules are the built-in rubric, which you can extend or replace.
| Check | Catches | From our batches |
|---|---|---|
| decode / black / freeze | Corrupt files, black renders, stuck frames. The cheap deaths; kill on sight. | Not triggered: hosted models rarely produce them. The kill line reads like frozen for 3.1s of 5.0s. |
| flicker, motion, cuts | Luminance instability with genuine motion masked out, jerky motion via reconstruction error, a cut inside a single generated shot. | Motion smoothness ranked the latte survivors, 0.989 best. |
| anatomy.hands / faces / limbs | Finger counts, joints, grips; warped faces; extra or missing limbs. | Not triggered at kill confidence this round; the crowd faces held up. |
| artifact.morphing | Objects that morph, duplicate, or teleport. | “An entire chalkboard materializes from thin air in front of the bartender’s hand” (bar, 2.875s); “The beer mug’s handle morphs and duplicates as it is placed on the bar” (beer slide, 4.0s) |
| physics.contact / physics.motion | Held objects that slip, clip, or float; movement that ignores gravity; things that change size without cause. | Not triggered at kill confidence this round. |
| continuity.objects | Props that vanish or appear without leaving the frame. | “A small chalkboard sign suddenly pops up from the bottom of the frame without entering naturally” (bar, 2.333s); the milk pitcher call we overruled (latte, 0.333s, confidence 0.67) |
| environment.stability | Backgrounds that melt or rearrange. | Not triggered this round; the crowd banner’s garble filed under text instead. |
| text.legibility | Garbled or unstable text anywhere in frame. | “HAPPY HOUR 5-5PM instead of 5-7PM” (bar, 2.333s); “The banner text in the background is garbled/AI-gibberish (‘RRAHARGAKIBIDS’)” (crowd, 0.0s) |
| adherence.prompt | Direction the model ignored. Needs the prompt in the sidecar. | “Bartender holds the mug and hands it forward instead of sliding it down the bar” (beer slide, 0.0s, confidence 1.0) |
The other quotes come from 3 more small batches aimed at the categories the bar shot could not reach. The crowd batch (a wedding dance floor) stressed faces, motion, and backgrounds: the faces held up, and the garbled banner was flagged at full confidence but at severity 3, below the kill line, so the take sits in the review pile where a background defect belongs. The beer-slide batch stressed liquid and object handoff: one take killed for the morphing mug handle, and the other caught by the adherence rule at confidence 1.0 for handing the mug over instead of sliding it, a miss no pixel-level check would ever see. The juggler batch (3 oranges, one leg) is the honest failure: an earlier judging run caught one take’s juggler abruptly transforming mid-shot, and this run’s judge let both takes through clean. Vision judges have blind spots, and they do not announce them. That is precisely what the gold labels are for: our kill ruling on that take is on record, and dailies judge-check re-judges the labeled set on demand and reports how much agreement any new judge, prompt, or model swap just cost you. You do not get to pick which way a take fails, and you also do not get to force one: that is why the checks run as a battery, and why a clean pass is information too. Reports: crowd, juggler, beer slide.
The exact prompts
Crowd: A crowded dance floor at a wedding reception, many guests dancing energetically, faces clearly visible in the foreground, a couple spins past the camera, fast motion, string lights and a banner in the background, one continuous shot Juggler: A street performer juggles three oranges while balancing on one leg on a plaza, audience faces watching close in the foreground, fast hand motion, one continuous shot Beer slide: A bartender slides a full beer mug down a wooden bar counter to a customer who catches it, foam sloshing over the rim, coasters and peanut bowls on the counter, one continuous shot
When something looks wrong
- “error: ffmpeg not found on PATH”: install ffmpeg (see Before you start) and open a new terminal.
- “no clips found”: check the path, and check the extensions; dailies looks for mp4, mov, webm, mkv, and avi.
- “VLM endpoint unreachable”: the URL should end in
/v1, and hosted providers needDAILIES_VLM_KEYset in the same shell. - A take you fixed still shows the old verdict: reviews are cached by content hash;
--forcere-reviews everything. - “not enough gold-pass takes” from
calibrate: working as designed. Label more mornings withgold add; the guarantee unlocks at 19 passing takes. watch --regenrefuses to start: it wants a healthy judge on record first. Rundailies judge-checkon your labeled takes, or use--dry-runwhile you build trust.
Where this is going
Triage was half of the loop, and 0.2 closes most of the other half: verdicts you can calibrate, receipts on every take, and a regen loop that acts on kills overnight. What remains is memory at the source: which seed, which model, which LoRA stack produced the take you kept. The take.json spec reserves a recipe block for exactly that, and a ComfyUI driver plus in-graph recipe capture are next, so a kept take can be reproduced next month on demand.
The code is on GitHub under MIT. Issues and rubric contributions are welcome, especially the rules you wrote for your own production.