@officexapp/vidfarm-devcli 0.21.28 → 0.21.29
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 +26 -0
- package/.agents/skills/vidfarm/SKILL.md +53 -2
- package/.agents/skills/vidfarm/recipes/bulk-scripting-with-a-regime.md +65 -0
- package/.agents/skills/vidfarm/recipes/cutout-graphics-for-explainers.md +78 -7
- package/.agents/skills/vidfarm/recipes/local-edit-render-approve.md +2 -2
- package/.agents/skills/vidfarm/recipes/retheme-template.md +1 -1
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +64 -5
- package/.agents/skills/vidfarm/references/editor-workflows.md +94 -1
- package/.agents/skills/vidfarm/references/hooks-and-virality.md +237 -0
- package/.agents/skills/vidfarm/references/onboarding.md +1 -1
- package/.agents/skills/vidfarm/regimes/README.md +77 -0
- package/.agents/skills/vidfarm/regimes/explainer.QA_REGIME.md +82 -0
- package/.agents/skills/vidfarm/regimes/hooks.QA_REGIME.md +117 -0
- package/.agents/skills/vidfarm/regimes/product-demo.QA_REGIME.md +92 -0
- package/.agents/skills/vidfarm/regimes/short-form.QA_REGIME.md +163 -0
- package/.agents/skills/vidfarm/regimes/ugc-testimonial.QA_REGIME.md +82 -0
- package/SKILL.director.md +597 -19
- package/SKILL.md +18 -2
- package/demo/dist/app.js +103 -103
- package/dist/src/cli.js +925 -18
- package/dist/src/devcli/handoff.js +162 -0
- package/dist/src/devcli/interaction-mode.js +154 -0
- package/dist/src/devcli/qa-check.js +173 -0
- package/dist/src/devcli/qa-regime.js +396 -0
- package/dist/src/devcli/sticker-pack.js +396 -0
- package/dist/src/devcli/storyboard.js +243 -0
- package/package.json +6 -1
|
@@ -370,13 +370,79 @@ A clip/image whose **native aspect differs from the canvas** — a 16:9 landscap
|
|
|
370
370
|
- **`object_fit:"cover"`** (default) fills + crops — right for most social footage **when paired with a subject-aware `object_position`**.
|
|
371
371
|
- **`object_position`** aims WHERE the cover-crop lands: a keyword (`left`, `right`, `top`, `bottom`, `"top left"`, …) or a **percentage pair** (`"30% 50%"` = 30% from the left, 50% down). Landscape clip with the subject on the left → `"left"`/`"25% 50%"`; tall screenshot with key text up top → `"top"`.
|
|
372
372
|
- **`object_fit:"contain"`** shows the WHOLE media with black letterbox/pillarbox bars — use ONLY when nothing may be cropped (an infographic, a whole screenshot, a chart, a logo); a 16:9 source full-canvas on a 9:16 frame reads unfinished in `contain`.
|
|
373
|
-
- **Blurred
|
|
373
|
+
- **Blurred plate** — the default answer for landscape footage on a vertical canvas. Full section below; reach for it instead of `contain` whenever the whole frame matters.
|
|
374
374
|
- Never `object_fit:"fill"` (stretch/distort) unless intentional. You usually know a clip's aspect from how you sourced it (a hunted raw's `--aspect`, a generated clip's `--aspect-ratio`); if you truly need exact dims, `vidfarm probe` / `GET /videos/probe`. When you REPLACE a full-canvas scene, keep it full canvas + `cover` + a focal `object_position` rather than shrinking; when you GENERATE, request the canvas aspect so no crop is needed.
|
|
375
375
|
|
|
376
376
|
Two surfaces:
|
|
377
377
|
- **Editor web/serve copilot** — `editor_action` `action_type=set_layer_media` with `object_fit` and `object_position` (both also seedable on `add_layer`/`generate_layer`). `editor_context.layers[]` reports each clip's current `object_fit`/`object_position` so you can read before re-cropping.
|
|
378
378
|
- **Desktop agents (devcli)** — `vidfarm place ./work --src <url> --object-fit cover --object-position "25% 50%"` (also on `--replace <layer_key>`).
|
|
379
379
|
|
|
380
|
+
### The blurred plate — landscape footage, fullscreen, on a vertical canvas
|
|
381
|
+
|
|
382
|
+
**This is the house treatment for real filmed footage in a fullscreen explainer.** The director hands you 16:9 (or 4:3, or 960×540) footage and wants a 9:16 video where the picture reads *fullscreen* — edge to edge, no bars, no dead space — but where nothing important gets cropped away. Neither default fit gets you there: `cover` throws away the left and right thirds of a wide shot (the subject ends up half out of frame), and `contain` leaves hard black letterbox bars that read as "unfinished export" the instant it hits a feed.
|
|
383
|
+
|
|
384
|
+
The fix is the **blurred plate**: the *same* clip twice, one behind the other.
|
|
385
|
+
|
|
386
|
+
1. **Backdrop** — a duplicate of the clip, full canvas, `cover`, so it's blown up ~3.5× and fills the whole 1080×1920. Heavy **gaussian blur** (`sigma ≈ 60` at 1080 wide), plus a **fade to dark** (brightness ≈ −0.14, saturation ≈ 0.9) so it recedes and never competes with type. Because it's the same frame at the same timecode, the colors, motion, and lighting are continuous with the sharp copy — the eye reads one deep image, not a picture pasted on wallpaper.
|
|
387
|
+
2. **Hero band** — the sharp copy, centered, at (or slightly above) native aspect. Optionally **zoom it ~1.2–1.4×** past canvas width so the sharp band is taller and the blur does less work; the small side-crop that costs you is almost always cheaper than the vertical dead space it buys back.
|
|
388
|
+
3. **Feathered seam** — fade the hero band's top and bottom edges out over ~100px instead of butting them against the backdrop. **This is the step that makes or breaks it.** A hard edge is just a letterbox with a fancy background; a feathered edge makes the sharp band read as the in-focus plane of a single continuous shot, like shallow depth of field.
|
|
389
|
+
|
|
390
|
+
The result: a fullscreen vertical frame, no bars, the entire landscape composition intact, and generous dark space at top and bottom for a header bar and captions. Reference render: `drafts/real-filming-demos/videos/fruitpoint-explainer/renders/video.mp4`.
|
|
391
|
+
|
|
392
|
+
**Bake it once with ffmpeg (recommended).** One 1080×1920 file per clip, produced locally and free, that then behaves like any ordinary full-canvas layer — no dual-track bookkeeping, no double video decode at render, and it works identically in the Trackpad Editor, `vidfarm serve`, and cloud render. Keep the baked plates beside the originals (e.g. `assets/full/` next to `assets/clips/`) so you can re-bake with different numbers later.
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
# landscape clip → 1080x1920 blurred plate. Tune: sigma (blur), brightness (fade),
|
|
396
|
+
# 1404 (= 1080 * 1.3 hero zoom; use 1080 for native), 110 (feather height in px).
|
|
397
|
+
ffmpeg -y -i clip.mp4 -filter_complex "\
|
|
398
|
+
[0:v]split=2[bg][fg];\
|
|
399
|
+
[bg]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=60,eq=brightness=-0.14:saturation=0.90[bgb];\
|
|
400
|
+
[fg]scale=1404:-2,crop=1080:in_h:(in_w-1080)/2:0,format=rgba,geq=r='r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='255*min(1,min(Y,H-1-Y)/110)'[fgs];\
|
|
401
|
+
[bgb][fgs]overlay=(W-w)/2:(H-h)/2:format=auto,format=yuv420p[v]" \
|
|
402
|
+
-map "[v]" -map 0:a? -c:v libx264 -crf 18 -preset veryfast -c:a copy clip-plate.mp4
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
For a **still image** the same shape applies — drop `split`/`overlay` timing concerns and swap `-i clip.mp4` for the image with `-loop 1 -t <seconds>`, or just do it in HTML (below), since a still costs nothing to duplicate.
|
|
406
|
+
|
|
407
|
+
For **16:9 canvas fed portrait footage** (a phone clip on a wide frame), it's the identical trick rotated: blurred `cover` backdrop, sharp `contain`-width hero band, feathered left/right edges — swap the feather expression to `min(X,W-1-X)`.
|
|
408
|
+
|
|
409
|
+
**Or do it in the composition HTML** when you're hand-authoring and want the sharp band to animate independently (a slow push-in on the hero while the backdrop sits still):
|
|
410
|
+
|
|
411
|
+
```html
|
|
412
|
+
<div class="plate">
|
|
413
|
+
<video class="clip backdrop" src="assets/clip.mp4" muted playsinline data-start="0" data-duration="4"></video>
|
|
414
|
+
<div class="plate-scrim"></div>
|
|
415
|
+
<video class="clip hero" src="assets/clip.mp4" muted playsinline data-start="0" data-duration="4"></video>
|
|
416
|
+
</div>
|
|
417
|
+
<style>
|
|
418
|
+
.plate { position: absolute; inset: 0; overflow: hidden; }
|
|
419
|
+
.backdrop { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
|
|
420
|
+
filter: blur(60px) brightness(0.86) saturate(0.9); transform: scale(1.1); }
|
|
421
|
+
.plate-scrim { position: absolute; inset: 0; background: rgba(8,14,6,.18); }
|
|
422
|
+
.hero { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%) scale(1.3);
|
|
423
|
+
width: 100%; height: auto; object-fit: contain;
|
|
424
|
+
-webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 100px,
|
|
425
|
+
#000 calc(100% - 100px), transparent 100%);
|
|
426
|
+
mask-image: linear-gradient(to bottom, transparent 0, #000 100px,
|
|
427
|
+
#000 calc(100% - 100px), transparent 100%); }
|
|
428
|
+
</style>
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Both `<video>` elements carry `class="clip"` with the **same `src`, `data-start`, and `data-duration`**, so framework-owned playback keeps them frame-locked and the render stays seek-safe. `transform: scale(1.1)` on the backdrop hides the blur's soft outer edge (a gaussian blur samples past the element bounds and leaves a pale rim otherwise). Mute the backdrop copy — never both.
|
|
432
|
+
|
|
433
|
+
**Numbers that work, and what to move:**
|
|
434
|
+
|
|
435
|
+
| Knob | Default | Move it when |
|
|
436
|
+
|---|---|---|
|
|
437
|
+
| Blur `sigma` / `blur()` | `60` (at 1080 wide) | Under ~40 the backdrop stays legible and fights the hero; over ~90 it flattens to mush. Scale proportionally on other canvas widths. |
|
|
438
|
+
| Backdrop fade | `brightness −0.14`, `saturation 0.90` | Push darker (−0.20) only if captions sit **on** the blur; a scrim under the type is usually the better fix. Too dark reads as a black bar with texture — the exact thing you were avoiding. |
|
|
439
|
+
| Hero zoom | `1.3×` | `1.0` when the framing is tight and the sides carry the subject; up to `1.4` for loose wide shots where more picture beats more edge. |
|
|
440
|
+
| Feather | `~100px` | Bigger (150px) on soft/organic footage; smaller (60px) on graphic footage with strong horizontals that a long fade turns to smear. |
|
|
441
|
+
|
|
442
|
+
**Do not** vignette the backdrop, don't hard-edge the seam, don't run the backdrop unblurred-but-dark (you get a visible duplicate ghost), and don't reach for a solid color or a gradient behind the band — a flat backdrop is what makes a repurposed landscape clip look repurposed.
|
|
443
|
+
|
|
444
|
+
**Editor-surface caveat:** layer-level blur is not an `editor_action` property, so the two-layer version can't be assembled from the editor alone — **bake the plate with ffmpeg and drop it in as one full-canvas `cover` layer**. That's the path for `set_layer_media` / `vidfarm place` work, and it's why the pre-bake is the recommended default rather than the HTML variant.
|
|
445
|
+
|
|
380
446
|
### The opening frame is the post's thumbnail
|
|
381
447
|
|
|
382
448
|
**The composition's first frame (t=0) is the still that represents the whole video before anyone presses play** — it's the poster on the approved-post share page, the `/discover` card, the autoplay-off feed preview, and the file/scrubber thumbnail. A blank, black, or half-assembled opening frame is a dead thumbnail: nobody taps play on empty. Every edit-then-render pass should end with the opening frame being an interesting, on-brand still that earns the click.
|
|
@@ -405,6 +471,27 @@ Three surfaces:
|
|
|
405
471
|
- **Editor UI (human)** — the Inspector's **Animate (Ken Burns)** dropdown on any selected image.
|
|
406
472
|
- **Desktop agents (devcli)** — `vidfarm place ./work --src <img> --kind image --ken-burns zoom-in [--ken-burns-intensity 0.3]`, or fused with generation: `vidfarm generate image --prompt "…" --place ./work --ken-burns pan-left`.
|
|
407
473
|
|
|
474
|
+
### The first frame is the thumbnail — compose t=0 on purpose
|
|
475
|
+
|
|
476
|
+
**Frame 0 is one frame of ~30 in the first second, and it does more work than the other 29 combined.** Every feed card, share link, embed, DM preview, and paused player freezes on it, and platforms use it as the default poster unless a cover is picked by hand. So it is seen by everyone who scrolls past — including the majority who never press play. Treat it as a still ad for the video, not as "the moment before the video starts."
|
|
477
|
+
|
|
478
|
+
**The failure mode is silent.** A timeline that reads perfectly in the editor (where you scrub to an interesting moment) can open on black, because:
|
|
479
|
+
|
|
480
|
+
- the opening clip starts at `0.4s` instead of `0`, leaving a black gap at the head;
|
|
481
|
+
- the first clip carries a `fade-black` / `fade-white` / `flash` **entrance** — the intro fades *up from* a flat frame, so the poster is a solid black or white rectangle;
|
|
482
|
+
- the hook line is timed to the first spoken word (`start: 0.6`), so the poster frame is footage with no promise on it;
|
|
483
|
+
- a decomposed fork inherited the source's slate/countdown/blank head.
|
|
484
|
+
|
|
485
|
+
**Rules for every production:**
|
|
486
|
+
|
|
487
|
+
- **Something real is on screen at `start:0`.** No gap, no black leader. `vidfarm retime <dir> --layer <key> --start 0` pulls the opening clip to the head.
|
|
488
|
+
- **No entrance transition on the FIRST clip.** `vidfarm transitions set <dir> --layer <key> --in none`. Junction transitions between later clips are unaffected — this is only about the opening. (`vidfarm transitions apply --intro fade-black` deliberately opens from black; only use it when the piece is not being posted to a feed.)
|
|
489
|
+
- **The hook words are already up at t=0** unless you're deliberately opening on a clean face/product shot. The strongest short-form thumbnails state the promise in text.
|
|
490
|
+
- **Nothing is mid-animation.** A zoom/slide/blur entrance means the poster catches the frame offset, scaled, or blurred. Author the opening as its resting state and animate *after* frame 0.
|
|
491
|
+
- **Verify the actual pixels, never the intent:** `vidfarm stills ./work --at 0` renders exactly that frame to a PNG — look at it before you render. `vidfarm qa` flags the structural causes (`thumbnail-blank-open`, `thumbnail-fade-in`, `thumbnail-no-hook-text`), but the still is the ground truth.
|
|
492
|
+
|
|
493
|
+
Web copilot: same standard, applied by hand — check the opening layer's `start`, clear any entrance transition on it, and start the hook caption at 0.
|
|
494
|
+
|
|
408
495
|
### Social-native visual standard — no HTML slop, ever
|
|
409
496
|
|
|
410
497
|
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.**
|
|
@@ -433,6 +520,8 @@ Compositions are authored in HTML, so the single most common way an AI-edited vi
|
|
|
433
520
|
|
|
434
521
|
### TikTok-native caption standard (position + font + background) — always adhere
|
|
435
522
|
|
|
523
|
+
> Captions are also the *delivery system* for three of the four charges: the hook is read before any audio, the loop has to stay on screen, and the payoff number needs its own card. What the words should SAY is in `references/hooks-and-virality.md`; this section is how they must LOOK.
|
|
524
|
+
|
|
436
525
|
Short-form is watched on a phone, and the phone's UI eats the frame's edges. **Never pin on-screen text to the extreme top or bottom** — the top ~8% sits under the status bar / "Following · For You" tabs and the bottom ~15% under the username, caption text, music marquee, and action rail. Text there is literally clipped and reads as amateur. Three rules, applied to **every** caption/title/overlay you place or inherit:
|
|
437
526
|
|
|
438
527
|
- **Position → readable safe zone.** Keep the text box's vertical extent inside **~8%–85%** of canvas height (9:16). A lower-third subtitle lives around **y≈70%** (the `captions generate` default); a "me when…" / "POV:" meme line sits near the **top of the safe zone (y≈8%), not y:0**. Preserve the original top-vs-bottom anchoring — just pull it off the dead-zone edge, don't recenter everything. Also keep wide captions clear of the **right ~12%** action rail (a centered box at `x:10 width:80` is safe).
|
|
@@ -506,6 +595,10 @@ Beyond the Ken Burns / transition / animated-caption presets, the copilot can ha
|
|
|
506
595
|
|
|
507
596
|
**Make the sticker (`vidfarm cutout`).** Local, free, ffmpeg-only, image-only. `vidfarm cutout --generate "<subject>"` AI-generates on a matching plate then keys+trims (only the generation is billed); `vidfarm cutout <file|url>` keys+trims a graphic you already have for $0. Because the output is trimmed tight, its on-frame **width % maps directly to how big the subject reads** — no invisible padding skewing the layout. Knobs: `--preset`/`--key-color` (match the plate), `--pad <px>` (transparent breathing room), `--alpha-threshold <0..255>` (trim faint haloed edges), `--output-format webp`, `--no-trim`. A *moving* subject has no single bounding box → key it as a clip with `vidfarm remove-greenscreen <video>` (→ transparent WebM) instead.
|
|
508
597
|
|
|
598
|
+
**Make a whole PACK at once (`vidfarm sticker-pack`).** When the scene needs a *set* — props, icons, a cast, plus any backdrops — don't run `cutout` N times. Generate ONE greenscreen sheet holding every item and split it locally: `vidfarm sticker-pack --generate "<theme>" --items "a,b,c" --out-dir ./stickers` (one billed image job for the set), or `vidfarm sticker-pack ./sheet.png` for $0 on a sheet you already have. It keys the plate once, **segments the sheet's alpha channel to find each item automatically** (no hand-measured crop rects), trims each to its true box, and writes `stickers.json`. `--dry-run` to inspect the boxes; `--gap` to fix items that merged (lower) or split (raise). **Item size is unbounded** — a full-frame landscape/skyline/backdrop is as legitimate a sticker as a small icon, and belongs in the same sheet so it shares the cast's style. Recipe: `recipes/cutout-graphics-for-explainers.md` → "A sticker pack".
|
|
599
|
+
|
|
600
|
+
**Sticker formats.** Still sticker → **PNG** (or `--output-format webp` for smaller files). Sticker whose own artwork moves → **transparent WebM** via `vidfarm remove-greenscreen <video>` (ProRes `.mov` is the local fallback; `--cloud` always returns WebM). Sticker destined for a **GIF-only surface** (chat, forum, Notion, a shareable pack) → `--output-format gif` on `sticker-pack`, or `vidfarm remove-greenscreen <video> --gif` for an animated one; GIF alpha is 1-bit, so edges harden and soft shadows vanish — never the choice for compositing on a timeline.
|
|
601
|
+
|
|
509
602
|
**Place it.** Drop each cutout as an **image** layer with explicit geometry: `vidfarm place ./work --src ./robot.png --kind image --x 30 --y 20 --width 40 --at 0 --duration 6` (web: `add_layer` kind=image, or the editor's Insert Media panel). Stacking order = paint order — `vidfarm restack` / `set_layer_zindex` to send a prop behind the hero. Reuse the same PNG across scenes; a cutout is generated once and reused.
|
|
510
603
|
|
|
511
604
|
**Animate it — the explainer move vocabulary.** One layer at a time; all preview AND render identically. Devcli `vidfarm keyframes <dir> --layer <k> --preset <name>`; web `set_layer_keyframes`. Sticker-tuned presets:
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
## The hook & virality harness — apply to EVERY video
|
|
2
|
+
|
|
3
|
+
Most agent-made videos don't fail on polish. They fail on **structure**: no hook worth stopping for, no reason to stay, a payoff that never lands, and no ask at the end. Polish is what an agent is naturally good at, so it over-invests there and ships a beautiful video nobody watches.
|
|
4
|
+
|
|
5
|
+
This is the harness that fixes it. It is not a style — it's the load-bearing anatomy of anything that travels on TikTok/Reels/Shorts, distilled from grading hundreds of hooks against real funnels. **Run it on one-off videos and on batches alike.** It costs no credits, adds no render time, and it is the single largest quality delta available in this product.
|
|
6
|
+
|
|
7
|
+
The checkable form of this document is the bundled `hooks` regime (`vidfarm regime show hooks`); this file is the craft behind it.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The build order (do it in this order, it is the harness)
|
|
12
|
+
|
|
13
|
+
The reason agent videos come out structureless is that the timeline is the fun part, so it gets built first and the words get retrofitted. Invert it:
|
|
14
|
+
|
|
15
|
+
1. **Write the hook line as TEXT, before touching the timeline.** One line. Show it to the director. If it doesn't survive being read aloud with no visuals, no clip will save it.
|
|
16
|
+
2. **Name the open loop and where it closes.** One sentence: *"opens at 0:08 — 'the third one told me why the first two ghosted' — closes at 0:34."* If you can't state the timestamp it closes at, there is no loop.
|
|
17
|
+
3. **Name the payoff.** What is on screen at that moment, and why does it satisfy the promise?
|
|
18
|
+
4. **Write the bait.** The final-beat ask, in the video and in the post caption.
|
|
19
|
+
5. **Only now build the timeline** — and place the hook text at `start:0` so it's on screen at frame 0 (which is also the thumbnail).
|
|
20
|
+
6. **Verify the frame and the structure:** `vidfarm stills ./work --at 0` (look at the actual poster) and `vidfarm qa ./work --regime hooks` (machine checks + the judgment checklist).
|
|
21
|
+
|
|
22
|
+
Steps 1–4 are cheap, reversible, and where the entire outcome is decided. Steps 5–6 are where agents want to start.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Charge 1 — THE HOOK (first 3 seconds)
|
|
27
|
+
|
|
28
|
+
### The three gates
|
|
29
|
+
|
|
30
|
+
A hook clears three gates **in order**, and they fail in order too — each one silently, so the view count never tells you which one killed you:
|
|
31
|
+
|
|
32
|
+
| # | Gate | Question | Where it lives | Failure looks like |
|
|
33
|
+
|---|---|---|---|---|
|
|
34
|
+
| 1 | **Legibility** | Can a stranger *understand* this? | Caption chunk 1 | Never leaves the test batch |
|
|
35
|
+
| 2 | **Tension** | Is there something they don't know? | The withheld final clause | Views and likes, no clicks |
|
|
36
|
+
| 3 | **Relevance** | Is this person a buyer? | Chunks 2–3 and the body | Clicks and opt-ins, no sales |
|
|
37
|
+
|
|
38
|
+
**The order is the whole insight.** Everyone writes for gate 3 first, because relevance is what marketing training optimizes for. But the platform's first test batch is a few hundred essentially random viewers, of whom the buyer is 1–2%. **A hook that only works on a perfectly-targeted viewer never gets a perfectly-targeted viewer** — ~98% of the batch swipes without comprehending, watch time craters, and the video never escapes to reach the people who'd have understood it.
|
|
39
|
+
|
|
40
|
+
### The unit is caption chunk 1, not "the first 3 seconds"
|
|
41
|
+
|
|
42
|
+
Captions render in phrase chunks of ~2–6 words, and the viewer **reads chunk one before hearing any audio**. The real hook unit is **words 1–5 on screen**. This is why the hook text layer starts at 0 and why `hook_words_max` is a machine check.
|
|
43
|
+
|
|
44
|
+
> **Chunk 1 must be legible to everyone and relevant to few.**
|
|
45
|
+
|
|
46
|
+
Three constraints:
|
|
47
|
+
|
|
48
|
+
1. **Complete clause — subject plus verb.** Not a noun phrase, not a bare number, not a label. This one rule fixes most failures, because a clause forces context in.
|
|
49
|
+
2. **No jargon.** A domain term in chunk 1 kills the cold start even inside a valid clause. Jargon belongs in chunk 2+, where it *qualifies* instead of excluding.
|
|
50
|
+
3. **The cold-viewer test.** Would a random person with no interest in the category understand what situation is being described? Not *would they care* — *would they understand*.
|
|
51
|
+
|
|
52
|
+
### Situations, not labels — the cheapest predictor there is
|
|
53
|
+
|
|
54
|
+
The three constraints are checks you run *after* writing. This one tells you whether a hook will survive *before* you write it:
|
|
55
|
+
|
|
56
|
+
> **Situations are cold-legible by construction. Labels never are.**
|
|
57
|
+
|
|
58
|
+
A **situation** is a thing that happened, with a subject and an outcome: *someone deleted my message · she refused my money · they stopped replying · tracking said delivered · his lot is full.* A stranger parses all of it instantly, because humans are built to parse events.
|
|
59
|
+
|
|
60
|
+
A **label** is a concept with a name: *anonymity · distribution · platform risk · seeding · onboarding.* It carries meaning only for someone who already holds the category — exactly the person the cold-start batch does not contain.
|
|
61
|
+
|
|
62
|
+
Across four avatars and 100 graded hooks, **every hook cut for illegibility was built on a label**, and the one cluster that lost nothing was built entirely on situations. If your opening names a concept rather than an event, assume it fails until proven otherwise.
|
|
63
|
+
|
|
64
|
+
| Fragment (fails) | Complete clause (passes) |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `SIX STARTS, ZERO FINISHES` | `I'VE QUIT SIX BUSINESSES` |
|
|
67
|
+
| `FOURTH APP` | `I SHIPPED MY FOURTH APP` |
|
|
68
|
+
| `ANONYMITY` | `I'VE NEVER SHOWN MY FACE` |
|
|
69
|
+
| `$50,000 IN SALES` | `THEY WANTED $50,000 FIRST` |
|
|
70
|
+
| `A CARTOON FROG` | `A CARTOON FROG OUTSELLS ME` |
|
|
71
|
+
| `TWO HOURS A WEEK` | `THIS TAKES TWO HOURS A WEEK` |
|
|
72
|
+
|
|
73
|
+
The tension survives every fix — it just arrives inside a sentence a stranger can parse. Often the legible version is *stronger*, because a stranger can feel it where the fragment only rewarded someone already in the situation.
|
|
74
|
+
|
|
75
|
+
### Banned openers
|
|
76
|
+
|
|
77
|
+
*So I was thinking · There's a reason · Here's the thing · A lot of people · If you're a… · Let me tell you* — any throat-clearing. Also banned as an opening beat: a logo, a title card, a fade from black, a beat of silence, and any sentence whose subject arrives in the second half. **Context is beat 2**, and it goes after the hook, never before it.
|
|
78
|
+
|
|
79
|
+
### Stack three hooks simultaneously
|
|
80
|
+
|
|
81
|
+
Most videos fire only one:
|
|
82
|
+
|
|
83
|
+
1. **Verbal** — the spoken line.
|
|
84
|
+
2. **Text** — a caption already on screen at frame 0, readable before a word is said.
|
|
85
|
+
3. **Visual** — motion, an unusual angle, a face already mid-sentence, hands already working.
|
|
86
|
+
|
|
87
|
+
**Muted autoplay is the default viewing condition, so the text hook does more work than the verbal one.** If the video only works with sound on, it doesn't work.
|
|
88
|
+
|
|
89
|
+
⚠️ **Frame 0 is the hook AND the thumbnail** — the one frame more people see than watch the video. No black open, no fade-up, subject in frame, caption legible. Full rules in `references/editor-workflows.md` ("The first frame is the thumbnail").
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Charge 2 — THE CURIOSITY LOOP (retention)
|
|
94
|
+
|
|
95
|
+
**The charge most videos skip entirely**, and the one that separates 200 views from 30,000. The algorithm is measuring whether people stay, and nothing holds attention like an unresolved question.
|
|
96
|
+
|
|
97
|
+
### Identification vs curiosity — know which you're writing
|
|
98
|
+
|
|
99
|
+
| Type | Triggers | Earns |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| **Identification** | "that's me" · "finally someone said it" | a like, a save, a follow |
|
|
102
|
+
| **Curiosity** | "wait, what?" · "why?" · "I need to know if that's true" | **a click** |
|
|
103
|
+
|
|
104
|
+
Both are legitimate and they are not interchangeable. If the video's job is a click into a funnel, **the loop matters more than the resonance.**
|
|
105
|
+
|
|
106
|
+
### The mechanical fix
|
|
107
|
+
|
|
108
|
+
Most weak hooks state the insight **and its resolution** in one line, so there's nothing left to find out. **Move the payoff out of the line and into the video body** — same insight, same specificity, the last clause just stops answering itself.
|
|
109
|
+
|
|
110
|
+
| Closes (dead) | Opens (loop) |
|
|
111
|
+
|---|---|
|
|
112
|
+
| "I paid three creators. I ended up teaching all three how to hook." | "I paid three creators. The third one told me why the first two ghosted." |
|
|
113
|
+
| "Every customer I have, I'm renting." | "I asked my lead company one question. Their answer told me I don't own a single customer." |
|
|
114
|
+
| "Launch day: three upvotes, two of them mine." | "Fourth launch, three upvotes. The problem was never the product." |
|
|
115
|
+
| "Nobody quits a project that's getting attention." | "Nobody quits a project that's getting attention. So your problem was never discipline." |
|
|
116
|
+
|
|
117
|
+
> **Tension in the first chunk. Withheld answer in the last chunk.**
|
|
118
|
+
|
|
119
|
+
The two constraints pull against each other — the loop withholds at the end, legibility demands the opening earn a read on its own. Solve both. If the line can't carry both, rewrite the line; don't drop one.
|
|
120
|
+
|
|
121
|
+
### ⚠️ The answer must be unguessable
|
|
122
|
+
|
|
123
|
+
A structurally valid loop whose answer the viewer can supply themselves **is not a loop**. The withhold happens on the page; the resolution happens in their head before their thumb moves.
|
|
124
|
+
|
|
125
|
+
This is **the most dangerous failure in this document**, because it passes every mechanical check — complete clause, no jargon, payoff withheld at the end, cold-legible. It looks correct in review and dies in the field. The example that caught it:
|
|
126
|
+
|
|
127
|
+
> *"'TikTok's for kids' is exactly what the Yellow Pages guys said about Google. Ask what happened to them next."*
|
|
128
|
+
|
|
129
|
+
Formally a loop. Useless — everyone already knows what happened to the Yellow Pages. The answer closes instantly and for free.
|
|
130
|
+
|
|
131
|
+
**Test: can the viewer answer your withheld question without watching?** If yes, two ways out:
|
|
132
|
+
|
|
133
|
+
1. **Withhold something only you know** — the other side of a transaction, what the gatekeeper actually checks, what the person who ignored them said. That material is unknowable without watching, and it qualifies naturally.
|
|
134
|
+
2. **Send them to check rather than telling them** — replace the withheld fact with an instruction to go find out: *"You think it's all teenagers on there. Go and look at who's still watching at eleven at night."* The tension is an unresolved *belief*, which they can't close from the sofa.
|
|
135
|
+
|
|
136
|
+
### Loop rules
|
|
137
|
+
|
|
138
|
+
- **Open it by 0:10.** After that you're re-hooking someone who already decided to leave.
|
|
139
|
+
- **Say the loop on screen**, not just out loud — caption it, or run it as a visible timer/counter.
|
|
140
|
+
- **One loop.** Two open questions dilute both.
|
|
141
|
+
- **It opens and closes inside THIS video.** Withholding for forty seconds is retention; deferring the answer to another upload is a broken video — you don't control feed order, and a breakout video is seen alone. "Part two" is not a loop, it's an unpaid debt.
|
|
142
|
+
- **Never bait-and-switch the payoff.** If you promised a number, give the real number.
|
|
143
|
+
- **Don't force it.** When a line lands in one beat and withholding would break it, keep it and *label it an identification hook* — tagged, those become the control group that tells you whether loops actually beat identification for this audience.
|
|
144
|
+
|
|
145
|
+
**Mechanics, ranked:** the withheld number (strongest — a real number is a promise you can keep, and an on-screen timer sustains the loop visually for the whole video) · the withheld part ("one part of this nobody explains, and it decides whether it works for you") · the numbered list ("three reasons, the third is the only one that matters" — tells the viewer exactly how long to stay; deliver all three) · the unresolved visual (a half-cut timeline, an export bar, a finished video not yet played — works with zero words, survives muting) · the contradiction/concession ("X pays better, and I'm still going to explain this" — the concession *is* the loop: *so why bother?*).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Charge 3 — THE PAYOFF (tap the desire)
|
|
150
|
+
|
|
151
|
+
The moment the video pays what the hook promised, landing on **what they actually want** — not on what you're selling.
|
|
152
|
+
|
|
153
|
+
- **Show it, don't summarize it.** The result plays, full frame, ≥5 uninterrupted seconds. The real number appears on screen. Description is not payoff.
|
|
154
|
+
- **The payoff is not the CTA.** No product pitch in this slot. Pitching where the payoff belongs is the most common way a good video converts nothing.
|
|
155
|
+
- **Land it before the last beat.** Payoff → bait → close. A payoff in the final second gets cut off by the loop-around.
|
|
156
|
+
- **Go past the functional layer.** Surface desire ("how do I do this?") gets a mechanism; functional gets a demo; **the emotional layer is usually where the real payoff is** — the honest limit named out loud, the concession that says you're not selling them. For a scam-fatigued or pitch-fatigued audience, *refusing to oversell is the payoff*.
|
|
157
|
+
- **Identity is the deepest layer:** "that's a real skill I now have." When a video can end there, it does.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Charge 4 — THE BAIT (comments are distribution)
|
|
162
|
+
|
|
163
|
+
A video with replies gets shown again; a video with none dies at its first audience regardless of watch time. **Bait is a deliberate beat, not something you hope for.**
|
|
164
|
+
|
|
165
|
+
**One ask, in the final beat, and also in the post caption** — many people reply from the caption without finishing. **Pay off first, then ask**; never make the bait the price of the payoff.
|
|
166
|
+
|
|
167
|
+
**Works:**
|
|
168
|
+
|
|
169
|
+
- ⭐ **The confession invite** — *"Tell me which one you already quit. I've quit two."* Low-stakes, high-volume, and it makes the comment section self-reinforcing.
|
|
170
|
+
- ⭐ **The guess** — *"Guess how long it took me. I say it at the end."* Pairs perfectly with a withheld-number loop: one device drives retention *and* comments.
|
|
171
|
+
- **The named omission** — *"I left one thing out on purpose. Ask me."* Converts what you couldn't fit into the reason to reply.
|
|
172
|
+
- **The soft disagree** — a true concession people want to argue with. Not ragebait if you actually believe it.
|
|
173
|
+
- **The specific question** — concrete always beats "thoughts?".
|
|
174
|
+
|
|
175
|
+
**⚠️ Bait is the highest-compliance-risk beat in the video.** For money/health/appearance topics the obvious bait is the illegal one:
|
|
176
|
+
|
|
177
|
+
- ❌ *"Comment MONEY and I'll send you the link"* — the most scam-coded line available, and a DM funnel.
|
|
178
|
+
- ❌ *"How much do you want to make a month?"* — invites an earnings-claim thread you now own.
|
|
179
|
+
- ❌ *"Follow for part two"* — breaks the standalone rule.
|
|
180
|
+
- ❌ Fake controversy, ragebait, or a deliberate error to farm corrections. Works once, costs the positioning permanently.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Gate 3 in depth — relevance and funnel alignment
|
|
185
|
+
|
|
186
|
+
- **Offer adjacency.** The hook should be the **"before" of the offer's "after."** A hook that fully solves the problem, or creates a desire the offer doesn't resolve, is *worse* than a weak hook: a weak hook underperforms; this one fills the funnel with people who will never buy. Symptom — great opt-in rate, lead→paid under ~5% — and by the time it's visible you've scaled it.
|
|
187
|
+
- **Deliverability.** Aggression isn't the problem; undeliverability is. Bold, curiosity-driven, outcome-led hooks are what work — but a promise the offer can't keep buys front-end conversion with churn. **Point the aggression at what you can demonstrate.** A large promise you cash every time beats a larger one you can't.
|
|
188
|
+
- **Guarantees are proof, not headline.** Leading with the guarantee attracts guarantee-shoppers who churn at the boundary, and draws enforcement on cold organic.
|
|
189
|
+
- **Sell the entry promise, not the offer.** The hook's job is to match **what the next screen actually says**. If the funnel opens on an eligibility question, sell the eligibility curiosity. A hook that pitches the end offer desyncs the sequence.
|
|
190
|
+
- **Move the whole awareness ladder together.** Hook = problem-aware · body = bridge to the solution · landing page/quiz = solution-aware · proof = product-aware. Changing the hook's awareness stage without moving the page it points at is the most common self-inflicted conversion loss — the drop-off reads as "bad hook" when the failure was the handoff. Unaware-stage hooks are a trap on short-form: that structure needs seven beats and you have one second.
|
|
191
|
+
- **TAM comes from identity, not awareness stage.** Widening the stage adds viewers, not buyers. Widening the *identity* — from a narrow role to the broader self-image it sits inside — multiplies the audience while keeping the desire intact.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Anti-patterns (they show up hardest at volume)
|
|
196
|
+
|
|
197
|
+
- **Never point a generator at your grader.** A model writing hooks scored by a model converges on whatever the rubric measures, not on what works — in practice it discovered that appending a proper noun moved "specificity," and produced 21 variants of one sentence with the city and trade swapped. Scores climbed every round; nothing got better. **The rubric is a check on a hook you already believe in, never a ranking function.** Use it to catch defects, not to pick winners. This applies directly to you: do not generate 50 hooks and then rank them with the same reasoning that wrote them.
|
|
198
|
+
- **A different noun is not a different hook.** If two variants could be filmed from one script by changing one word, you have one hook — and a batch of twenty of those teaches nothing. The dominant failure when writing for a category that spans verticals.
|
|
199
|
+
- **Specificity is observed detail, not generated nouns.** `Boston window cleaner` is a generated token; `comment posted 9:14, video finished 9:55` is an observation. One sounds invented, the other sounds witnessed — which is the entire point of the principle.
|
|
200
|
+
- **Obscurity is not qualification.** A hard-to-parse opening doesn't filter for serious buyers, it filters for nobody, because the video never reaches anyone. Qualification is the body's job.
|
|
201
|
+
- **Founder-confession drift.** "I paid… / my ads… / I spent…" is relatable but makes the video about you. A loop makes it about information the viewer wants. Bias toward the latter.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Measure and diagnose by charge
|
|
206
|
+
|
|
207
|
+
**The cluster is the unit, not the hook.** A single hook at low view counts tells you nothing — variance dominates. A cluster is a bet on one buyer insight and one emotional lever; that's what you kill or scale. One attribution slug per cluster (use the render `tracer`), one cluster per account — mixing them stops the algorithm finding the audience.
|
|
208
|
+
|
|
209
|
+
**Judge on the metric that pays rent.** In increasing order of truth: views → completions → profile visits → **funnel/quiz completions per post** → **revenue per 1,000 views**. Comments and views are distribution signals, not leads.
|
|
210
|
+
|
|
211
|
+
| What the numbers say | Weak charge | Fix |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| Barely any views | 🪝 Hook | Rewrite the first 3s. New opening line, new frame 0 |
|
|
214
|
+
| Views, mass exit at 3–8s | 🔄 Loop | No open question. Plant one by 0:10 |
|
|
215
|
+
| Watched to the end, no reaction | 😍 Payoff | Landed on features, not desire — go to the emotional layer |
|
|
216
|
+
| Good retention, no comments | 🎣 Bait | No ask, or the ask was "thoughts?" |
|
|
217
|
+
| Comments, but hostile | 🎣 Bait | Ragebait or an over-claim |
|
|
218
|
+
| Good clicks, no sales | Relevance | Offer adjacency — wrong audience |
|
|
219
|
+
|
|
220
|
+
**Vary one charge at a time.** Same body, new hook. Same hook, new bait. A batch where everything changed teaches you nothing. **Then play the power law:** ~2% of videos produce ~90% of results — when one hits, make ten more of exactly it. Iterate on winners; never fix losers.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Running this harness in Vidfarm
|
|
225
|
+
|
|
226
|
+
| Step | Web copilot | Desktop / devcli |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| Generate hook candidates | `brainstorm/hooks` (the consultation chain — see `references/onboarding.md`) | same, via `vidfarm api` |
|
|
229
|
+
| Check what the source template's hook actually was | `editor_context` → `viral_dna.hook` / `retention` / `payoff` / `emotional_punch` | `.harness/context.json`, `video-context.json` |
|
|
230
|
+
| Place the hook at frame 0 | `add_layer` / `set_captions` with `start:0` | `vidfarm set-text ./work --layer hook --text "…"` |
|
|
231
|
+
| Look at the poster frame | ask the user to scrub to 0 | `vidfarm stills ./work --at 0` |
|
|
232
|
+
| Grade the structure | by hand, against this file | `vidfarm qa ./work --regime hooks` |
|
|
233
|
+
| Bulk hook test | hand off to a local agent | `recipes/bulk-scripting-with-a-regime.md` |
|
|
234
|
+
|
|
235
|
+
**Re-theming a decomposed template?** `viral_dna` already names the source's hook, retention device, and payoff — that structure is *why the template worked*. Rebuild each charge for the new subject; don't drop the loop because the new topic feels self-explanatory. Flattening a template's loop into a product statement is the single most common way a re-theme kills a format.
|
|
236
|
+
|
|
237
|
+
**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).
|
|
@@ -7,7 +7,7 @@ The point of onboarding is to build **durable, reusable context** in My Files, n
|
|
|
7
7
|
1. **Product context** → **`OFFER.md`** (or `OFFER_<NAME>.md` — e.g. `OFFER_ACME_SKINCARE.md` — when the director runs more than one offer). Drive the interview questions with `brainstorm/coldstart` and **recommend the director save their answers into that file**, either locally next to their project or in cloud My Files. This is the durable artifact every later step reads back; `About.md` / `Interview.md` are legacy names — prefer `OFFER*.md` for anything new.
|
|
8
8
|
2. **Awareness level** (Eugene Schwartz — problem-aware, solution-unaware, …) → `awareness-levels.md`. If it's genuinely unknown after thinking it through, note that ads for **every** level should be made and tested. Use `brainstorm/awareness_stages`.
|
|
9
9
|
3. **Persuasive angles** → `persuasive-angles.md`, via `brainstorm/angles`.
|
|
10
|
-
4. **Hooks** → `ad-hooks.md`, via `brainstorm/hooks`.
|
|
10
|
+
4. **Hooks** → `ad-hooks.md`, via `brainstorm/hooks`. **Grade what comes back against `references/hooks-and-virality.md`** — the three gates, situation-vs-label, and the unguessable test — instead of shipping the raw list. And never rank a generated batch with the same reasoning that wrote it; the rubric catches defects, it doesn't pick winners.
|
|
11
11
|
5. **Brand assets & demos** — ask if they have logos/mascots/themes (suggest a `/brand-assets/` folder, e.g. `/brand-assets/logo.png`) or product demos / screen recordings (suggest a `/product-demos/` folder). `browse_files list` / `vidfarm files` first to see what they already uploaded; filenames should be descriptive and every asset worth finding later should get **notes** (`annotate-file` / `browse_files annotate`) so `files --search` works months from now. If they have a recurring character/mascot, set up its `/files/characters/<slug>/` trio now — `<character_id>.json` (e.g. `character_zara.json`) + `character_sprite_card.png` + `character_about.md` (see "Recurring characters are first-class").
|
|
12
12
|
6. **Budget** — ask roughly what they want to spend per video, and map it to the Cost spectrum (free reuse+local render → pennies for cloud render → ~$1 for some AI scenes → $10+ for heavy AI gen). This sets which approach you default to and whether AI **video** generation is on the table (ask permission before using it; image gen is cheap and fine). Budget can also be revisited per editor project.
|
|
13
13
|
7. **Recommend & adapt a template** — pair what you now know about the offer against the decomposed template catalog (`GET /discover/feed?q=<offer>`, read each result's `promotions`/`keywords`/`summary`), recommend the best 3-6, then fork and **modify** the winner to fit their offer. Prefer already-decomposed templates so the director skips the ~$0.10 decompose cost.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# QA_REGIME.md — the per-style quality contract
|
|
2
|
+
|
|
3
|
+
`vidfarm qa`'s built-in rules are **universal**: no HTML slop, the caption font regime, the thumbnail frame. They're the same for every video anyone makes, so they live in code.
|
|
4
|
+
|
|
5
|
+
A **QA_REGIME.md is the opposite**: it's what makes *your* format good — your audience, your hook shape, your banned vocabulary, your pacing, your compliance line. That changes per account, per offer, per campaign, so it can't be hard-coded. It lives next to the work as Markdown you own, edit, and version.
|
|
6
|
+
|
|
7
|
+
**It matters most in scripting mode.** One video gets human eyes on every frame; fifty variants generated in a loop do not. The regime is what the batch is graded against — the thing that keeps variant #37 as good as variant #1.
|
|
8
|
+
|
|
9
|
+
## Using one
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
vidfarm regime list # what ships with the CLI
|
|
13
|
+
vidfarm regime show hooks # read one
|
|
14
|
+
vidfarm regime init short-form --out ./work/QA_REGIME.md # copy it next to your work, then EDIT it
|
|
15
|
+
|
|
16
|
+
vidfarm qa ./work # auto-uses ./work/QA_REGIME.md if present
|
|
17
|
+
vidfarm qa ./work --regime hooks # a built-in by name
|
|
18
|
+
vidfarm qa ./work --regime ./brand/HOUSE_RULES.md # any file, anywhere
|
|
19
|
+
vidfarm qa ./work --regime short-form --regime ./work/QA_REGIME.md # they STACK
|
|
20
|
+
vidfarm qa ./work --json # checks + review items, for a scripted batch
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Regimes compose: a shared house regime plus a per-campaign one is the intended shape. `--no-regime` skips auto-discovery; `VIDFARM_QA_REGIME` sets a default for a whole scripting run.
|
|
24
|
+
|
|
25
|
+
## The format
|
|
26
|
+
|
|
27
|
+
Plain Markdown, with two machine-readable affordances:
|
|
28
|
+
|
|
29
|
+
**1. Optional front matter with a `checks:` block** — the assertions the CLI settles deterministically from the composition, instantly, with no AI and no network:
|
|
30
|
+
|
|
31
|
+
```markdown
|
|
32
|
+
---
|
|
33
|
+
name: my-house-style
|
|
34
|
+
video_type: what this regime is for
|
|
35
|
+
checks:
|
|
36
|
+
duration_sec: 8-34 # also "<=34", ">=8", or "30"
|
|
37
|
+
aspect: 9:16 # "9:16|1:1" to allow several
|
|
38
|
+
first_frame_visual: required
|
|
39
|
+
first_frame_text: required | forbidden
|
|
40
|
+
hook_words_max: 7
|
|
41
|
+
text_by_sec: 1.0
|
|
42
|
+
audio: required | forbidden
|
|
43
|
+
captions: required
|
|
44
|
+
font_regime: required
|
|
45
|
+
safe_zone: required
|
|
46
|
+
scenes: 3-12
|
|
47
|
+
max_scene_sec: 8
|
|
48
|
+
max_text_cards: 3
|
|
49
|
+
max_simultaneous_text: 2
|
|
50
|
+
forbid_text: ["link in bio", "comment below"]
|
|
51
|
+
require_text: []
|
|
52
|
+
---
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Unknown keys are reported and ignored, never silently dropped.
|
|
56
|
+
|
|
57
|
+
**2. Any `- [ ]` checkbox line** in the body becomes a **review item** — a question handed back for the agent or the human to answer. "Is the withheld answer one the viewer can't supply themselves?" is a judgment call; pretending a linter settles it would be a lie.
|
|
58
|
+
|
|
59
|
+
Everything else is prose the agent reads for context. That split is the whole design: the CLI is honest about which half it can enforce, and it never passes a video on the strength of the half it can't.
|
|
60
|
+
|
|
61
|
+
## Writing your own
|
|
62
|
+
|
|
63
|
+
Start from the closest built-in (`vidfarm regime init <name>`), then **delete what doesn't apply and add what makes your format yours**. A regime you didn't edit isn't about your videos.
|
|
64
|
+
|
|
65
|
+
Good regimes tend to have: a **Part 0** naming the viewer in one line (the thing that decides everything else), an **anatomy** section for the beats your format needs, **rules** with the reason attached — a rule whose "why" is missing gets argued away by the next agent that reads it — and a **pre-flight checklist** of `- [ ]` items, which is the part the CLI hands back on every run.
|
|
66
|
+
|
|
67
|
+
Keep the checklist short enough that answering it honestly is cheaper than skipping it.
|
|
68
|
+
|
|
69
|
+
## Built-ins
|
|
70
|
+
|
|
71
|
+
| Name | For |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `short-form` | The general default: the four charges (hook / loop / payoff / bait) + the standalone rule. Start here |
|
|
74
|
+
| `hooks` | Hook-variant batches — chunk-1 legibility, the unguessable test, the anti-patterns that only appear at volume |
|
|
75
|
+
| `ugc-testimonial` | A person vouching for a product. Mostly rules about what NOT to add |
|
|
76
|
+
| `explainer` | Faceless educational video: one claim, invented visuals |
|
|
77
|
+
| `product-demo` | Real product doing a real thing — the highest slop-risk format in the catalog |
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: explainer
|
|
3
|
+
video_type: faceless explainer / educational — one idea, invented visuals, narrated
|
|
4
|
+
checks:
|
|
5
|
+
duration_sec: 20-180
|
|
6
|
+
first_frame_visual: required
|
|
7
|
+
first_frame_text: required
|
|
8
|
+
captions: required
|
|
9
|
+
audio: required
|
|
10
|
+
font_regime: required
|
|
11
|
+
max_scene_sec: 8
|
|
12
|
+
max_simultaneous_text: 1
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Explainer QA Regime
|
|
16
|
+
|
|
17
|
+
For faceless educational video: one idea, explained, with visuals that are *invented* (typography, diagrams, data, abstract motion) rather than captured. No presenter, so the structure has to carry everything a face would.
|
|
18
|
+
|
|
19
|
+
## The one test
|
|
20
|
+
|
|
21
|
+
> **Can you say what single thing the viewer knows at the end that they didn't know at the start — in one sentence, without using the word "and"?**
|
|
22
|
+
|
|
23
|
+
If it takes an "and," you have two videos. Split them. The dominant failure of the format is not being boring — it's being *about a topic* instead of being *about one claim*.
|
|
24
|
+
|
|
25
|
+
## Structure
|
|
26
|
+
|
|
27
|
+
| Beat | Job |
|
|
28
|
+
|---|---|
|
|
29
|
+
| **The claim** (0:00–0:03) | State the surprising conclusion first. Not "today we'll look at X" |
|
|
30
|
+
| **The stakes** | Why it matters to the viewer specifically. One sentence |
|
|
31
|
+
| **The mechanism** | The actual explanation, in 3–5 steps that each earn their scene |
|
|
32
|
+
| **The turn** | The part that isn't obvious — the reason the video exists |
|
|
33
|
+
| **The consequence** | What the viewer does or thinks differently now |
|
|
34
|
+
|
|
35
|
+
**Conclusion first, not last.** Educational-video instinct is to build to the reveal; feed instinct is that nobody stays for a reveal they weren't promised. Say the conclusion in the first three seconds, then spend the video *earning* it. Retention comes from "how could that be true?", not from suspense.
|
|
36
|
+
|
|
37
|
+
## Rules
|
|
38
|
+
|
|
39
|
+
### Rule 1 — one idea, and the scene count proves it
|
|
40
|
+
|
|
41
|
+
3–5 steps in the mechanism, each with a scene that shows something the narration doesn't say. If a scene only re-renders the words being spoken, it isn't a scene — it's a slide, and slides are where retention dies. `max_scene_sec` is checked above for exactly this reason: a static hold is the visual form of "I ran out of things to show."
|
|
42
|
+
|
|
43
|
+
### Rule 2 — visuals carry information, not decoration
|
|
44
|
+
|
|
45
|
+
Every invented visual should be doing one of: showing a quantity (chart, count-up, comparison), showing a relationship (diagram, flow, before/after), showing a scale (physical comparison), or showing a process (step, progress, transformation). Ambient loops and abstract gradients under narration are the explainer equivalent of stock footage — they cost time and buy nothing.
|
|
46
|
+
|
|
47
|
+
### Rule 3 — numbers get their own moment or they get cut
|
|
48
|
+
|
|
49
|
+
A figure spoken over a busy frame doesn't land. If a number matters, it appears on screen, alone, long enough to read. If it doesn't deserve that, it doesn't belong in the script.
|
|
50
|
+
|
|
51
|
+
### Rule 4 — no unsourced authority
|
|
52
|
+
|
|
53
|
+
"Studies show", "experts agree", "it's well known" — either name the source on screen or state it as your own reasoning. The format's whole currency is that it sounds like it knows; borrowed authority with no name is what makes an explainer feel like content farming.
|
|
54
|
+
|
|
55
|
+
### Rule 5 — narration is written to be heard, not read
|
|
56
|
+
|
|
57
|
+
Short clauses. One idea per sentence. No subordinate clause stacking. Read it aloud once — if you run out of breath or have to re-read a line, rewrite it. TTS in particular will happily deliver an unreadable sentence at a perfectly even pace, which is how a script defect ships.
|
|
58
|
+
|
|
59
|
+
### Rule 6 — production floor
|
|
60
|
+
|
|
61
|
+
Captions verbatim in the font regime and safe zone · frame 0 states the claim (it is the thumbnail, and for this format it's usually pure typography, which makes it the *easiest* format to get a good thumbnail from — no excuse for a black open) · no HTML slop: an explainer's subject matter drags authors toward feature grids, comparison tables, and card layouts, and those are exactly the banned web furniture. A comparison is an animated before/after, not a two-column table.
|
|
62
|
+
|
|
63
|
+
## Bulk-generation notes
|
|
64
|
+
|
|
65
|
+
The variant axis is **the claim**, not the topic: ten videos each making a different one-sentence claim about the same subject beats one video making ten points. It also gives you clean attribution — you learn which claim travels, which is the only thing you can act on.
|
|
66
|
+
|
|
67
|
+
Reuse across variants: the mechanism scenes are often identical, so build them once as reusable assets and vary the claim + stakes + turn. That's the cheap path — HTML/CSS motion over a fixed asset set, no per-variant generation spend.
|
|
68
|
+
|
|
69
|
+
## Pre-flight checklist
|
|
70
|
+
|
|
71
|
+
- [ ] The one thing the viewer learns fits in a single sentence with no "and"
|
|
72
|
+
- [ ] The conclusion is stated in the first 3 seconds, not saved for the end
|
|
73
|
+
- [ ] The stakes are named — why this viewer specifically should care
|
|
74
|
+
- [ ] The mechanism is 3–5 steps and each has a scene that adds information
|
|
75
|
+
- [ ] No scene is a slide that just re-renders the narration
|
|
76
|
+
- [ ] Every visual shows a quantity, relationship, scale, or process
|
|
77
|
+
- [ ] Every number that matters gets its own readable moment
|
|
78
|
+
- [ ] No unsourced "studies show" / "experts agree"
|
|
79
|
+
- [ ] The narration was read aloud and survived it
|
|
80
|
+
- [ ] Frame 0 states the claim and works as a standalone thumbnail
|
|
81
|
+
- [ ] No comparison tables, feature grids, or card layouts (`vidfarm qa` clean)
|
|
82
|
+
- [ ] In a batch: this variant makes a genuinely different claim, not a rephrased one
|