@officexapp/vidfarm-devcli 0.21.32 → 0.21.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/editor-capabilities/SKILL.md +4 -3
- package/.agents/skills/vidfarm/SKILL.md +18 -3
- package/.agents/skills/vidfarm/recipes/bulk-scripting-with-a-regime.md +15 -0
- package/.agents/skills/vidfarm/recipes/cutout-graphics-for-explainers.md +46 -2
- package/.agents/skills/vidfarm/recipes/local-edit-render-approve.md +2 -1
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +13 -1
- package/.agents/skills/vidfarm/references/editor-workflows.md +7 -3
- package/.agents/skills/vidfarm/references/reviewing-renders.md +139 -0
- package/.agents/skills/vidfarm/regimes/README.md +2 -0
- package/.agents/skills/vidfarm/regimes/explainer.QA_REGIME.md +20 -1
- package/.agents/skills/vidfarm/regimes/product-demo.QA_REGIME.md +18 -0
- package/.agents/skills/vidfarm/regimes/short-form.QA_REGIME.md +31 -0
- package/.agents/skills/vidfarm/regimes/ugc-testimonial.QA_REGIME.md +7 -0
- package/SKILL.director.md +241 -10
- package/SKILL.md +2 -1
- package/dist/src/cli.js +28 -3
- package/dist/src/devcli/qa-check.js +68 -0
- package/dist/src/devcli/stills.js +65 -1
- package/package.json +1 -1
- package/public/assets/homepage-client-app.js +14 -14
|
@@ -98,6 +98,27 @@ Not an accessibility afterthought: captions are how the hook, the loop, and the
|
|
|
98
98
|
- **Cards are timed text over footage** — never a card UI, table, chip row, or frosted panel (`vidfarm qa` flags those as slop).
|
|
99
99
|
- **Max ~3 standalone cards per video:** one for the loop, one for the payoff, one for the bait.
|
|
100
100
|
|
|
101
|
+
#### Caption styling is MEASURED off the background, never hardcoded
|
|
102
|
+
|
|
103
|
+
A white rounded caption plate copied from another video onto a near-black stage is a bright slab the design never asked for — it dominates the frame and reads as a UI element pasted over the video. So measure what is actually behind the caption band, then pick one of three treatments:
|
|
104
|
+
|
|
105
|
+
| Background behind the caption band | Treatment |
|
|
106
|
+
|---|---|
|
|
107
|
+
| Dark and calm (luma < ~70, variation < ~42) | **Light type, NO plate** — the plate is dead weight |
|
|
108
|
+
| Bright and calm (luma > ~160, variation < ~42) | **Dark type, NO plate** |
|
|
109
|
+
| Busy / mid-tone / moving colour | **Plate** — nothing else stays readable |
|
|
110
|
+
|
|
111
|
+
- **The active-word highlight colour follows the treatment.** A deep red active word is fine on a white plate and unreadable against near-black; on a dark treatment it has to become a bright accent. Pick `--color` / `--active-color` / `--background-style` on `vidfarm captions generate` by measuring, not by taste.
|
|
112
|
+
- **ONE treatment per video, not per caption.** Styling that flips every few seconds reads as a bug, not as intelligence. Re-measure per scene only if the footage genuinely changes character.
|
|
113
|
+
- **Measure the COMPOSITED value, not the source file.** When the background is a moving image, walk the caption band's rect back through the background's own transform (Ken Burns scale/pan) into the source image, crop, then apply the composition's own darkening stack — gradient overlay, vignette, layer opacity — so the number is what the viewer sees. Sampling the raw source is the mistake: it reported a mid-grey for a stage that renders near-black.
|
|
114
|
+
- **Verify it for free in a rendered frame.** On the reference video the model predicted `luma 11, variation 5` and the rendered file measured `luma 16, variation 7` — same bucket, and it correctly shipped plate-free. Grab a frame where no caption is showing and measure the band there.
|
|
115
|
+
|
|
116
|
+
#### On-screen text and captions must not say the same thing at once
|
|
117
|
+
|
|
118
|
+
Animated text and diagrams on the page competing with voiceover captions is a real, nameable defect: the two are fighting for the same visual space and the same attention, as if they aren't aware of each other. **Display text carries the argument; captions carry only the parts of the narration the screen does NOT show.** When both render the same words, the viewer reads one line twice in two sizes while hearing it once, and the frame feels like two videos overlaid.
|
|
119
|
+
|
|
120
|
+
The mechanical fix: compare each caption phrase against the words on screen in that scene and **drop the caption when overlap is ≥60% of its content words** (words longer than 2 chars, case- and punctuation-normalised). On the reference video that suppressed **10 of 27 tiles**, and the hook and end card came out **entirely caption-free** — that is correct, not a bug, and worth saying out loud because an author who doesn't expect it will think captions broke. Second-order benefit: the surviving captions inherit wider time windows (the narrowest tile went from **0.41s to 0.71s**), so it buys readability as well as calm.
|
|
121
|
+
|
|
101
122
|
## Part II — the rules
|
|
102
123
|
|
|
103
124
|
### Rule 1 — every video is standalone. There is no part two.
|
|
@@ -147,9 +168,19 @@ Verbatim captions in the font regime and the safe zone · frame 0 works as hook
|
|
|
147
168
|
|
|
148
169
|
**Production**
|
|
149
170
|
- [ ] Verbatim captions, weight 700–900, safe zone, one line at a time
|
|
171
|
+
- [ ] Caption colour / active colour / plate were chosen by measuring the composited background behind the band, not copied from another video
|
|
172
|
+
- [ ] One caption treatment for the whole video, and the active-word colour is readable on it
|
|
173
|
+
- [ ] No caption repeats the words already on screen in that scene (≥60% overlap → drop the caption)
|
|
150
174
|
- [ ] ≤3 standalone cards, no slop furniture, `vidfarm qa` otherwise clean
|
|
151
175
|
- [ ] In a batch: this variant differs from its siblings by more than one noun
|
|
152
176
|
|
|
177
|
+
**Whole-video review** — on the render, not the plan; scenes built one at a time pass individually and drift as a sequence
|
|
178
|
+
- [ ] A contact sheet of ~12 stills was read as an image: consistent margins, one type scale, one accent colour, one illustration style
|
|
179
|
+
- [ ] Pacing is deliberate — not N identically-long beats, and no single hold that stalls it — and no join is jarring
|
|
180
|
+
- [ ] No dead band under top-anchored content; no frame rests empty >0.5s; the end card is settled ≥2s before the last frame
|
|
181
|
+
- [ ] Frames from two different scenes were compared (a frozen render passes duration, frame-count and audio-hash checks)
|
|
182
|
+
- [ ] Audio verified by measurement — ~12–15 dB speech-over-bed, peak <0 dBFS — not by "it sounds fine"
|
|
183
|
+
|
|
153
184
|
## Diagnosing a flop — read the charges, not the video
|
|
154
185
|
|
|
155
186
|
| What the numbers say | Weak charge | Fix |
|
|
@@ -80,3 +80,10 @@ If you're generating variants with different speakers/avatars, hold the honest-l
|
|
|
80
80
|
- [ ] Any paid/gifted/affiliate relationship is disclosed on screen and in the caption
|
|
81
81
|
- [ ] Frame 0 shows the speaker mid-sentence with a legible caption
|
|
82
82
|
- [ ] In a batch: this variant's *situation* differs from its siblings, not just its wording
|
|
83
|
+
|
|
84
|
+
**Whole-video review** — on the render, not the plan. The bar here is *un*-produced, so drift shows up as the opposite defect: one scene that suddenly looks designed breaks the whole illusion.
|
|
85
|
+
- [ ] A contact sheet of ~12 stills was read as an image — nothing in it looks more produced than the rest
|
|
86
|
+
- [ ] Caption treatment, framing and lighting are consistent end to end; no join is jarring
|
|
87
|
+
- [ ] No frame rests empty >0.5s, and the close is settled ≥2s before the last frame
|
|
88
|
+
- [ ] Frames from two different scenes were compared (a frozen render passes duration, frame-count and audio-hash checks)
|
|
89
|
+
- [ ] Audio verified by measurement — peak <0 dBFS, speech clearly above any bed — not by "it sounds fine"
|
package/SKILL.director.md
CHANGED
|
@@ -213,7 +213,7 @@ Present both harnesses to the director, recommend (A) unless they've asked for p
|
|
|
213
213
|
- **The ART must be CLOSED and SOLIDLY FILLED — this is the other half of surviving the key, and the #1 way stickers come back broken.** Ask an image model for "icons on a green plate" and it will happily draw **outline art**: a colored stroke with the shape's interior left as bare plate. It looks perfect on the sheet, and after the key each sticker is a **rim floating around a see-through hole** (an apple-shaped outline with nothing inside it). Same outcome from a *near-plate* fill (the keyer works on tolerance, not exact match), a translucent/glassy/glowing material, or a soft glow fading into the plate. **You cannot key those pixels back — it has to be in the prompt:** *"every object is a closed, solidly filled shape; outlines must enclose an opaque fill of a different color; no outline-only or hollow art; nothing on the art in the plate color or any near-shade of it; fully opaque, no translucency, glow or drop shadow."* `cutout --generate`, `sticker-pack --generate`, `handoff image` and the `create-overlay` primitive **append that clause for you** with the chosen plate hex — write it yourself only when you prompt a generator directly. After the key, both commands report per-item `hole_pct`/`hollow` (console `⚠ N% hollow`, `--json`, `stickers.json`) — a ring or picture frame reads the same way, so it **warns, never blocks**. Flagged and it shouldn't be? Re-generate with the fill clause; a *near*-plate fill can sometimes be rescued with a lower `--tolerance`; one stubborn item can be lifted with `vidfarm mask --crop …` (ONNX matting ignores fill color).
|
|
214
214
|
- **Transparent GIF is supported, for GIF-only surfaces.** `vidfarm sticker-pack … --output-format gif` (stills) and `vidfarm remove-greenscreen <video> --gif` (animated) emit transparent GIFs. GIF alpha is **1-bit**, so edges go hard — fine for chat/forum/Notion sticker surfaces, worse than PNG/WebP/WebM for compositing on a timeline. Prefer PNG/WebP/WebM unless the destination only eats GIF.
|
|
215
215
|
|
|
216
|
-
**Explainer house style — the defaults to build with unless told otherwise.** **White background / light mode** (plain white stage, no gradients, no dark mode, no photo backdrop), **kinetic word-by-word captions** in dark ink on the light stage (`vidfarm captions generate --style word-pop --color "#111111" --active-color "#7C3AED" --background-style plain` — skip outlines/shadows, they're only needed over busy footage), and **female TTS narration** (`vidfarm tts --voice coral` on OpenAI — `nova` for energy, `sage` for calm; `Kore`/`Leda` on Gemini; any ElevenLabs voice via `vidfarm voices`). **Keep it clean and simple** — one idea on screen at a time, two or three cutouts per beat, one accent color, one font, lots of white space; remove before you add. **Illustrations default to simplicity**: flat vector, simple shapes, minimal detail, 2–3 flat colors, no baked-in text — simple art keys cleanly, trims tight, and stays on-style across the whole cast. State the defaults once so the director can override any of them. Full detail: recipe `recipes/cutout-graphics-for-explainers.md` (“House style — the explainer defaults”).
|
|
216
|
+
**Explainer house style — the defaults to build with unless told otherwise.** **White background / light mode** (plain white stage, no gradients, no dark mode, no photo backdrop), **kinetic word-by-word captions** in dark ink on the light stage (`vidfarm captions generate --style word-pop --color "#111111" --active-color "#7C3AED" --background-style plain` — skip outlines/shadows, they're only needed over busy footage), and **female TTS narration** (`vidfarm tts --voice coral` on OpenAI — `nova` for energy, `sage` for calm; `Kore`/`Leda` on Gemini; any ElevenLabs voice via `vidfarm voices`). **Keep it clean and simple** — one idea on screen at a time, two or three cutouts per beat, one accent color, one font, lots of white space; remove before you add. **Illustrations default to simplicity**: flat vector, simple shapes, minimal detail, 2–3 flat colors, no baked-in text — simple art keys cleanly, trims tight, and stays on-style across the whole cast. State the defaults once so the director can override any of them. Full detail: recipe `recipes/cutout-graphics-for-explainers.md` (“House style — the explainer defaults”). **If the director takes the stage off white**, two things stop being optional: every sticker's **white die-cut rim** has to be stripped (on a dark stage it's a glaring halo and the most obvious bot-made artefact in the frame — recipe → “Stickers on a DARK or photographic stage”), and the caption hexes above stop applying — **caption colour, active-word colour and plate are chosen by measuring the composited background behind the caption band**, one treatment per video (`regimes/short-form.QA_REGIME.md` → “Caption styling is MEASURED off the background”). Related: **on-screen text and captions must not say the same thing at once** — display text carries the argument, captions carry only what the screen doesn't show.
|
|
217
217
|
|
|
218
218
|
**Landscape footage in a fullscreen vertical explainer — use the blurred plate, never bars.** When an explainer is built on **real filmed footage** and the source is 16:9 (or 4:3) on a 9:16 canvas, do not `contain` it (hard black letterbox bars read as an unfinished export) and do not blindly `cover` it (a wide shot loses its left and right thirds). Duplicate the clip: a full-canvas `cover` copy behind, heavily **gaussian-blurred and faded dark**, plus the sharp copy centered as a hero band — optionally zoomed ~1.3× — with its **top and bottom edges feathered** into the blur. Same clip, same timecode, so it reads as one continuous image with a shallow-depth-of-field plane, fullscreen edge to edge, nothing cropped, and clean dark space for the header and captions. Bake it once with ffmpeg into a single 1080×1920 file (free, local) and place it as one ordinary full-canvas layer — layer blur is not an editor property, so the pre-bake is the path that works in the editor, `serve`, and cloud render alike. Copy-paste ffmpeg + HTML recipes, tuning table, and the failure modes: `references/editor-workflows.md` (“The blurred plate — landscape footage, fullscreen, on a vertical canvas”).
|
|
219
219
|
|
|
@@ -251,6 +251,18 @@ The mechanism is deterministic, not luck: rendering is seek-safe, so frame 0 sho
|
|
|
251
251
|
|
|
252
252
|
Full mechanics and editor verbs: `references/editor-workflows.md` (“The opening frame is the post's thumbnail”); poster-state authoring craft: `hyperframes-creative/references/beat-direction.md`.
|
|
253
253
|
|
|
254
|
+
## Judge the WHOLE video, not the parts you built — and never by one frame
|
|
255
|
+
|
|
256
|
+
**Assume your own finished video has a defect you can't see.** That's the observed base rate, not modesty: across a 32-video batch, *every* first-pass video had a real defect that the agent who built it had already reported as "verified, looks good" — dead space under the content, a placeholder that reads as a failed render, contradictory numbers in one frame, a CTA still animating at the last frame.
|
|
257
|
+
|
|
258
|
+
**The cause is how agents build: part by part, each part correct in isolation.** Scene 3 gets authored while scene 3 is the whole world, so every scene passes on its own and the video fails *as a video* — type size jumps between beats, one scene breathes and the next is crammed, the accent colour drifts, a transition lands like a slap because nothing before it moved that fast, one asset is flat vector and the next is photographic. Nobody watches a scene; they watch the sequence. **So before you ship, look at the whole thing at once as a stranger would**, and ask: is it visually balanced (or top-anchored with a dead band below), is the spacing consistent scene to scene, is there ONE type scale / accent colour / illustration style, does the pacing have a deliberate rhythm instead of N identical beats, is anything jarring at the joins, does any frame make two elements compete for the eye, and — the summary question — **does it look like one person made it in one sitting?** When it doesn't, fix the *system* (define the type scale, the margin, the default beat, the palette, then apply it to every scene) rather than patching the one scene that stood out.
|
|
259
|
+
|
|
260
|
+
The mechanical form is a contact sheet, because one image read shows drift that twelve separate checks can't — and it's one command: **`vidfarm stills ./work --sheet`** renders the frames and tiles them into `stills/contact-sheet.png`. **Read the sheet as an image.**
|
|
261
|
+
|
|
262
|
+
**And never verify a video by a single frame.** Frame 0 is the thumbnail so you judge it alone — but a whole class of render bug (an overlay pass missing `-loop 1`, assets outside the composition root so the timeline never runs) freezes *every* frame while duration, frame count and audio hash all still pass, and frame 0 looks perfect. **Always compare two frames from different scenes; they must differ a lot.** Likewise, verify audio by measurement, never "it sounds good" — you can't hear it.
|
|
263
|
+
|
|
264
|
+
Full method — the holistic checklist, the six defects in observed frequency order, the "does it rest there" test, frozen-render verification, audio measurement, and how to brief a revision pass: **`references/reviewing-renders.md`**.
|
|
265
|
+
|
|
254
266
|
## Web AI chat vs. local devcli — know your surface's limits
|
|
255
267
|
|
|
256
268
|
**Desktop users: this is a skill, not an SDK or required runtime.** It gives an AI agent Vidfarm's operating knowledge; it does not proxy requests, add hidden capabilities, or require the agent to use a particular client. Every cloud workflow described here can be performed with ordinary HTTP against the Vidfarm REST API. For convenience and safer composition/file workflows, recommend the `vidfarm` command from `@officexapp/vidfarm-devcli`: it wraps the same REST routes, handles auth, uploads, polling, pagination, local files, and pull/edit/render loops. Use raw REST whenever the user prefers it, the CLI lacks a new route, or the work belongs in an existing service or automation stack.
|
|
@@ -269,13 +281,14 @@ You may be running as the **in-web AI chat** (the /editor copilot, the chat dock
|
|
|
269
281
|
|
|
270
282
|
- **Determine the surface before claiming capabilities.** The web chat has only its declared tools and REST routes. It cannot execute arbitrary JavaScript/Python, open a shell, create a local repository script, or use the user's filesystem. Never tell a web-chat user that you ran code or wrote a script unless a dedicated declared tool actually did so. A desktop coding agent has a real shell and filesystem and MAY write/run scripts, perform arbitrary local computations over paginated API results, create reports/CSVs/JSON, edit composition files, and orchestrate long devcli workflows within the user's authorization.
|
|
271
283
|
- **The web AI chat can do all three paintbrushes** — clip raws, author HTML/hyperframe motion, and generate AI media — and it drives edits directly on the live timeline. Keep small-to-medium jobs here: text/caption swaps, a scene or two replaced, single generations, captions, approve/schedule. Just do them.
|
|
272
|
-
- **No HTML slop — a video is not a web page.** Compositions are authored in HTML, so the #1 tell of an AI-edited video is landing-page furniture: gradient CTA "buttons" ("Sign Up for a Free Trial →"), rows of benefit chips/badges ("✓ No Credit Card Needed"), frosted/bordered cards holding a gradient headline + a URL, feature grids, bullet lists, web-default type (Inter/Roboto/Arial at weight 400-600). None of that exists in a real TikTok/Reel — **nothing in a video is clickable**. If you are typing `btn` / `badge` / `chip` / `card` / `rounded-full` / `shadow-lg` / `backdrop-blur` / `bg-gradient-to-r
|
|
284
|
+
- **No HTML slop — a video is not a web page.** Compositions are authored in HTML, so the #1 tell of an AI-edited video is landing-page furniture: gradient CTA "buttons" ("Sign Up for a Free Trial →"), rows of benefit chips/badges ("✓ No Credit Card Needed"), frosted/bordered cards holding a gradient headline + a URL, feature grids, bullet lists, web-default type (Inter/Roboto/Arial at weight 400-600). None of that exists in a real TikTok/Reel — **nothing in a video is clickable**. **The test is the native-editor test: could you have made this element with the tools inside TikTok's own editor?** That toolset is font / color / stroke / shadow / tight text box / alignment / rotation / animation presets, plus stickers, emoji, drawn marks and clips — it has no padded capsule, no border, no gradient fill, no blur panel, no card. If you reached past it, cut it. That includes **a single lonely pill around a stat or label** — `( 10 hrs / week )`, `( STEP 2 )`, `( EP.01 )`: being alone doesn't make a badge native, and the only legitimate capsule in a video is the active-word `spotlight`/`karaoke` highlight, which moves with the spoken word. Emphasize a stat the way the editor would instead: bigger, heavier, ALL-CAPS, an accent color, a hand-drawn circle, or its own beat. If you are typing `btn` / `badge` / `chip` / `card` / `rounded-full` / `shadow-lg` / `backdrop-blur` / `bg-gradient-to-r` — or a `border-radius` over ~8px on anything filled that holds words — stop and rewrite it as timed text on the footage. Arrows, scribble/underline marks, italics, ALL-CAPS, single-word color pops, emoji, transparent cut-out stickers, and mock social UI (iMessage bubbles, comment cards) are all *fine* — they're native to the platform.
|
|
273
285
|
- **Every video gets the four charges — hook, loop, payoff, bait — and you write them BEFORE you touch the timeline.** This is the largest quality delta in the product and it costs nothing: most agent-made videos fail on structure, not polish, because the timeline is the fun part so it gets built first and the words get retrofitted. Invert it: (1) write the **hook line as text** — a complete clause, subject + verb, no jargon, naming a **situation** ("I've quit six businesses"), never a label ("anonymity") — and put it on screen at `start:0`; (2) name the **curiosity loop** and the timestamp it closes at, *inside this video* (if you can't state the timestamp, there is no loop, and the withheld answer must be one the viewer **can't guess**); (3) name the **payoff** — shown, not summarized, landing before the final beat; (4) write the **bait** — one ask in the final beat and in the post caption. Then build. Chunk 1 is read before any audio (muted autoplay is the default), so the text hook does more work than the spoken one. Full harness — the three gates, banned openers, loop mechanics, compliance, and diagnosis-by-charge — in `references/hooks-and-virality.md`; the checkable form is `vidfarm regime show hooks`.
|
|
274
286
|
- **The first frame IS the thumbnail — compose it on purpose.** Frame 0 is a single frame of ~30 in the first second, but it's the poster every feed, share link, and paused player freezes on, so **more people see that one frame than watch the video**. It must never be black, empty, mid-fade, or caught mid-animation: put a real visual at `start:0`, have the hook words already on screen at t=0, and never hang a `fade-black`/`fade-white`/`flash` *entrance* on the **first** clip (junction transitions between later clips are fine — this rule is only about the opening). Verify it, don't assume: devcli `vidfarm stills ./work --at 0` renders that exact frame, and `vidfarm qa` flags a blank or fading open.
|
|
275
287
|
- **Ask early: one-time video, or bulk?** "Make me a video about X" and "I need to post daily / give me 20 hook variants" are different jobs, and directors often don't know the second one has a name. Ask once, up front: *"One video, or should we set this up as a repeatable batch?"* Bulk = **scripting mode** (a pinned base fork + a loop that varies ONE thing per variant; a public-raws shelf is the cheapest source of the N), and every batch gets a **`QA_REGIME.md`** — because a loop of fifty videos has no human looking at every frame, and the regime is what replaces those eyes. Don't silently ship a one-off when they asked for volume, or drag someone into a harness when they wanted one clip.
|
|
276
288
|
- **`QA_REGIME.md` is the director's own quality contract, and it's a first-class artifact.** `vidfarm qa`'s built-ins are universal (slop, fonts, the thumbnail frame); a regime is what makes *this* format good — audience, hook shape, banned vocabulary, pacing, compliance line. It lives next to the work, they own it, it stacks: `vidfarm regime init short-form --out ./work/QA_REGIME.md` (bundled bases: `short-form`, `hooks`, `ugc-testimonial`, `explainer`, `product-demo` — each a starting point to **edit**, never a house style), then `vidfarm qa ./work --regime hooks --regime ./brand/HOUSE.md`, and any user file anywhere is valid. Its `checks:` front matter is machine-settled; its `- [ ]` checklist comes back as **review items you answer honestly in your report** — never claim a video passed the half the CLI can't judge. When a batch teaches you something, **write it back into the regime**: that's the artifact that compounds. Details in `references/automation-and-local-dev.md` ("Scripting mode"), format in `regimes/README.md`.
|
|
289
|
+
- **A video is judged as a SEQUENCE, so review it as one.** Agents build scene by scene and each scene passes in isolation while the video drifts — inconsistent margins, three type sizes, an accent colour that wanders, beats that are all the same length, a jarring join. Tile a dozen stills into one contact sheet (`vidfarm stills ./work --sheet`) and read it as an image before you call anything done, fix drift by defining the system rather than patching the odd scene out, and remember that **your own confident "verified, looks good" is the single least reliable signal in this workflow** — it was wrong on every video of a 32-video batch. Method: `references/reviewing-renders.md`.
|
|
277
290
|
- **On devcli, QA every video you produce: `vidfarm qa ./work`.** Free, instant, local-only — it blocklists exactly the slop above plus first-frame/thumbnail and font-regime/safe-zone drift, and prints a concrete fix per finding. **Feedback, not a gate**: it exits 0 even on findings, never runs automatically, and is a blocklist (unusual/stylized compositions pass untouched), so there's no reason not to run it before every publish. `--json` for scripted batches, `--strict` only if you want a CI failure. **Web-chat copilot: this command does not exist for you** (devcli-only, no REST twin) — apply the standard by hand, and when handing a heavy job to a local coding agent, tell them to run `vidfarm qa`.
|
|
278
|
-
- **Every production adheres to the TikTok-native caption standard.** On-screen text lives inside the readable safe zone (**~8%–85%** of a 9:16 frame — never pinned to the top/bottom edges the phone UI clips), uses the composition's bold font regime (**Montserrat** default / TikTok Sans, weight **700–900**, ~36–64px on a 1080-wide frame), and uses **exactly one of four valid backgrounds**: outline/stroke (`background_style:"outline"`, the default), plain + shadow (`"plain"`), an active-word highlight pill (`set_captions` `spotlight`/`karaoke` — the only legitimate pill), or a tight-hugging solid band (`"highlight-solid"`, radius ≤8px, no border/shadow/gradient/blur). Decomposed forks often inherit the source's edge-pinned caption in an off-regime font — fix it, don't inherit it. Local devcli renders auto-normalize position + font family only (never the slop), so author it correctly. Full rules in `references/editor-workflows.md` ("Social-native visual standard" + "TikTok-native caption standard").
|
|
291
|
+
- **Every production adheres to the TikTok-native caption standard.** On-screen text lives inside the readable safe zone (**~8%–85%** of a 9:16 frame — never pinned to the top/bottom edges the phone UI clips), uses the composition's bold font regime (**Montserrat** default / TikTok Sans, weight **700–900**, ~36–64px on a 1080-wide frame), and uses **exactly one of four valid backgrounds**: outline/stroke (`background_style:"outline"`, the default), plain + shadow (`"plain"`), an active-word highlight pill (`set_captions` `spotlight`/`karaoke` — the only legitimate pill *in the whole frame*, static labels included), or a tight-hugging solid band (`"highlight-solid"`, radius ≤8px, no border/shadow/gradient/blur). Decomposed forks often inherit the source's edge-pinned caption in an off-regime font — fix it, don't inherit it. Local devcli renders auto-normalize position + font family only (never the slop), so author it correctly. Full rules in `references/editor-workflows.md` ("Social-native visual standard" + "TikTok-native caption standard").
|
|
279
292
|
- **Where the web chat struggles: complex, long, multi-step transformations.** A full multi-scene re-theme, an iterative render-critique-iterate loop, heavy scripted or batch work, or anything needing a real filesystem and many sequential tool calls will hit context limits, turn/timeout ceilings, and the web editor's constraints (CSS/declarative motion only — JS animation adapters are stripped on save). Don't grind a big transformation one layer at a time in a chat turn and stall.
|
|
280
293
|
- **Practical workaround — hand the heavy job to local devcli.** When a task is genuinely large or long-running, **proactively recommend the director run it locally with an AI coding agent** (Claude Code / OpenAI Codex / any capable agent): `vidfarm pull <forkId>` writes the composition + the `.harness/` grounding bundle to disk, the agent edits with the full devcli verb set and JS animation adapters, renders free with `vidfarm serve`, and `vidfarm publish` pushes it back. This is the **best-quality (B) harness's** natural home (adversarial grading with a coding agent). Frame it as "this is a big rebuild — you'll get a better, faster result running it locally with a coding agent; here's how," not as a dead end.
|
|
281
294
|
- **Offer a handoff, do not impersonate the desktop agent.** When web chat reaches that boundary, offer to save a Markdown handoff in My Files containing the objective, selected template/fork IDs, asset paths, grounding, constraints, completed work, and suggested devcli commands. Create it only after the user agrees. The desktop agent should read that document, pull the referenced fork, and then use its actual code/shell capabilities.
|
|
@@ -292,6 +305,7 @@ Read only the relevant reference file for the current task.
|
|
|
292
305
|
- Getting-started interviews, **consultations**, strategy docs, onboarding flow: `references/onboarding.md`
|
|
293
306
|
- Primitive routes such as TTS, STT, music, overlays, background removal, product placement: `references/primitives.md`
|
|
294
307
|
- **Hooks, retention, and why a video travels — the four charges: `references/hooks-and-virality.md`.** Read it before writing ANY hook, caption script, or re-theme, and before a hook-variant batch. It is the craft harness; the rest of the skill is mechanics.
|
|
308
|
+
- **Reviewing a finished render — the holistic pass, the six common defects, frozen-render and audio verification: `references/reviewing-renders.md`.** Read it before you report a video as done, and before you grade someone else's.
|
|
295
309
|
- Bulk / N-variant production: recipe `recipes/bulk-scripting-with-a-regime.md`; the `QA_REGIME.md` format and the bundled bases: `regimes/README.md` (+ the regime files beside it, readable as-is)
|
|
296
310
|
- Complete REST API map and raw-HTTP conventions: `references/rest-api.md`. Load it only when the user asks for REST, an endpoint/schema, direct HTTP integration, or exhaustive API coverage. For the entire specification, follow its domain links and load every listed reference; do not preload them into ordinary director conversations.
|
|
297
311
|
|
|
@@ -334,6 +348,7 @@ Choose the narrowest path that satisfies the request.
|
|
|
334
348
|
- Submission routes are generally not idempotent. Especially for renders and expensive primitives, check status before retrying.
|
|
335
349
|
- In the web editor, use CSS/declarative motion only. Script-bearing HTML is stripped or rejected there.
|
|
336
350
|
- **Never render or approve without judging frame 0 as a standalone still.** It is the thumbnail everywhere the post appears; an empty/black opening frame ships a dead post. See “The FIRST FRAME is the thumbnail”.
|
|
351
|
+
- **Never judge the VIDEO by one frame, and never report a render as reviewed without the holistic pass.** Compare frames from at least two different scenes (a frozen render passes every other check), read a contact sheet for balance/spacing/style/pacing drift, and state separately what you measured vs. what you judged. See “Judge the WHOLE video”.
|
|
337
352
|
|
|
338
353
|
## Recommended Recipes
|
|
339
354
|
|
|
@@ -1208,12 +1223,12 @@ Web copilot: same standard, applied by hand — check the opening layer's `start
|
|
|
1208
1223
|
|
|
1209
1224
|
Compositions are authored in HTML, so the single most common way an AI-edited video betrays itself is by **looking like a web page**. A landing-page button, a row of feature chips, a frosted card with a gradient headline — these are everywhere on the web and **nowhere in a real social post**. A director scrolling TikTok/Reels/Shorts never sees them, so the moment one appears the video reads as machine-made and the retention dies. HTML and hyperframes are the right tool; **Bootstrap/Tailwind furniture is not.**
|
|
1210
1225
|
|
|
1211
|
-
**The one test:** *
|
|
1226
|
+
**The one test — the native-editor test:** *could you have made this element with the tools inside TikTok's (or CapCut's) own editor?* That editor's text tool gives you exactly this: a font, a color, a stroke/outline, a soft shadow, a tight text-box band, alignment, opacity, rotation, and animation presets — plus stickers, emoji, drawn marks, and clips. It does **not** give you a padded capsule, a border, a gradient fill, a blur panel, or a card. If you had to reach past that toolset, you are decorating like a web designer, and the frame will read as machine-made no matter how good the copy is. The second half of the same test: if the element's whole job is to look **clickable**, cut it — **nothing in a video is clickable.**
|
|
1212
1227
|
|
|
1213
1228
|
**BANNED — never author, and strip on sight when a fork or a paste brings one in:**
|
|
1214
1229
|
|
|
1215
1230
|
- **CTA / "button" shapes.** Any filled capsule or rounded rect containing action copy — "Sign Up for a Free Trial →", "Learn More", "Get Started", "Book a Call" — especially gradient-filled with a glow or drop shadow. A social CTA is *spoken*, or a plain caption line, or an arrow pointing at the real UI. Not a button.
|
|
1216
|
-
- **
|
|
1231
|
+
- **Badges, chips, pills — including a SINGLE one.** The "✓ ID-Verified Tutors · ✓ No Credit Card Needed · ✓ 30-Min Trial" strip is the obvious case, but the far more common one is **one lonely capsule holding a stat or a label**: `( 10 hrs / week )`, `( STEP 2 )`, `( EP.01 )`, `( BEGINNER )`, `( +40% )`. Being alone does not make it native — a rounded, padded, filled tag around static text is a `<span class="badge">` wearing a different hat, and it is one of the loudest web tells in the whole frame. **The only legitimate pill in a video is the active-word highlight** (`spotlight`/`karaoke`), because it tracks the spoken word and moves. Static text gets `outline`, `plain`, or a tight band that hugs the glyphs (radius ≤ ~8px). If a stat deserves emphasis, give it emphasis the *editor* can give: bigger, heavier, ALL-CAPS, an accent color, a hand-drawn circle or underline around it, its own beat on screen.
|
|
1217
1232
|
- **Cards, panels, containers.** A rounded-rect box (dark, bordered, shadowed, or `backdrop-filter` frosted) holding a heading plus a subheading/URL. Text belongs **on the footage**, not inside a floating panel with padding around it.
|
|
1218
1233
|
- **Gradient text fills, neon border glows, elevation shadows, glassmorphism, hover-implying strokes.**
|
|
1219
1234
|
- **Web page furniture of any kind:** navbars, hero sections, feature grids, two-column layouts, `<ul>` bullet lists with disc markers, tables, alerts, progress bars as decoration, logo-in-a-circle avatars, "as seen in" strips.
|
|
@@ -1221,6 +1236,8 @@ Compositions are authored in HTML, so the single most common way an AI-edited vi
|
|
|
1221
1236
|
|
|
1222
1237
|
**Greppable smell test.** If you are typing `class="btn…"`, `badge`, `chip`, `tag`, `card`, `panel`, `container`, `row`/`col-`, `hero`, `cta`, `rounded-full`, `shadow-lg`, `backdrop-blur`, `bg-gradient-to-r`, `border border-…` — **stop.** You are building a web page, not a video. Rewrite as timed text on footage.
|
|
1223
1238
|
|
|
1239
|
+
**The capsule rule of thumb.** On any element that holds words: `border-radius` over ~8px **combined with** a background fill and padding = a badge. Either take the fill away (bare text + outline/shadow) or take the radius and padding down until the band hugs the glyphs. There is no third option for static text.
|
|
1240
|
+
|
|
1224
1241
|
**ALLOWED and encouraged — these ARE social-native:**
|
|
1225
1242
|
|
|
1226
1243
|
- **Arrows** (drawn, animated, hand-style), circles/scribbles/underline strokes highlighting part of the frame, hand-drawn marks, checkmarks *as glyphs inside a caption line* (not as chips).
|
|
@@ -1247,7 +1264,9 @@ Short-form is watched on a phone, and the phone's UI eats the frame's edges. **N
|
|
|
1247
1264
|
| 3 | **Highlight pill behind the ACTIVE word only** | `set_captions caption_style:"spotlight"` / `"karaoke"` (+ `caption_highlight_color`) | Hormozi/CapCut word-by-word. **The only legitimate "pill" in a video** — it tracks the spoken word, so it isn't a badge |
|
|
1248
1265
|
| 4 | **Solid band that tightly hugs the text lines** (CapCut "text box") | `background_style:"highlight-solid"` (or `"highlight-translucent"`) + a `background` color | Guaranteed legibility over noisy footage |
|
|
1249
1266
|
|
|
1250
|
-
|
|
1267
|
+
**Pick between them by MEASURING the background behind the caption band, not by habit.** Dark-and-calm behind the band (luma < ~70, variation < ~42) → light type, **no plate** (treatment 1/2 — a plate there is a bright slab the design never asked for); bright-and-calm (luma > ~160) → dark type, no plate; busy / mid-tone / moving colour → treatment 4, because nothing else stays readable. The active-word colour has to follow the same call — a deep red that reads on a white plate is unreadable on near-black. **One treatment for the whole video**; styling that flips every few seconds reads as a bug. Procedure, thresholds and how to measure the *composited* value (not the source file): `regimes/short-form.QA_REGIME.md` → "Caption styling is MEASURED off the background".
|
|
1268
|
+
|
|
1269
|
+
Treatment 4 is a **band, not a card**: it hugs the glyphs with minimal padding, corner radius ≤ ~8px, **no border, no drop shadow, no gradient, no blur**, and it wraps *one* text run — never a heading + subheading + URL stacked inside one rounded box. The moment it grows padding, a stroke, or a second element inside it, it has become a web card. Fix it. And the moment its radius goes fully round, it has become a **badge** — treatment 3 is the *only* capsule allowed, and only because it tracks the spoken word. A static "10 hrs / week" in a rounded pill is web furniture; the same words in treatment 1 or 2, bigger and heavier, are a beat.
|
|
1251
1270
|
|
|
1252
1271
|
**A common trap: decomposed templates mirror the source's caption placement**, so a forked meme can arrive with its caption pinned at `top:0` in a non-regime font — and a re-theme prompt ("make this for my tutoring service") is exactly where an agent starts inventing landing-page CTAs and benefit chips because the *subject* is a SaaS product. **Fix to the standard, don't inherit it, and don't import the website's design language into the video.** When placing text yourself (`set_captions`, `set_layer_text`, `set_layer_style`, `add_layer`, devcli `place`/`captions`), set `y` / `font_family` / `font_weight` / `background_style` to the standard from the start.
|
|
1253
1272
|
|
|
@@ -1585,6 +1604,146 @@ A video with replies gets shown again; a video with none dies at its first audie
|
|
|
1585
1604
|
|
|
1586
1605
|
**The checkable version of everything above:** `vidfarm regime show hooks` — the twelve-item pre-flight checklist is the part you answer honestly on every video, and two items carry most of the weight: *situation, not label* (predicts cold-start survival before you write a word) and *unguessable* (the only item a hook can fail while passing every other one, which is why it ships).
|
|
1587
1606
|
|
|
1607
|
+
## Reviewing a render — look at the whole video, and never trust one frame
|
|
1608
|
+
|
|
1609
|
+
**Assume your own finished video has a defect you can't see, because you built it.** This is not humility, it's the observed base rate: across a 32-video bespoke batch, **every single first-pass video had a real defect that the agent who built it had already reported as "verified, looks good."** Dead space under the content, a placeholder that reads as a failed render, two contradictory numbers 200px apart, a CTA still animating when the video ends. None of these are subtle. All of them survived a confident self-review.
|
|
1610
|
+
|
|
1611
|
+
The reason is structural, not sloppiness: **an agent builds a video the way it builds code — part by part, each part correct in isolation.** Scene 3 is written while scene 3 is the whole world. So each scene passes on its own and the video fails as a video: the type jumps two sizes between beats, one scene breathes and the next is crammed to the margins, the accent colour drifts, a transition lands like a slap because nothing before it moved that fast. Nobody watches a scene. They watch the sequence.
|
|
1612
|
+
|
|
1613
|
+
So the review has two jobs, and they need two different passes:
|
|
1614
|
+
|
|
1615
|
+
1. **The holistic pass** — does this read as ONE video, made by one person, on purpose?
|
|
1616
|
+
2. **The defect pass** — is any individual frame broken in one of the six ways frames are usually broken?
|
|
1617
|
+
|
|
1618
|
+
Do them in that order. The holistic pass is the one agents skip, and it is the one that separates "technically correct" from "good."
|
|
1619
|
+
|
|
1620
|
+
---
|
|
1621
|
+
|
|
1622
|
+
## Pass 1 — the holistic pass: watch it as one object
|
|
1623
|
+
|
|
1624
|
+
**Before you look for defects, look at the video the way a stranger will: all at once, start to finish, with no memory of how it was built.** You cannot do this from the code, from the storyboard, or from the scene you just edited — you have to look at the actual frames, in order, side by side.
|
|
1625
|
+
|
|
1626
|
+
```bash
|
|
1627
|
+
# ONE command — renders the stills AND tiles them into ./work/stills/contact-sheet.png.
|
|
1628
|
+
# No MP4 render needed; free, local, in-process.
|
|
1629
|
+
vidfarm stills ./work --sheet # default timestamps = midpoint of each scene clip (cap 8)
|
|
1630
|
+
vidfarm stills ./work --sheet --at 0,2,4,6,8,10,12,14,16,18,20,22 # or pick them yourself
|
|
1631
|
+
# then READ ./work/stills/contact-sheet.png as an image
|
|
1632
|
+
# --sheet-out <file> relocates it; --sheet-width <px> for bigger tiles (default 320)
|
|
1633
|
+
|
|
1634
|
+
# already have the MP4? same idea, straight off the file
|
|
1635
|
+
for t in 0 2 4 6 8 10 12 14 16 18 20 22; do
|
|
1636
|
+
ffmpeg -y -ss $t -i final.mp4 -frames:v 1 "qa/f$(printf %03d $t).png"; done
|
|
1637
|
+
ffmpeg -y -pattern_type glob -i "qa/f*.png" \
|
|
1638
|
+
-vf "scale=300:-1,tile=4x3:margin=6:padding=6:color=0x999999" -frames:v 1 qa/sheet.png
|
|
1639
|
+
```
|
|
1640
|
+
|
|
1641
|
+
**The tile sheet is the point.** One image read, twelve frames, and the eye picks up drift instantly that no per-scene check can see. Read it as an image — not the filenames, not the HTML that produced it. (The still filenames are zero-padded seconds, so a glob stays in chronological order.)
|
|
1642
|
+
|
|
1643
|
+
Then answer these, out loud, in your report:
|
|
1644
|
+
|
|
1645
|
+
- **Balance.** Is weight distributed across the frame, or is every scene top-anchored with an empty band underneath? Does the composition use the canvas, or does it use the top third of the canvas and leave the rest as dead area? A sheet of twelve frames makes a recurring dead zone obvious; one frame at a time never will.
|
|
1646
|
+
- **Spacing and breathing room.** Are margins consistent scene to scene? Does one beat have generous air and the next one crowd the safe zone? Uneven padding across scenes is the single loudest "assembled by a machine" tell, and it's invisible while you're inside any one scene.
|
|
1647
|
+
- **Typographic continuity.** One type system, or three? Headline sizes should belong to a small set (two, maybe three), not be individually chosen per scene. Same for weight, case, and colour. If scene 2's headline is 64px and scene 5's is 41px for no dramatic reason, that's drift, not design.
|
|
1648
|
+
- **Colour and style coherence.** One accent colour, one background treatment, one illustration style. Assets generated or sourced at different moments drift — a flat-vector sticker next to a photographic cutout next to a gradient panel reads as three videos spliced together.
|
|
1649
|
+
- **Rhythm and pacing.** Do scene durations form a deliberate pattern (a fast open, a longer explanation, a fast close), or is every scene the same length because a loop wrote them? Same-length beats are hypnotic in the bad way. Conversely, one 9-second hold in a video of 2-second cuts stalls it dead.
|
|
1650
|
+
- **Nothing jarring at the joins.** Watch each transition specifically. A cut from a dark scene to a white one is a flash in the face; a scale-up entrance immediately after a scale-up exit reads as a stutter; two consecutive scenes whose subjects sit in the same screen position with different content look like a glitch, not a cut. Where a join is harsh, either match the two frames either side of it (colour, position, energy) or make the harshness deliberate and rhythmic.
|
|
1651
|
+
- **One idea per moment.** Across the whole sheet, is there any frame where two things compete for the eye — display text over captions saying the same words, a busy background under type, two headlines superimposed at a handoff? At the video level this shows up as a *density* problem: some beats carry three elements and some carry one.
|
|
1652
|
+
- **Does it look like one person made it in one sitting?** The summary question. If the honest answer is "it looks assembled," name specifically which scenes don't belong and fix them toward the majority, don't average everything.
|
|
1653
|
+
|
|
1654
|
+
**When something is off, fix it globally, not locally.** The instinct after spotting drift is to patch the one scene that stands out. Usually the right fix is to define the rule (two headline sizes, one accent, 8% margins, 2.5s default beat) and apply it across every scene — including the ones that already looked fine. A video is a system; patching one node keeps the system inconsistent.
|
|
1655
|
+
|
|
1656
|
+
**Build order helps too, if you're still building.** Author the shared system first — type scale, palette, margins, motion vocabulary, default beat length — as one thing that every scene reads from, then fill the scenes. Scenes written first and harmonised later almost never fully converge.
|
|
1657
|
+
|
|
1658
|
+
---
|
|
1659
|
+
|
|
1660
|
+
## Pass 2 — the defect pass: what actually goes wrong, in frequency order
|
|
1661
|
+
|
|
1662
|
+
From the same 32-video batch, ranked by how often it happened. Look for these specifically; they are what your own review misses.
|
|
1663
|
+
|
|
1664
|
+
1. **Large flat dead regions.** Content top-anchored with an empty band below it. Agents do this constantly and never notice, because during authoring the element is the subject and the emptiness is just "background."
|
|
1665
|
+
2. **A placeholder empty state that reads as a missing asset.** A big empty dashed rectangle held for two seconds looks exactly like the render failed. If a scene's job is "an empty inbox," it still has to look designed, not broken.
|
|
1666
|
+
3. **Two contradictory numbers in one frame.** Especially on anything data-shaped — a stat in the headline and a different one in the visual beneath it.
|
|
1667
|
+
4. **A CTA or end card still building when the video ends.** The final state must be **settled at least 2 seconds before the last frame**, or the loop-around cuts it off and the ask never lands.
|
|
1668
|
+
5. **Two headlines superimposed at a scene handoff.** The outgoing scene's text hasn't left when the incoming one arrives. Fix at the timing level: exit at `nextIn − 0.18`, duration `0.24`, ease `power2.out` — a slow-leaving `power2.in` is what causes the overlap in the first place.
|
|
1669
|
+
6. **Type colliding with a busy background layer** exactly at the moment it's spoken. Particles, pins, footage detail — legible in the still you checked, unreadable at the second the word lands.
|
|
1670
|
+
|
|
1671
|
+
**If a frame looks empty, check whether it RESTS there.** Sample at 0.2–0.25s intervals through that transition. A transient near-empty wipe frame is fine and normal; anything holding empty for **>0.5s** is a hole in the video.
|
|
1672
|
+
|
|
1673
|
+
```bash
|
|
1674
|
+
vidfarm stills ./work --at 6.0,6.2,6.4,6.6,6.8,7.0 # is it a wipe, or a hole?
|
|
1675
|
+
```
|
|
1676
|
+
|
|
1677
|
+
---
|
|
1678
|
+
|
|
1679
|
+
## Never verify a video by one frame
|
|
1680
|
+
|
|
1681
|
+
**This is the failure mode that survives every check you'd think to run.** Whole classes of render bug produce a video where *every frame is identical* — the timeline never ran — while duration, frame count, file size and audio hash all come out exactly right. Frame 0 looks perfect, so a single-frame check passes and you ship a frozen video.
|
|
1682
|
+
|
|
1683
|
+
Two real causes, both silent:
|
|
1684
|
+
|
|
1685
|
+
- **A watermark/overlay pass without `-loop 1` on a single-frame PNG input.** The frame-sync collapses the whole video onto one frame. Five videos shipped this way before it was caught.
|
|
1686
|
+
- **Assets outside the composition root.** Only `<style>`/`<script>` *inside* the `data-composition-id` root execute, and sibling relative files may not resolve — fonts, images, even the animation library itself. The timeline never starts; frame 0 still renders fine because frame 0 is the static DOM.
|
|
1687
|
+
|
|
1688
|
+
**The rule that catches both: always compare two frames from different scenes.** They must differ a lot. And when you've applied any pass over an existing video (watermark, overlay, dedupe, re-encode), also compare each output frame against **its own** input frame at the same timestamp — that difference should be tiny. Two checks, opposite directions:
|
|
1689
|
+
|
|
1690
|
+
```bash
|
|
1691
|
+
# consecutive/distant frames must DIFFER (motion preserved)
|
|
1692
|
+
vidfarm stills ./work --at 0,4,9,14
|
|
1693
|
+
# after an overlay pass: same timestamp, before vs after, must be NEARLY IDENTICAL
|
|
1694
|
+
ffmpeg -y -ss 7 -i clean.mp4 -frames:v 1 a.png
|
|
1695
|
+
ffmpeg -y -ss 7 -i final.mp4 -frames:v 1 b.png
|
|
1696
|
+
ffmpeg -i a.png -i b.png -filter_complex "psnr" -f null - # very high PSNR = only the mark changed
|
|
1697
|
+
```
|
|
1698
|
+
|
|
1699
|
+
This sits directly beside the frame-0 rule and is its necessary counterweight: **frame 0 is the thumbnail, so judge it alone — but never judge the VIDEO by it.** The two rules are checking different things, and an agent that only internalises the first one has a perfect blind spot for a frozen render.
|
|
1700
|
+
|
|
1701
|
+
---
|
|
1702
|
+
|
|
1703
|
+
## Verify audio by measurement, not by ear
|
|
1704
|
+
|
|
1705
|
+
**You cannot hear the render.** Do not report "the mix sounds good" — you have no way to know it, and it is the claim that most often turns out false. Measure instead.
|
|
1706
|
+
|
|
1707
|
+
- **Speech-over-bed separation: target 12–15 dB.** Measure the RMS of the mix across the spans where words actually occur, minus the RMS of a bed-only stretch. Word spans come free from the transcription you're already running for captions (`vidfarm stt <file> --engine whisper` → word timings).
|
|
1708
|
+
- **Peak below 0 dBFS.** A mix that clips reads as amateur instantly on a phone speaker.
|
|
1709
|
+
- **Beware "separation" numbers computed over the music-only tail** — they measure the wrong thing (bed alone vs. bed alone) and over-report by a wide margin. Don't retune a mix based on one.
|
|
1710
|
+
|
|
1711
|
+
```bash
|
|
1712
|
+
ffmpeg -i final.mp4 -af "volumedetect" -f null - # peak + mean over the whole file
|
|
1713
|
+
ffmpeg -i final.mp4 -ss 3.1 -t 1.4 -af "volumedetect" -f null - # a span where a word is spoken
|
|
1714
|
+
```
|
|
1715
|
+
|
|
1716
|
+
**Narration timing gotchas that produce a correct-looking, wrong-sounding video:**
|
|
1717
|
+
|
|
1718
|
+
- **Never `adelay` the voiceover.** Whisper's word timings — and therefore every caption you generated from them — are relative to the raw `vo.wav`. Delaying the VO desyncs every caption in the video while the file still plays fine. Use `apad` + `atrim` to place it instead.
|
|
1719
|
+
- **Scene handoffs can leave ~0.3s of silence.** Extend each clip's audio ~0.35s into the next.
|
|
1720
|
+
- **Whisper's default model is English-only and will hallucinate fluent English over another language.** Non-English narration needs `--model large-v3 --language <code>`. The output looks like a clean transcript, so this one ships silently.
|
|
1721
|
+
- **`vidfarm tts` reads stdin** — always redirect `</dev/null` when calling it inside a shell loop, or the loop eats its own input.
|
|
1722
|
+
|
|
1723
|
+
---
|
|
1724
|
+
|
|
1725
|
+
## The revision pass — how to fix what review found
|
|
1726
|
+
|
|
1727
|
+
**Spawn a fresh pass rather than re-litigating with the context that produced the defect.** If you're handing fixes to a subagent (or picking the work back up yourself later), the brief that works:
|
|
1728
|
+
|
|
1729
|
+
- **State it as N targeted fixes and nothing else.** "The video is good — you are making three specific fixes." Open-ended "improve it" turns a working video into a different, differently-broken video.
|
|
1730
|
+
- **Edit the generator, not the generated output.** If a script produced `composition.html`, fix the script. Check first that a generator exists — some compositions are hand-authored.
|
|
1731
|
+
- **Back up before overwriting** — keep `<slug>-v1.mp4`. Re-renders are cheap locally; a lost good version isn't.
|
|
1732
|
+
- **Keep audio bit-identical unless audio is the defect.** Reuse the existing `vo.wav` / word timings rather than re-recording; a re-record retimes every caption for no reason.
|
|
1733
|
+
- **Give the PROBLEM, not just your proposed solution.** Repeatedly, the agent handed a described defect found a better fix than the one specified — using an app's own collapsed UI state instead of a redaction box, a type safe-zone solver instead of a scrim, making a document's *arrival* the spectacle instead of cutting the document. Say what's wrong and at what timestamp; let the fix be found.
|
|
1734
|
+
- **Then sweep for the same class of problem** across the rest of the video, and report what else turned up. Defects of a given kind are rarely solitary — they come from a habit.
|
|
1735
|
+
|
|
1736
|
+
---
|
|
1737
|
+
|
|
1738
|
+
## Report both halves honestly
|
|
1739
|
+
|
|
1740
|
+
When you hand back a render, say what you **measured** and what you **judged**, separately:
|
|
1741
|
+
|
|
1742
|
+
- Machine-settled: `vidfarm qa ./work` findings, `vidfarm lint`, durations, peak dBFS, frame-difference checks.
|
|
1743
|
+
- Human-judgment: the holistic pass above — balance, spacing, type continuity, colour coherence, pacing, joins — plus the regime's `- [ ]` review items.
|
|
1744
|
+
|
|
1745
|
+
**Never report a clean pass on the half you didn't actually look at.** A confident "verified, looks good" over an unreviewed video is worse than no review, because it spends the director's trust on nothing — and per the base rate at the top of this file, it is usually wrong.
|
|
1746
|
+
|
|
1588
1747
|
## Download a video from a website (Vidfarm fetches it for you — paid plans)
|
|
1589
1748
|
|
|
1590
1749
|
**Yes, Vidfarm can download videos off the web for the user.** When someone pastes a YouTube / TikTok / Instagram / X (Twitter) / other supported post URL and wants the actual file — "grab this video", "save this clip", "get me that audio" — you do **not** need yt-dlp, a browser extension, or an apology. One primitive does it and returns a durable Vidfarm-hosted asset:
|
|
@@ -2051,7 +2210,7 @@ The licensed harness also carries the **generative build workflow** guidance (ch
|
|
|
2051
2210
|
| `vidfarm raws search "…"` / `raws match "…"` / `raws list` / `raws sources` | (local library; NL→criteria via local agent or provider key) | search/reuse the raws library |
|
|
2052
2211
|
| `vidfarm raws preset list\|run\|save` / `raws export <ids…> --to <dir>` | (local library) | saved queries; copy raw MP4s out |
|
|
2053
2212
|
| `vidfarm lint <dir\|composition.html>` | (local static validation) | pre-publish composition check: timing, overlaps, preset names, media src |
|
|
2054
|
-
| `vidfarm stills <dir> [--at 0,2.5,…]` | (local in-process render of PNG frames) | visually verify an edit without a full render |
|
|
2213
|
+
| `vidfarm stills <dir> [--at 0,2.5,…] [--sheet]` | (local in-process render of PNG frames) | visually verify an edit without a full render. **`--sheet` also tiles them into one contact sheet** (`<out>/contact-sheet.png`, `--sheet-out`/`--sheet-width` to tune) — the whole-video review pass: read it as ONE image and sequence-level drift (uneven margins, three type sizes, a wandering accent colour, N identical beats, a jarring join) becomes obvious where per-scene checks never see it |
|
|
2055
2214
|
| `vidfarm qa <dir\|composition.html> [--regime <name\|path>…] [--json] [--strict]` | (local static QA — **devcli-only**, no cloud/REST twin) | **social-native QA: HTML slop + first frame + font regime. Run it on EVERY video you produce.** `--regime` grades against a QA_REGIME.md too (stackable). Free, instant, feedback-only |
|
|
2056
2215
|
| `vidfarm regime list\|show <name\|path>\|init <name> [--out <path>]` | (local — **devcli-only**) | QA_REGIME.md: the director's own per-style quality contract. `init` copies a bundled base to edit — the pattern for bulk/scripting mode |
|
|
2057
2216
|
| `vidfarm doctor` | (local environment triage) | check ffmpeg/node/keys/agent CLI/poisoned env + list local serve/preview processes before debugging anything else; `--kill-orphans` reaps dead servers squatting ports (fixes the "Waiting for preview server…" hang) |
|
|
@@ -2091,6 +2250,7 @@ What it flags:
|
|
|
2091
2250
|
|---|---|---|
|
|
2092
2251
|
| `cta-button` | error | Action copy ("Sign Up for a Free Trial →") **inside** a filled/gradient rounded capsule. Bare CTA copy in a caption is fine — "BUY NOW" is real social copy |
|
|
2093
2252
|
| `badge-chip-row` | error | 2+ sibling small rounded filled tags — the "✓ No Credit Card Needed · ✓ 30-Min Trial" strip |
|
|
2253
|
+
| `static-pill` | error | ONE filled, padded, ≥20px-radius capsule around static text — a stat/label badge like "10 hrs / week", "STEP 2", "EP.01". Skips active-word `spotlight`/`karaoke` highlights (the only legitimate pill) and mock social UI (chat bubbles, comment cards — mark yours `data-vf-mock-ui` if the heuristic misses it) |
|
|
2094
2254
|
| `card-panel` / `glass-card` | error | A rounded box with a border/shadow/`backdrop-filter` wrapping 2+ elements. A tight legibility **band** (radius ≤8px, one text run, no border/shadow) stays legal |
|
|
2095
2255
|
| `gradient-text` | error | `background-clip:text` gradient headline fills |
|
|
2096
2256
|
| `clickable-element` | error/warn | `<button>`, `<form>`, `<input>`; `<a href>` warns |
|
|
@@ -2105,6 +2265,8 @@ What it flags:
|
|
|
2105
2265
|
|
|
2106
2266
|
Every finding carries a concrete `fix` line — the answer is always "say it as timed text on the footage", never just "delete it". Fold `--json` into scripted batch runs to QA N variants at once.
|
|
2107
2267
|
|
|
2268
|
+
**`vidfarm qa` is a static DOM check — it cannot see the rendered video.** It never looks at pixels, motion, spacing, colour drift, pacing, or the joins between scenes, so a clean `qa` run says nothing about whether the video reads as one coherent piece. That judgment is a separate, mandatory pass: tile stills into a contact sheet, read it as an image, and check balance/spacing/type/colour/rhythm across the whole sequence. It also can't catch a **frozen render** (every frame identical while duration, frame count and audio hash all pass), which is why you compare frames from two different scenes. Full method: `references/reviewing-renders.md`.
|
|
2269
|
+
|
|
2108
2270
|
**The two halves, and why the tool only claims one.** Everything above is universal and mechanical. The half that decides whether a *particular* video is any good — is the hook legible cold, does the loop close, is this variant genuinely different from its siblings — is the director's, and it lives in a **`QA_REGIME.md`** (see "Scripting mode" above). Pass one with `--regime <name|path>` (repeatable, and a `QA_REGIME.md` sitting next to the composition is picked up automatically): its `checks:` front matter is settled deterministically alongside the built-ins, and its `- [ ]` checklist comes back as **review items you must answer yourself**. `vidfarm qa` deliberately never fakes a verdict on those — a "PASS" it couldn't have earned is worse than no check at all.
|
|
2109
2271
|
|
|
2110
2272
|
## Cost mode — the devcli's money-saving guardrail
|
|
@@ -2130,6 +2292,15 @@ The four modes, quoted as **cost per finished video**. The first two are spend p
|
|
|
2130
2292
|
|
|
2131
2293
|
**Narration defaults to the FREE local voice in minimize AND hybrid.** A bare `vidfarm tts "…"` runs the keyless local Kokoro-82M engine in both of those modes — you no longer have to remember `--engine local`. A run **opts out** of that default by asking for a premium voice (`--style`, `--provider`, `--model`, `--own-key`, or a non-Kokoro `--voice` like `alloy`/`Kore`/an ElevenLabs id), by passing `--cloud`/`--engine byok`, or by being in `rich-ai`/`pure-videogen`. If the local engine isn't installed on the machine (it needs `pip install kokoro-onnx soundfile` + a ~340MB model on first use), the run **falls back** to the user's provider key / cloud instead of failing — it prints the reason on stderr so you can tell the user why the voice changed.
|
|
2132
2294
|
|
|
2295
|
+
**Narration gotchas that ship a correct-looking, wrong-sounding video.** Each of these produces output that passes every structural check:
|
|
2296
|
+
|
|
2297
|
+
- **Never `adelay` the voiceover to position it.** Whisper's word timings — and therefore every caption generated from them — are relative to the raw `vo.wav`. An `adelay` desyncs every caption in the video while the file still plays perfectly. Use `apad` + `atrim`.
|
|
2298
|
+
- **Whisper's default model is English-only and hallucinates fluent English over other languages.** Non-English narration needs `--model large-v3 --language <code>`; without it you get a clean, confident, entirely invented transcript.
|
|
2299
|
+
- **`vidfarm tts` reads stdin** — redirect `</dev/null` when calling it inside a shell loop, or the loop consumes its own input.
|
|
2300
|
+
- **Check the brand/product name's pronunciation** before you render 20 variants with it. TTS engines mangle proper nouns (Kokoro reads *Genki* as "Jenki"); respell it phonetically in the TTS input and confirm with a whisper round-trip — you're already running whisper for the caption timings.
|
|
2301
|
+
- **For a calm, unhurried read, render line by line** and concatenate the takes with measured silences, rather than one continuous pass. A single pass reads rushed however slow the copy is, because the pauses are TTS filler rather than real beats.
|
|
2302
|
+
- **Verify the mix by measurement, not by ear** — you can't hear the render. Target **12–15 dB** of speech-over-bed separation measured across the actual word spans, peak below **0 dBFS**. A "separation" figure computed over the music-only tail measures bed-vs-bed and over-reports badly; don't retune against it. See `references/reviewing-renders.md`.
|
|
2303
|
+
|
|
2133
2304
|
Precedence: `--cost-mode <m>` flag → `VIDFARM_COST_MODE` env → the saved `cost-mode` → default (hybrid, flagged as "not set"). When nothing is saved and a billed op runs, the CLI prints a "no preference set — ask the user" nudge instead of silently spending, so the default posture really is *ask before you spend*.
|
|
2134
2305
|
|
|
2135
2306
|
**Agent-memory handoff.** After the user picks, offer to remember it across sessions — but the destination depends on the agent, so ask: Claude Code → `CLAUDE.md` (or its memory dir); Codex / OpenCode / most others → `AGENTS.md`; or a note file the user names. `vidfarm cost-mode <choice>` already persists the devcli-side preference; agent memory is the extra step that survives a fresh checkout. In the **web app UI** there is no memory file — ask each time unless the user states a standing preference for the session.
|
|
@@ -2798,8 +2969,9 @@ Use this when a coding agent is doing the work locally or the user wants a repro
|
|
|
2798
2969
|
3. Read `./work/.harness/agent-guide.md` and `./work/.harness/context.json` before editing.
|
|
2799
2970
|
4. Make deterministic edits to `composition.html` and optionally `composition.json`.
|
|
2800
2971
|
5. Validate with `vidfarm lint` or `vidfarm stills` when useful. **Always look at `vidfarm stills ./work --at 0`** — that frame becomes the thumbnail, so it must not be black, empty, or mid-fade.
|
|
2801
|
-
6. **QA before you render: `vidfarm qa ./work`.** Free, instant, devcli-only. It blocklists HTML slop (CTA buttons, benefit chip rows, frosted cards, gradient text, web-page classes/fonts), checks the caption font regime + safe zone, and flags a blank/fading first frame (the thumbnail). Feedback only — exit 0 even on findings, never automatic — but it catches the #1 tell of an agent-made video, so run it on every production. Fix what's real, ignore what's a deliberate style call, then render.
|
|
2972
|
+
6. **QA before you render: `vidfarm qa ./work`.** Free, instant, devcli-only. It blocklists HTML slop (CTA buttons, benefit chip rows, a lone pill around a static stat/label, frosted cards, gradient text, web-page classes/fonts), checks the caption font regime + safe zone, and flags a blank/fading first frame (the thumbnail). Feedback only — exit 0 even on findings, never automatic — but it catches the #1 tell of an agent-made video, so run it on every production. Fix what's real, ignore what's a deliberate style call, then render.
|
|
2802
2973
|
7. Render with `vidfarm render <forkId> --dir ./work --wait`.
|
|
2974
|
+
7b. **Review the render as a whole before you approve — this is the step that most changes quality.** `vidfarm qa` and `lint` are static checks on the DOM; neither can see the video. Tile ~12 stills into one contact sheet and read it as an image — `vidfarm stills ./work --sheet` does both in one command (add `--at 0,2,4,…` to pick the timestamps): consistent margins, one type scale, one accent colour, deliberate pacing, no jarring join, no dead band under top-anchored content, end card settled ≥2s before the last frame. Compare frames from two different scenes — a frozen render (overlay pass without `-loop 1`, assets outside the composition root) passes duration, frame-count and audio-hash checks while every frame is identical. Check the mix by measurement, not by ear. Full method + the six most common defects: `references/reviewing-renders.md`.
|
|
2803
2975
|
8. **Ask about deduplication before you approve** — "is this going out more than once (several accounts, another platform, a re-post later)?" If yes, run `vidfarm dedupe ./final.mp4 [--variants N]` on the **exported** MP4 (free, local ffmpeg, no re-render) and approve each variant separately. Asking here rather than after publication is what avoids paying for a second render. See `references/core-workflows.md` → *Deduplicate before you publish*.
|
|
2804
2976
|
9. Approve the finished MP4 with `vidfarm approve --video <url|./final.mp4> --caption "..."`. This prints the shareable `share_url`.
|
|
2805
2977
|
|
|
@@ -2873,6 +3045,21 @@ done
|
|
|
2873
3045
|
|
|
2874
3046
|
Free, offline, no wallet. Variant 1 is the `standard` preset as authored (skew 2%, zoom 3%, rotate 2°, speed +2%, saturation +4%); later variants get jittered magnitudes and flipped signs, so they differ from the original **and from each other**. One variant per account — two accounts posting the same variant defeats the point. Reuse one `--seed` per source so the batch is reproducible.
|
|
2875
3047
|
|
|
3048
|
+
### 5c. Eyeball the renders — the loop cannot do this for you
|
|
3049
|
+
|
|
3050
|
+
**A batch is exactly where "the agent passed its own broken work" compounds**: nobody is watching variant #37, and `vidfarm qa` is a static DOM check that never sees a rendered pixel. So add one cheap visual pass over the output — a contact sheet per video, read as an image:
|
|
3051
|
+
|
|
3052
|
+
```bash
|
|
3053
|
+
for MP4 in renders/*.mp4; do
|
|
3054
|
+
S="$(basename "$MP4" .mp4)"; mkdir -p "qa/$S"
|
|
3055
|
+
for t in 0 3 6 9 12 15; do ffmpeg -y -ss $t -i "$MP4" -frames:v 1 "qa/$S/f$(printf %03d $t).png"; done
|
|
3056
|
+
ffmpeg -y -pattern_type glob -i "qa/$S/f*.png" \
|
|
3057
|
+
-vf "scale=320:-1,tile=3x2:margin=6:padding=6:color=0x999999" -frames:v 1 "qa/$S-sheet.png"
|
|
3058
|
+
done
|
|
3059
|
+
```
|
|
3060
|
+
|
|
3061
|
+
Read the sheets. In a batch you're looking for two different things: **per-video** defects (dead regions, a placeholder that reads as a failed render, contradictory numbers, an unlanded CTA, superimposed headlines at a handoff) and **cross-video** drift (variants that no longer look like siblings, or look *too* identical to be N distinct posts). Also compare two frames from different scenes in at least a sample of the renders — a systematic frozen-render bug in the loop will produce N broken files that all pass duration/frame-count checks. Full method: `references/reviewing-renders.md`.
|
|
3062
|
+
|
|
2876
3063
|
### 6. Answer the review items — don't skip this
|
|
2877
3064
|
|
|
2878
3065
|
The regime's `- [ ]` checklist comes back on every run because the CLI *can't* settle it. Machine checks catch a 13-word hook or a black first frame; only you can answer "is this variant genuinely different from its siblings?" or "can the viewer guess the withheld answer?" **Report both halves honestly**: what the machine checked, and what you judged. A batch report claiming a clean pass on the judgment half is worse than no report.
|
|
@@ -2895,13 +3082,15 @@ The mechanical trio — **generate on a chroma plate → key it out → trim to
|
|
|
2895
3082
|
|
|
2896
3083
|
**Unless the director asks for something else, build every explainer this way. Don't ask, just do it, and mention the defaults once so they can override.** The whole point of the house style is that explainers read as *clean, bright, and easy* — a busy explainer is a failed explainer.
|
|
2897
3084
|
|
|
2898
|
-
- **White background, light mode.** A plain white (or near-white `#FFFFFF`–`#FAFAFA`) stage. No dark mode, no gradients, no photographic backdrop, no texture. Light mode reads cleaner on every feed, keeps cutout stickers legible, and makes flat-vector art look intentional. Set the composition/scene background to white first, before placing anything.
|
|
3085
|
+
- **White background, light mode.** A plain white (or near-white `#FFFFFF`–`#FAFAFA`) stage. No dark mode, no gradients, no photographic backdrop, no texture. Light mode reads cleaner on every feed, keeps cutout stickers legible, and makes flat-vector art look intentional. Set the composition/scene background to white first, before placing anything. **This is the default, not a law** — a director who asks for a dark or photographic stage gets one, but it changes two things mechanically: the stickers need their white die-cut rim stripped, and the caption treatment has to be re-measured. Both are documented in **"Stickers on a DARK or photographic stage"** below.
|
|
2899
3086
|
- **Kinetic captions.** Narration is always captioned word-by-word (`vidfarm captions generate ./work --style word-pop`). Because the stage is white, **override the preset's dark-canvas colors to dark ink on light**:
|
|
2900
3087
|
```
|
|
2901
3088
|
vidfarm captions generate ./work --style word-pop \
|
|
2902
3089
|
--color "#111111" --active-color "#7C3AED" --background-style plain --max-words 4
|
|
2903
3090
|
```
|
|
2904
3091
|
One accent color for the active word, everything else near-black. No outline/stroke, no drop shadow, no pill — those exist to survive busy footage and just add noise on white.
|
|
3092
|
+
|
|
3093
|
+
**Those hexes are the answer for a white stage, not the answer.** They are one instance of a general rule: **caption color, active-word color and plate are chosen by MEASURING the background behind the caption band, never by taste or habit.** On a near-black stage the same flags ship a bright plate the design never asked for and an active word nobody can read. The measurement procedure and the three treatments live in `regimes/short-form.QA_REGIME.md` → "Caption styling is measured off the background" — read it before you copy the line above onto anything that isn't white.
|
|
2905
3094
|
- **Female TTS narration.** Default to a warm, friendly **female** voice and say which one you picked: local-first `vidfarm tts "<script>" --voice coral` (OpenAI — `nova` if the script wants more energy, `sage` for calmer), `--voice Kore` or `Leda` on Gemini, or `vidfarm voices` → `vidfarm tts --cloud --voice <voice_id>` on ElevenLabs. Tell the director they can swap it in one flag.
|
|
2906
3095
|
- **Clean and simple wins.** One idea on screen at a time. Two or three cutouts per beat, not eight. Generous white space, one accent color, one font. When in doubt, remove an element rather than add one.
|
|
2907
3096
|
|
|
@@ -2983,6 +3172,48 @@ vidfarm remove-greenscreen ./mascot.mp4 --gif --gif-fps 12 --gif-width 480 # AN
|
|
|
2983
3172
|
|
|
2984
3173
|
GIF alpha is **1-bit** — a pixel is fully opaque or fully gone, so antialiased edges go hard and semi-transparent shadows/glows disappear (`--gif-alpha <0..255>` moves where that line falls). That's the format, not the key. **For anything going onto a composition, prefer PNG/WebP (still) or transparent WebM (clip);** reach for GIF only when the destination demands it.
|
|
2985
3174
|
|
|
3175
|
+
### Stickers on a DARK or photographic stage — strip the white die-cut rim
|
|
3176
|
+
|
|
3177
|
+
The house style above puts stickers on a **white** stage, and on white the thing this section is about is invisible. The moment the stage goes dark, photographic, or coloured, every sticker arrives wearing a **white die-cut rim** — a 4–12px light halo tracing its silhouette — and that halo is the single most obvious "a bot made this" artefact in the frame. The art stops reading as an element in the scene and starts reading as a cutout pasted on top of it.
|
|
3178
|
+
|
|
3179
|
+
**Why the rim is there:** it's a PRINT convention. Real die-cut vinyl needs a white border so the blade has something to cut along, so sticker art is drawn with one, so generators reproduce it. It has no purpose whatsoever in a video composition. **This is a different failure from the `⚠ N% hollow` flag** `sticker-pack` prints — hollow means outline-only art whose interior got keyed away (fix it in the prompt, see above); the rim is extra art that was drawn on purpose and has to be removed after the key.
|
|
3180
|
+
|
|
3181
|
+
**The fix:** delete exactly the band of light pixels **connected to the transparent edge**, by morphological reconstruction inward from the boundary. Interior whites — an eyeball's sclera, a screen highlight, a paper label — are enclosed by linework, so they are not connected to the edge and survive untouched. Local, free, `numpy` + `scipy` + `PIL`:
|
|
3182
|
+
|
|
3183
|
+
```python
|
|
3184
|
+
def strip_rim(img, light=188):
|
|
3185
|
+
a = np.array(img.convert("RGBA")).astype(np.int16)
|
|
3186
|
+
solid = a[..., 3] > 128
|
|
3187
|
+
is_light = (a[..., :3].mean(axis=2) > light) & solid
|
|
3188
|
+
seed = is_light & ndimage.binary_dilation(~solid, iterations=3) # light AND touching transparency
|
|
3189
|
+
if seed.any():
|
|
3190
|
+
rim = ndimage.binary_propagation(seed, mask=is_light) # flood through light only
|
|
3191
|
+
a[..., 3][rim] = 0
|
|
3192
|
+
return Image.fromarray(a.astype(np.uint8), "RGBA")
|
|
3193
|
+
```
|
|
3194
|
+
|
|
3195
|
+
**Test LIGHTNESS, not per-channel whiteness — this is the gotcha that makes the whole thing non-obvious.** The rim is **not white**. It is white **contaminated with the chroma plate**, because the plate fringes into it during keying. Measured on a magenta-plate pack, the outermost solid ring averaged **R≈250, G≈205, B≈250** — the green channel is nowhere near white. A per-channel test like `(rgb > 224).all(axis=2)` therefore misses the rim on every sticker whose edge is even slightly anti-aliased: in testing it stripped **1 of 4** stickers, and because the one it did strip looked *different* from its three siblings, the result read as broken art rather than as a bad threshold. `rgb.mean(axis=2) > ~188` catches all four.
|
|
3196
|
+
|
|
3197
|
+
**Then clean up what stripping leaves behind.** Removing the rim produces two artefacts, and both read to a viewer as "the sticker didn't mask properly":
|
|
3198
|
+
|
|
3199
|
+
1. **A dotted halo** — surviving specks along the old rim edge. Measured **213** and **164** stray connected components of 3–20px each on two different stickers of the same pack.
|
|
3200
|
+
2. **A bright blob** — a large uniform light region the art *enclosed* is no longer visually held in by the rim. Measured at 7,983px (a ring's centre) and 9,582px (a stamp's paper plate).
|
|
3201
|
+
|
|
3202
|
+
```python
|
|
3203
|
+
def clean(img, speck=0.008, blob=0.015, light=200):
|
|
3204
|
+
# 1. drop connected components smaller than `speck` of the largest
|
|
3205
|
+
# 2. for each enclosed light region larger than `blob` of the sticker area:
|
|
3206
|
+
# holes = binary_fill_holes(m) & ~m
|
|
3207
|
+
# if holes.sum() < m.sum() * 0.02: # solid fill, no detail -> it is background
|
|
3208
|
+
# set alpha 0 there
|
|
3209
|
+
```
|
|
3210
|
+
|
|
3211
|
+
**The discriminator is worth remembering on its own: an enclosed light region is only background if it has NO internal detail, and its holes are the giveaway.** An eyeball's sclera is riddled with drawn veins and a pupil → many holes → keep it. A ring's centre is a flat fill → no holes → punch it transparent. Area alone gets this wrong in both directions.
|
|
3212
|
+
|
|
3213
|
+
**When you recolour a pack to a palette** (a duotone or luminance ramp onto a brand accent), **cap the top of the ramp** — e.g. `accent + 0.66·(white − accent)` — so interior whites land as a light tint instead of glaring pure white against a flat two-colour design. Uncapped, every kept interior white becomes the brightest pixel in the frame, which undoes the point of the ramp.
|
|
3214
|
+
|
|
3215
|
+
> **Known gap:** none of this is in the CLI. `vidfarm sticker-pack` / `vidfarm cutout` have no `--strip-rim` (or equivalent) flag today, so on a dark stage you run the two passes above yourself as a local post-step. A `--strip-rim` flag on both commands, defaulting off, is the right long-term home for it.
|
|
3216
|
+
|
|
2986
3217
|
### The guided sequence (prompt harness)
|
|
2987
3218
|
|
|
2988
3219
|
**Step 0 — Decide the cast of stickers.** With the director, list every element the explainer needs as its own cutout: the hero subject, each labelled prop, each icon/arrow/emoji, any mascot, any full-frame backdrop. Each becomes one transparent PNG. Stickers are reusable — generate once, reuse across scenes. **If the cast is more than two or three items, make it a PACK** (one sheet, split locally — see "A sticker pack" above) rather than N separate `cutout` calls.
|
|
@@ -3017,7 +3248,7 @@ GIF alpha is **1-bit** — a pixel is fully opaque or fully gone, so antialiased
|
|
|
3017
3248
|
|
|
3018
3249
|
**Both are image-only.** A moving subject has no single bounding box — matte a video clip with `vidfarm remove-background <video>` or key a flat backdrop with `vidfarm remove-greenscreen <video>` (→ transparent WebM/mov).
|
|
3019
3250
|
|
|
3020
|
-
**Step 2 — Show the director each cutout, get corrections.** Cutouts are cheap to regenerate. Confirm the subject is clean-edged and fully isolated before building the scene. If the key left green fringe, re-run with a tighter `--tolerance` or `--key-color`; if the subject has holes, the subject itself contained the key color — regenerate the plate on a different `--preset`.
|
|
3251
|
+
**Step 2 — Show the director each cutout, get corrections.** Cutouts are cheap to regenerate. Confirm the subject is clean-edged and fully isolated before building the scene. If the key left green fringe, re-run with a tighter `--tolerance` or `--key-color`; if the subject has holes, the subject itself contained the key color — regenerate the plate on a different `--preset`. **If the stage isn't white, strip the white die-cut rim here**, before anything is staged — see "Stickers on a DARK or photographic stage" above.
|
|
3021
3252
|
|
|
3022
3253
|
**Step 3 — Stage them on the composition.** Fork/seed a working composition (`vidfarm pull` or `vidfarm serve`), **set the stage to a white light-mode background first** (house style), then drop each cutout as an **image layer**, sized and positioned deliberately:
|
|
3023
3254
|
```
|
package/SKILL.md
CHANGED
|
@@ -114,10 +114,11 @@ For composition *authoring* craft (motion, keyframes, scene design), Vidfarm shi
|
|
|
114
114
|
- Never build composition HTML by string concatenation — parse, edit, re-serialize the DOM.
|
|
115
115
|
- Render only through `POST /api/v1/compositions/:forkId/render`; never call the renderer directly.
|
|
116
116
|
- Submissions are **not idempotent** — every render/primitive POST charges again. Check status before retrying.
|
|
117
|
-
- **No HTML slop.** Compositions are HTML, but a video is not a web page:
|
|
117
|
+
- **No HTML slop.** Compositions are HTML, but a video is not a web page. The test: *could you have made this element with the tools inside TikTok's own editor* (font, color, stroke, shadow, tight text box, rotation, animation presets, stickers, emoji, drawn marks)? If you reached past that — a padded capsule, a border, a gradient fill, a blur panel, a card — cut it. So: no CTA "buttons", no benefit chip/badge rows, **no single pill around a static stat or label** (`( 10 hrs / week )`, `( STEP 2 )` — the only legitimate capsule is the active-word `spotlight`/`karaoke` highlight), no frosted/bordered cards holding a headline + URL, no gradient text, feature grids, or bullet lists. Nothing in a video is clickable. Say it as timed text on the footage; emphasize with size, weight, ALL-CAPS, an accent color, or a drawn circle. Arrows, scribble/underline marks, italics, color pops, emoji, cut-out stickers, and mock social UI are fine.
|
|
118
118
|
- **Structure before polish — the four charges, written before the timeline.** 🪝 **Hook**: the first line is a complete clause naming a *situation*, not a label; on screen at `start:0` (chunk 1 is read before any audio). 🔄 **Loop**: one open question by 0:10, stated on screen, **closing inside this video**, with an answer the viewer can't guess. 😍 **Payoff**: shown, not summarized, before the final beat. 🎣 **Bait**: one ask, final beat + post caption. Banned openers: throat-clearing, a logo, a title card, a fade from black. Full harness: `references/hooks-and-virality.md`; checkable form: `vidfarm regime show hooks`.
|
|
119
119
|
- **The first frame is the thumbnail.** Frame 0 is one frame of ~30 in the first second, but it's the poster every feed and share link freezes on — so it's seen by more people than the video is. Never open on black, an empty frame, or a fade-up: a real visual at `start:0`, the hook words already up, and no *entrance* transition on the first clip (junction transitions between later clips are fine). Check it with `vidfarm stills <dir> --at 0`.
|
|
120
120
|
- **On devcli, `vidfarm qa <dir>` before every render.** Free, instant, local-only blocklist for the slop above + the first frame + the font regime. Feedback, not a gate (exits 0, never automatic). No REST/web equivalent.
|
|
121
|
+
- **Review the finished video as a WHOLE, and never by one frame.** Agents build scene by scene, so every scene passes alone while the video drifts as a sequence — shifting margins, three type sizes, a wandering accent colour, N identically-long beats, a jarring join, a dead band under top-anchored content. Tile ~12 stills into one contact sheet — one command, `vidfarm stills <dir> --sheet` — and **read it as an image**: balance, spacing, one type scale, one palette, deliberate pacing, clean joins — then fix the *system*, not the one odd scene. Compare frames from two different scenes (a frozen render passes duration, frame-count and audio-hash checks), verify audio by measurement (~12–15 dB speech-over-bed, peak <0 dBFS) rather than "sounds fine", and report what you measured separately from what you judged. Your own "verified, looks good" is the least reliable signal here — it was wrong on every video of a 32-video batch. Method: `references/reviewing-renders.md`.
|
|
121
122
|
- **Ask about deduplication before you publish or bulk-produce.** "Is this going out more than once — several accounts, another platform, a re-post later? How many copies?" Ask *before* the render or the batch, not after: dedupe is a post-render ffmpeg pass, so answering early keeps it at **render once → dedupe N** instead of paying for a second render per slot. Then post each variant to a **different** account — two accounts posting the same variant defeats the point.
|
|
122
123
|
- **Ask one-time vs bulk before you build.** Volume = **scripting mode**: a pinned base fork, a loop varying ONE thing, and a **`QA_REGIME.md`** — the director's own written standard, because a fifty-video loop has no human watching every frame. `vidfarm regime init short-form --out ./work/QA_REGIME.md` (bases: `short-form`, `hooks`, `ugc-testimonial`, `explainer`, `product-demo`), then `vidfarm qa ./work --regime <name|path>` — stackable, any user file valid, auto-discovered from the work dir. Its `checks:` are machine-settled; its `- [ ]` items come back for **you** to answer honestly.
|
|
123
124
|
- **Caption regime is mandatory**: an imported display font (Montserrat default / TikTok Sans), weight 700–900, ~36–64px on a 1080-wide frame, inside the 8%–85% safe zone, and exactly one of four backgrounds — `outline`, `plain`, an active-word `spotlight`/`karaoke` pill, or a tight-hugging `highlight-solid` band (radius ≤8px, no border/shadow/gradient/blur).
|