@officexapp/vidfarm-devcli 0.21.52 → 0.21.54
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/vidfarm/SKILL.md +6 -2
- package/.agents/skills/vidfarm/harnesses/hooks.HARNESS.md +10 -3
- package/.agents/skills/vidfarm/harnesses/product-explainer.HARNESS.md +1 -1
- package/.agents/skills/vidfarm/harnesses/short-form.HARNESS.md +2 -0
- package/.agents/skills/vidfarm/references/assets-and-sourcing.md +44 -4
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +1 -1
- package/.agents/skills/vidfarm/references/content-ideas.md +72 -1
- package/.agents/skills/vidfarm/references/core-workflows.md +17 -2
- package/.agents/skills/vidfarm/references/editor-workflows.md +39 -9
- package/.agents/skills/vidfarm/references/reviewing-renders.md +1 -0
- package/SKILL.director.md +180 -19
- package/SKILL.md +9 -2
- package/clipper.md +22 -0
- package/dist/src/cli.js +98 -5
- package/dist/src/devcli/composition-edit.js +36 -3
- package/dist/src/devcli/marketplace-gigs.js +2 -2
- package/dist/src/devcli/qa-check.js +7 -3
- package/dist/src/devcli/shared-folder.js +333 -44
- package/dist/src/devcli/skill-docs.js +2 -2
- package/experimental/meme-recaption.md +1756 -0
- package/experimental/sticker-slideshow-tips.md +1486 -0
- package/experimental/ugc-reaction-greenscreen.md +1963 -0
- package/experimental/wall-text-pov-ugc.md +2036 -0
- package/marketplace.md +82 -8
- package/package.json +6 -1
- package/public/assets/file-directory-app.js +33 -33
- package/public/assets/homepage-client-app.js +14 -14
package/SKILL.director.md
CHANGED
|
@@ -188,7 +188,7 @@ Cost mode answers *how much money may I spend*. It does not answer *how much of
|
|
|
188
188
|
**How aggressively to run phase 2 depends on the cost mode.** This is the single biggest lever on both quality and spend:
|
|
189
189
|
|
|
190
190
|
- **`minimize` — the collection phase is the whole job. Budget most of your effort here.** You have no generation to fall back on, so the pool has to be good, and it can be: work the ladder in order and stop at the first rung that answers the beat.
|
|
191
|
-
1. `vidfarm public-raws --categories` then `--category <shelf>` — pre-tagged, free, already cut to short-form. Check this first, every time.
|
|
191
|
+
1. `vidfarm public-raws --categories` then `--category <shelf>` — pre-tagged, free, already cut to short-form. Check this first, every time. Need the **same face in several shots**? Read the `actor_<uuid>` token off a card's summary, then `vidfarm public-raws --query actor_<uuid>` returns that person's other clips (`references/assets-and-sourcing.md` → *Same face across clips*).
|
|
192
192
|
2. `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|video>` (keyless via openverse/iconify) and `vidfarm iconscout "<meaning>" --free` for icons, stickers, illustrations and props.
|
|
193
193
|
3. `vidfarm raws search "<meaning>"` — what the director already owns from previous videos.
|
|
194
194
|
4. **Browser control** — `vidfarm browse videos "<the shot>"` drives the user's own Chrome for $0 (`references/browser-harness.md`). This is the free substitute for the paid `video-search` / `image-search` / `news-search` / `download-video` primitives. **Set it up rather than declaring the shot unavailable.**
|
|
@@ -382,7 +382,7 @@ You may be running as the **in-web AI chat** (the /editor copilot, the chat dock
|
|
|
382
382
|
**Two halves, and only one is machine-checkable.** The `checks:` front matter is settled deterministically by `vidfarm qa` (duration, aspect, `hook_words_max`, `forbid_text`, …); every `- [ ]` line comes back as a **review item you answer honestly in your report** — never claim a video passed the half the CLI can't judge. Harnesses stack and auto-discover: `vidfarm qa ./work` picks up `./work/HARNESS.md`, `--harness hooks --harness ./brand/HOUSE.md` adds more, and any file of theirs anywhere is valid. Format and strand table: `harnesses/README.md`; scripting-mode detail: `references/automation-and-local-dev.md`. *(Formerly `QA_REGIME.md` — same file, and `vidfarm regime …` still works as an alias.)*
|
|
383
383
|
- **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`.
|
|
384
384
|
- **On devcli there's an OPTIONAL checker: `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). **Skipping it is fine — watching the render is the review that actually counts, and a clean `qa` is not one.** When you do run it, it allows **one** fix round by default: the first pass names the slop, one fix clears it, and a second round is nearly always taste rather than a defect. The human owns that number — `--max-revisions <n>` raises it, `0` disables it; ask rather than raising it yourself. `--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`.
|
|
385
|
-
- **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) and, inside that band, is **placed in the emptiest part of the frame** rather than dumped on the default lower third (read a still first — `vidfarm stills ./work --at <t>`; words over open sky or a blank wall beat words over the subject, and usually need no plate at all). Long narration is **paged into 3–5-word kinetic cues** (`vidfarm captions generate --style word-pop`), never one static wall of text. It uses the composition's bold font regime
|
|
385
|
+
- **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) and, inside that band, is **placed in the emptiest part of the frame** rather than dumped on the default lower third (read a still first — `vidfarm stills ./work --at <t>`; words over open sky or a blank wall beat words over the subject, and usually need no plate at all). Long narration is **paged into 3–5-word kinetic cues** (`vidfarm captions generate --style word-pop`), never one static wall of text. It uses the composition's bold font regime — **five default families**: TikTok Sans (safe default), Montserrat (bold default), Abel (condensed), Source Code Pro (mono, 700 only), Yesteryear (script accent line only) — at weight **700–900**, ~36–64px on a 1080-wide frame. Any other family (Inter / Roboto / Arial / system-ui / Georgia / a client brand font) is not imported and silently falls back to a web-default sans at render — **so a custom font IS allowed, but only if you declare it in the composition** (`@font-face` or a Google Fonts `@import`); a declared family is never coerced, an undeclared one is. The regime is a heavy suggestion, and the fallback is the actual defect. **The regime on one page: <https://vidfarm.cc/fonts>** — all five rendered as real captions, the four legal backgrounds, and copy-paste commands. **Every family and background also has its own standalone reference image** (`https://vidfarm.cc/assets/fonts/caption-font-<family>.png`, e.g. `…caption-font-montserrat.png`; backgrounds are `…caption-bg-outline.png` and friends) — after you style a caption, pull a still and **compare it against the card for the family you picked**; a font that failed to load is obvious side by side and invisible on its own. It 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").
|
|
386
386
|
- **Use the REAL track — a popular/copyrighted song in the render is expected here, not a problem.** Do not refuse it, do not silently substitute a generic royalty-free bed, and do not stop to warn about it. **Why it is legitimate:** the render is a **review artifact**, and at post time the user re-attaches the *same* song from the platform's own in-app music library (TikTok / Reels / Shorts), which is licensed through the platform's agreements with the labels. The music is therefore cleared on the surface where viewers actually hear it. **Why it is required:** a trending song IS the edit — cut points, pacing, the drop, the meme association. A render with the wrong bed, or no bed, reads as a different and much weaker video, so a review over a substitute bed grades a video nobody will ever post. Silent or generic-bed review is the bigger defect. **How to do it:** get the exact track (`vidfarm download-audio <post-url>`, the user's own file, or the audio off the source post the format is copying), and keep it on **its own `<audio>` layer** at its own `data-volume` — never baked into the footage or mixed into the voice stem. One separate track means the user can mute or swap it in one action when they upload, which is exactly what the platform flow expects. Say which track you used in the handoff. **The one limit:** this is for review renders and platform posting where the platform holds the license. If the user asks to sell, syndicate, or run the render as a **paid ad** with the track baked in, say once that ad placements are not covered by the in-app music license, and offer `vidfarm music "<same vibe, same BPM>"` as the swap for that cut. Then do what they decide.
|
|
387
387
|
- **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.
|
|
388
388
|
- **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.
|
|
@@ -443,6 +443,10 @@ Why they exist: the prompts you find on the `/discover` pages are tuned to **one
|
|
|
443
443
|
|---|---|
|
|
444
444
|
| `https://vidfarm.cc/experimental/unique-product-explainers.md` | N customer URLs → N product-introduction videos that do not look like each other. Differentiation as an input, frame-level review, measured verification |
|
|
445
445
|
| `https://vidfarm.cc/experimental/google-news-to-video.md` | A recent real event → a timely video. Two stages: `news-search` finds the STORY, `video-search` finds the VISUALS. Query formulas, Google operators, licence discipline |
|
|
446
|
+
| `https://vidfarm.cc/experimental/meme-recaption.md` | One borrowed meme clip + one new caption aimed at the offer's problem space. Casting the meme for the caption's **verb**, the seven caption frames, naming the offer without letting the joke resolve into a pitch, keying a MemeScreens raw onto a background world, and a render-level QA gate. Runs at $0 |
|
|
447
|
+
| `https://vidfarm.cc/experimental/ugc-reaction-greenscreen.md` | Sell an app with three streams cut against each other: `ugc-reaction` raws, a Display Greenscreen device whose screen carries the customer's real demo, and the demo itself. One actor across every beat via `actor_<uuid>`, a per-frame tracked screen insert, captions inside the platform-chrome-safe core, and two exports from one render (voiceover-only to publish, voiceover+music to review) |
|
|
448
|
+
| `https://vidfarm.cc/experimental/sticker-slideshow-tips.md` | A tips **carousel** — the deliverable is N still slides, and the 3.0s-per-slide MP4 is only the playable preview. Die-cut cutouts on a paper page or a photo background, a literal "Tips for…" cover, the specificity ladder, one slide shilled from the middle and written so it survives deleting the brand name, four background modes, and a measured WCAG contrast gate on the exported PNGs |
|
|
449
|
+
| `https://vidfarm.cc/experimental/wall-text-pov-ugc.md` | One unbroken ambient take + one static block of unplated type. No cuts, no voiceover, no subtitles, nothing animated. The retention engine is arithmetic — `duration = words / 8`, so one play lands the viewer at the **halfway mark**, committed and one pass from done — floored at 8s so a trending sound gets a real phrase of a track. The four speaker frames, MIRROR vs TURN, a density pass that treats padding as the fatal failure, casting the scene dark so the type needs no plate (measured), ping-ponging the plate for a seamless loop, and three gates. Runs at $0 |
|
|
446
450
|
|
|
447
451
|
Fetch one as plain markdown and follow it end to end; do not skim it into a summary.
|
|
448
452
|
|
|
@@ -558,6 +562,21 @@ Templates are listed on the Vidfarm homepage and `/discover`. Each has a `templa
|
|
|
558
562
|
- API: `GET /discover/feed` — returns `{ templates: [{ templateId, slugId, title, previewUrl, viralDna, durationSeconds, sourceType, promotions, keywords, summary, ... }], next_cursor }`
|
|
559
563
|
- Search: `GET /discover/feed?q=<offer>&limit=20&sort=relevance` hybrid-searches the eligible public catalog using semantic embeddings plus lexical matches. Semantic query embedding uses Vidfarm's canonical OpenRouter-routed model and bills the provider cost × the standard 1.2 markup to the user's wallet. The response's `search` block reports `mode`, `embedding_space`, and any `semantic_limitation`; when it says `lexical_structured`, disclose the limitation briefly and continue rather than refusing. Decomposition adds `promotions`, `keywords`, `summary`, `categoryTags`, and `catalogIntelligence` (`wowScore`, `wowReason`, `automationScore`, `automationReason`, `contentStyles`, `searchText`). Use `sort=wow` for highest-quality/client-impressing formats, `sort=automation` for cheap repeatable bulk formats, and `sort=recent` only when freshness is the intent. Follow `next_cursor` with `cursor=<value>`; never call page one the whole catalog. `GET /api/v1/videos?q=<offer>&limit=20[&mine=true]` searches source **inspirations**. Undecomposed inspirations have only sparse ingest metadata, so they are harder to retrieve semantically. Explain that somebody in the world needs to decompose one once and the shared enrichment then benefits everyone; the current user need not act unless they want that specific inspiration immediately.
|
|
560
564
|
|
|
565
|
+
### Featured templates (members only)
|
|
566
|
+
|
|
567
|
+
Featured is the small, hand-curated shelf at the top of the Discover feed. Signed-in members open on it by default; anonymous visitors do not see it at all.
|
|
568
|
+
|
|
569
|
+
- `GET /discover/feed?view=featured` — the shelf, in curated order (`featured: true`, `featuredRank` ascending). Signed-out callers get `401`.
|
|
570
|
+
- In the ordinary `view=available` feed, a signed-in caller's featured picks sort first (an active `q=` search keeps relevance order instead).
|
|
571
|
+
- Browser: the **View** dropdown on `/discover/templates/feed` carries **View Featured**, and `?view=featured` deep-links to it.
|
|
572
|
+
|
|
573
|
+
Operators curate the shelf with the superagency key (`x-superagency-key`). Every id below accepts an `inspiration_...`, `template_...`, or `fork_...` id:
|
|
574
|
+
|
|
575
|
+
- `GET /api/v1/admin/discover/featured` — the current shelf, plus `unresolved` ids that no longer render (deleted, archived, or gone private).
|
|
576
|
+
- `POST /api/v1/admin/discover/featured { id, rank? }` — feature one template. Default rank appends it to the end.
|
|
577
|
+
- `PUT /api/v1/admin/discover/featured { items: [{ id, rank? }] }` — replace the whole shelf; array order is shelf order, and anything not listed is un-featured. Ids that resolve to nothing come back under `unknown`.
|
|
578
|
+
- `DELETE /api/v1/admin/discover/featured/:id` — drop one pick from the shelf.
|
|
579
|
+
|
|
561
580
|
Each template exposes a public preview:
|
|
562
581
|
|
|
563
582
|
- `GET /editor/:templateId` — opens the Trackpad Editor for the template (redirects to your fork of it, or to `/login`)
|
|
@@ -875,7 +894,7 @@ curl -s -X POST -H "vidfarm-api-key: $VIDFARM_API_KEY" -H "content-type: applica
|
|
|
875
894
|
curl -s -X POST -H "x-api-key: $DP" "https://dollarplatoon.com/api/gigs/$GIG/rollups"
|
|
876
895
|
```
|
|
877
896
|
|
|
878
|
-
`paid_out_at` on the proof — not `approved` — is the field that means the money moved. In the web UI this is the **Release N Payouts** button on /marketplace/
|
|
897
|
+
`paid_out_at` on the proof — not `approved` — is the field that means the money moved. In the web UI this is the **Release N Payouts** button on /marketplace/inbound/mailbox, which appears only when a machine has approved-but-unpaid proofs.
|
|
879
898
|
|
|
880
899
|
**4 — Add a new task.** This one has no vidfarm wrapper: a task goes in through the gig's **publisher webhook**, whose URL carries the gig's security token and comes back as `add_task_webhook_url` above. No key header — the token in the URL is the auth.
|
|
881
900
|
|
|
@@ -908,7 +927,7 @@ curl -s -X POST -H "x-api-key: $DP" -H "content-type: application/json" \
|
|
|
908
927
|
-d '{"max_uses":null,"label":"vidfarm"}' "https://dollarplatoon.com/api/gigs/$GIG/invites"
|
|
909
928
|
```
|
|
910
929
|
|
|
911
|
-
If you ring the bell directly against Dollar Platoon (`POST /feeds/$FEED/notifications`), resolve that invite FIRST and send it as `destination_url`. The feed registry's `invite_url` is tokened too and works as a fallback. In the web UI, **Ring Bell** does this for you, shows the exact link it published, and **Copy invite link** on /marketplace/
|
|
930
|
+
If you ring the bell directly against Dollar Platoon (`POST /feeds/$FEED/notifications`), resolve that invite FIRST and send it as `destination_url`. The feed registry's `invite_url` is tokened too and works as a fallback. In the web UI, **Ring Bell** does this for you, shows the exact link it published, and **Copy invite link** on /marketplace/inbound/swipe hands you the same link to share by hand.
|
|
912
931
|
|
|
913
932
|
Ring it when something actually **changed** — a new batch, a new price, a format you now want. An empty bell teaches agents to stop opening yours.
|
|
914
933
|
|
|
@@ -1071,23 +1090,36 @@ For any replication, offer the director **two harnesses** and recommend (A) unle
|
|
|
1071
1090
|
|
|
1072
1091
|
### Writing a meme recaption: aim at a pain or a win
|
|
1073
1092
|
|
|
1093
|
+
> ## → Building a meme video from scratch? Use the harness.
|
|
1094
|
+
> **`https://vidfarm.cc/experimental/meme-recaption.md`** is the full standalone format: casting a
|
|
1095
|
+
> clip off the public-raws shelf for the caption's **verb** and its **intensity**, keying a
|
|
1096
|
+
> MemeScreens raw onto a background world, caption placement, naming the offer, rights tiers, and a
|
|
1097
|
+
> render-level QA gate that exits non-zero. It runs at $0 and it supersedes this section for any
|
|
1098
|
+
> from-scratch build.
|
|
1099
|
+
>
|
|
1100
|
+
> **This section is the short version**, for when you already have the clip on a timeline and are
|
|
1101
|
+
> only rewriting the line (`set_captions` / `set_layer_text`). The writing craft below is the same
|
|
1102
|
+
> craft the harness uses — the harness just adds everything around it.
|
|
1103
|
+
|
|
1074
1104
|
Recaptioning is the cheapest brush, but the *line* is the whole product — the clip is just the punchline delivery system. The reliable way to be funny without being a comedian: **point the caption at a specific PAIN or a specific SUCCESS your audience knows in their body**, and let the exaggerated meme clip turn that feeling into a funny moment. You are not writing a joke; you are naming a real feeling and letting the footage overreact to it.
|
|
1075
1105
|
|
|
1076
|
-
- **Pain** — the annoying, tedious, humiliating, or absurd moment in the audience's day. The meme clip supplies the reaction they wish they could have. *"me opening the bank app after a weekend of 'small' purchases"*.
|
|
1077
|
-
- **Success** — the small win, the relief, the flex, the moment something finally worked. The clip supplies the celebration. *"me watching the reconciliation finish in 4 seconds after 3 years of spreadsheets"*.
|
|
1106
|
+
- **Pain** — the annoying, tedious, humiliating, or absurd moment in the audience's day. The meme clip supplies the reaction they wish they could have. *"me opening the bank app after a weekend of 'small' purchases"*. Ends on the problem; does not resolve.
|
|
1107
|
+
- **Success** — the small win, the relief, the flex, the moment something finally worked. The clip supplies the celebration. *"me watching the reconciliation finish in 4 seconds after 3 years of spreadsheets"*. Resolves by design — the relief IS the joke.
|
|
1108
|
+
|
|
1109
|
+
**Decide the pole before you write.** It changes whether the caption may resolve, and it is the most useful thing to A/B: run both across the same clip and see whether the market wants to be *seen* or wants *relief*.
|
|
1078
1110
|
|
|
1079
1111
|
How to work it:
|
|
1080
1112
|
1. **Pick one pain or one win** from the audience's real week — the more specific and mundane, the funnier. Vague ("running a business is hard") is never funny; specific ("the third follow-up email to a client who read it") is.
|
|
1081
|
-
2. **Match the intensity of the clip to the size of the feeling.** The comedy is the mismatch: a tiny pain + a wildly over-the-top reaction clip, or a small win + a stadium-scale celebration. Choose the pain/win that the existing clip's energy already fits, rather than fighting the footage.
|
|
1082
|
-
3. **Keep the meme's grammar** — `me when…` / `
|
|
1083
|
-
4. **
|
|
1084
|
-
5. **The line must make sense to a stranger to the offer — the cold-viewer test.**
|
|
1113
|
+
2. **Match the intensity of the clip to the size of the feeling.** The comedy is the mismatch: a tiny pain + a wildly over-the-top reaction clip, or a small win + a stadium-scale celebration. Choose the pain/win that the existing clip's energy already fits, rather than fighting the footage — the clip is the part you cannot edit.
|
|
1114
|
+
3. **Keep the meme's grammar** — `me when…` / `my clients when…` / `us after…` / `<thing> watching me…`. Swap the subject to whoever owns the feeling (the customer, the founder, the team), keep the frame.
|
|
1115
|
+
4. **Naming the product in the line is fine — as a subject, not as the answer.** On a **pain** caption the name may stand in the setup as a witness (*"AutoBooks watching me photograph a 41st receipt"*) but never in the last clause. On a **win** caption it belongs in the last clause, because the relief is the joke (*"me watching AutoBooks reconcile 3 months of receipts while I do nothing"*). What is never allowed on either pole is a **CTA** (*"Save 10 hours a month with AutoBooks 🚀"*) or a **feature** (*"after the auto-reconcile v2 sync clears"*). If naming it makes the line clumsy, leave it out of the sentence and put the offer in a small dimmed handle under the caption instead — both tiers, and the checks that enforce them, are in the harness.
|
|
1116
|
+
5. **The line must make sense to a stranger to the offer — the cold-viewer test.** Read it as a person who works in the niche but has NEVER heard of the product, the brand, the feature names, or the category jargon. If they cannot get the joke in one read, the caption failed, no matter how clever it is to the team.
|
|
1085
1117
|
- **Write about the niche, not the feature.** A meme about a product feature needs product context to land, so it lands only on people who already bought. A meme about the niche's lived experience lands on everybody in the niche — which is the whole point of a meme ad.
|
|
1086
1118
|
- BAD (needs product context): *"me after the auto-reconcile v2 sync finally clears"* — "auto-reconcile v2" means nothing to a cold viewer. GOOD: *"me at 1am matching bank lines to receipts by hand"*.
|
|
1087
1119
|
- **Test:** cover the brand and the feature names. If the caption still reads as a true, funny moment from the audience's week, keep it. If it turns into nonsense, rewrite it around the feeling instead of the feature.
|
|
1088
1120
|
- **No inside jokes, no invented vocabulary, no setups that only the founder's demo explains.** Slang the niche already uses is fine; slang only the product uses is not.
|
|
1089
1121
|
6. **One short punchy line**, matching the original's brevity, tone, and comedic timing. If the original was two stacked lines (setup / payoff), keep two — pain on top, reaction beneath.
|
|
1090
|
-
7. **Batch it.** One meme clip + a list of ten pains and ten wins is
|
|
1122
|
+
7. **Batch it.** One meme clip + a list of ten pains and ten wins is twenty videos. Enumerate the audience's pains and wins ONCE, then recaption the same clip (or a small set of clips) across the whole list — this is the highest-output, lowest-cost loop in Vidfarm. The list is the asset; the clips are interchangeable.
|
|
1091
1123
|
|
|
1092
1124
|
If the user insists on explicit ad copy in the recaption, say once that it flattens the joke, then give them the pain/win version alongside what they asked for.
|
|
1093
1125
|
|
|
@@ -1632,7 +1664,24 @@ Short-form is watched on a phone, and the phone's UI eats the frame's edges. **N
|
|
|
1632
1664
|
- *Judgement call, inside that band:* **put the words where the picture isn't.** `y≈70%` is the `captions generate` default because most footage puts its subject mid-frame — it is a default, not a law. Before you place text, **look at an actual frame** (`vidfarm stills ./work --at <t>`, free) and find the region with the least going on: open sky above a dashboard, a blank wall behind a talking head, an out-of-focus background, an empty tabletop. If nothing else in the video is competing for attention there — no subject, no motion, no product, no second text layer — that is where the caption belongs, even if it means **high-centre at y≈10–25%** instead of a lower third. A caption dropped over the busiest third of the frame (hands on a steering wheel, a face, the product) fights the shot and forces you to armour it with a plate; the same words parked in the sky are legible with no plate at all.
|
|
1633
1665
|
- *When you're only rescuing an inherited caption* off a dead-zone edge, preserve its top-vs-bottom anchoring and just pull it inside the band — don't recentre a template you haven't re-read. When **you** are the one placing the text, place it deliberately.
|
|
1634
1666
|
- **Size → scaled to the line, not maxed out.** Sizes are PIXELS of a 1080-wide frame: **~36–64px** reads well; below ~28px is unreadable on a phone and **0 is invisible**. Above ~64px is a *hook-word* size — one to three words, on purpose. The failure this catches: a full sentence set at display size runs edge-to-edge, wraps to three lines, and eats a third of the frame, so it has to be armoured with a full-width plate and there is nowhere left to put it. **If a line reaches the frame edges, the fix is a smaller size (or fewer words per cue), not a wider box.** Keep captions to ~2 lines / ~5 words per line; `line_height` 0.95–1.15 for stacked display lines.
|
|
1635
|
-
- **Font → the composition regime
|
|
1667
|
+
- **Font → the composition regime. Five families are the heavy default.** The whole regime on one page — each family rendered as a real caption, the four legal backgrounds, copy-paste `set-style` commands: **<https://vidfarm.cc/fonts>** (combined specimen sheet: `https://vidfarm.cc/assets/tiktok-caption-fonts.png`). Read it once before you style anything, and hand the link to a human director who is picking a look.
|
|
1668
|
+
|
|
1669
|
+
**Every style also has its OWN standalone reference image** — one family (or one background), rendered at caption size on real footage-like plate. **Use them for comparison.** The workflow: pick a family → style the layer → pull a still (`vidfarm stills ./work --at <t>`) → open that family's card next to the still and check the glyphs match. A fallback font is obvious side by side and nearly invisible on its own, and this is the only check that catches a family that failed to load. Fetch the one card you need instead of the whole sheet.
|
|
1670
|
+
|
|
1671
|
+
| Family | Weights it really has | Standalone reference card | Use it for |
|
|
1672
|
+
|---|---|---|---|
|
|
1673
|
+
| **TikTok Sans** | 400 / 600 / 700 / 800 / 900 | `https://vidfarm.cc/assets/fonts/caption-font-tiktok-sans.png` | The native TikTok caption look. **The safe default when unsure.** |
|
|
1674
|
+
| **Montserrat** | 600 / 700 / 800 / 900 | `https://vidfarm.cc/assets/fonts/caption-font-montserrat.png` | Geometric bold display. Hooks, hard statements, the Hormozi caption. **The bold default.** |
|
|
1675
|
+
| **Abel** | 400 only | `https://vidfarm.cc/assets/fonts/caption-font-abel.png` | Condensed headline / newsletter vibe. A long line that must stay on one row. |
|
|
1676
|
+
| **Source Code Pro** | 700 only | `https://vidfarm.cc/assets/fonts/caption-font-source-code-pro.png` | Code / terminal beats only. Never a whole video. |
|
|
1677
|
+
| **Yesteryear** | 400 only | `https://vidfarm.cc/assets/fonts/caption-font-yesteryear.png` | Cursive script. **One accent line** (a quote) — never a caption track; it is unreadable at cue size. |
|
|
1678
|
+
| ~~Georgia~~ | — | `https://vidfarm.cc/assets/fonts/caption-font-georgia.png` (anti-example) | **Decompose-only. Do not author in it.** The decompose vision pass may report `Georgia` off a source video's serif (it is the 6th value in `ALLOWED_FONTS`, `src/services/hyperframes.ts`), but the composition does not import it, so `normalizeTikTokCaptionLayout` coerces it to Montserrat on every local render. Rebuild an editorial look in Abel or Montserrat instead. |
|
|
1679
|
+
|
|
1680
|
+
The four legal backgrounds have cards too: `caption-bg-outline.png`, `caption-bg-plain.png`, `caption-bg-spotlight.png`, `caption-bg-highlight-solid.png` (same `/assets/fonts/` path). Regenerate all ten with `node scripts/render-font-specimens.mjs`.
|
|
1681
|
+
|
|
1682
|
+
These five are exactly what the composition `@import`s from Google Fonts, and exactly what `CAPTION_FONT_REGIME` (`src/devcli/composition-edit.ts`) keeps. **Anything else is normally not imported**: `Inter`, `Roboto`, `Arial`, `Helvetica`, `system-ui`, a client's brand font — the render silently falls back to a web-default sans, which is exactly the slop look. Asking for a weight the family does not ship (Abel 900, Yesteryear 700) fakes it with a synthetic bold and looks smeared — pick a family that has the weight instead.
|
|
1683
|
+
|
|
1684
|
+
**Custom fonts are allowed — the regime is a heavy suggestion, not a ban.** What is forbidden is *naming* a family the composition never ships, because that one silently falls back. If you want a sixth family, **declare it in the composition**: an `@font-face` pointing at a real file, or a Google Fonts `@import`/`<link>` that names it. A declared family is left alone — `normalizeTikTokCaptionLayout` does not coerce it and `vidfarm qa` does not flag it (`compositionDeclaresFont`, `src/devcli/composition-edit.ts`). An undeclared one is coerced to Montserrat locally and reported as a `font-regime` finding. Deviate on purpose, ship the font, then confirm on a rendered still — the editor preview loads fonts your render machine may not have. Matching a client's brand font is always fine for a *wordmark image*; on the caption layer, only do it with the `@font-face` in place.
|
|
1636
1685
|
- **Background → one of exactly four valid treatments.** Any text you place uses one of these and nothing else:
|
|
1637
1686
|
|
|
1638
1687
|
| # | Treatment | How to set it | When |
|
|
@@ -1657,7 +1706,7 @@ Short-form is watched on a phone, and the phone's UI eats the frame's edges. **N
|
|
|
1657
1706
|
|
|
1658
1707
|
**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.
|
|
1659
1708
|
|
|
1660
|
-
> Local devcli renders enforce part of this automatically: `renderCompositionLocally` runs `normalizeTikTokCaptionLayout` (src/devcli/composition-edit.ts) on every production, clamping caption/text layers into the 8%–85% safe zone and coercing off-regime primary fonts to Montserrat. It only fixes position and font family — it will happily render your Bootstrap card. Get it right in the composition so the editor preview, the local render, and any cloud render match.
|
|
1709
|
+
> Local devcli renders enforce part of this automatically: `renderCompositionLocally` runs `normalizeTikTokCaptionLayout` (src/devcli/composition-edit.ts) on every production, clamping caption/text layers into the 8%–85% safe zone and coercing off-regime primary fonts to Montserrat — **unless the composition declares that family itself**, in which case your custom font renders as authored. It only fixes position and font family — it will happily render your Bootstrap card. Get it right in the composition so the editor preview, the local render, and any cloud render match.
|
|
1661
1710
|
|
|
1662
1711
|
### Animated captions — word-by-word caption styles (TikTok/CapCut)
|
|
1663
1712
|
|
|
@@ -2090,6 +2139,7 @@ Then answer these, out loud, in your report:
|
|
|
2090
2139
|
- **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.
|
|
2091
2140
|
- **Fluff, named out loud.** Which beats would you cut? Answer with specific timestamps, not "it's tight". Every tile has to justify its seconds: a frame that repeats the previous one, a scene the video would survive losing, an intro, a tail after the last word, a hold that's just waiting. **Assume 30–50% of the first assembly can go** and name what you'd remove — "nothing to cut" on a first pass is almost always a review that didn't look. Then cut it and `ripple` the hole closed (craft: `references/hooks-and-virality.md` → "Density"; the mechanical half is `vidfarm qa`'s `dead-air` / `dead-tail` / `slow-scene`).
|
|
2092
2141
|
- **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.
|
|
2142
|
+
- **Did the font you asked for actually render?** A family the composition never imported falls back to a web-default sans silently — the render *looks* fine, just generic, and you will not notice by memory. Open the standalone reference card for the family you chose (`https://vidfarm.cc/assets/fonts/caption-font-<family>.png` — `tiktok-sans`, `montserrat`, `abel`, `source-code-pro`, `yesteryear`, plus the `georgia` anti-example) next to your still and **compare the glyphs**. Same check for the text background: `caption-bg-outline|plain|spotlight|highlight-solid.png`. A fallback is obvious side by side and invisible on its own.
|
|
2093
2143
|
- **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.
|
|
2094
2144
|
- **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.
|
|
2095
2145
|
- **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.
|
|
@@ -2334,6 +2384,29 @@ What each shelf is for, in practice: **Scroll Stoppers** are half-second pattern
|
|
|
2334
2384
|
|
|
2335
2385
|
A bookmarked public raw lands in the director's own `/raws/public/*` folder, after which it behaves like any other library raw (`set_layer_media` / `vidfarm set-media` to drop it on the timeline).
|
|
2336
2386
|
|
|
2387
|
+
### Same face across clips — the `actor_<uuid>` tag
|
|
2388
|
+
|
|
2389
|
+
A shelf is dozens of clips of a much smaller number of creators. When a video needs **the same person in more than one shot** — a hook, a reaction cutaway and a payoff that must read as one spokesperson — the shelf alone can't tell you which clips share a face. The `actor_<uuid>` tag can.
|
|
2390
|
+
|
|
2391
|
+
Every tagged public raw carries one actor id in its `summary` (and in its tags), so the id is **readable off any card you like** and searchable as a plain query token:
|
|
2392
|
+
|
|
2393
|
+
```bash
|
|
2394
|
+
vidfarm public-raws --category ugc-reaction --limit 60 # 1. browse the shelf, pick a face
|
|
2395
|
+
# 2. read actor_<uuid> off that card's summary
|
|
2396
|
+
vidfarm public-raws --query actor_5d9f0c31-… # 3. every other clip of that same person
|
|
2397
|
+
```
|
|
2398
|
+
|
|
2399
|
+
REST twin: `GET /api/v1/public-raws?q=actor_<uuid>`. It is an ordinary keyword search, so it composes: `?category=ugc-reaction&q=actor_<uuid>`.
|
|
2400
|
+
|
|
2401
|
+
Rules that matter:
|
|
2402
|
+
|
|
2403
|
+
- **The id is opaque and permanent — never guess or invent one.** Read it off a card; a made-up id matches nothing.
|
|
2404
|
+
- **One id per person, not per clip.** A card with no `actor_` token is simply not tagged yet (older raws, or a raw with no person on camera) — treat "no tag" as unknown, not as "a different person".
|
|
2405
|
+
- **Use it for continuity, not casting from scratch.** Pick the face first (browse the shelf, judge the frame), then pull that face's other takes.
|
|
2406
|
+
- Same trick when a director asks *"more of her"* / *"the guy from the last video"* — the actor id off the raw already in the composition is the answer.
|
|
2407
|
+
|
|
2408
|
+
The `ugc-reaction` shelf is tagged: 177 raws across 53 people, and **almost every clip on it has siblings** — 49 of those 53 actors carry 2+ clips, several carry 5-7. Assume a face you like has more takes, and check.
|
|
2409
|
+
|
|
2337
2410
|
## Raws (long-form → short-form raws)
|
|
2338
2411
|
|
|
2339
2412
|
Mine a **long-form** video (podcast, stream VOD, webinar, any YouTube/TikTok/IG/X URL, or an upload) into a library of tagged, searchable **raws**. **Don't have a source URL yet? `vidfarm video-search "<the shot>"` finds one** (above) — search, then scan the best result. This is the `/raws` surface — browse it at `https://vidfarm.cc/library/raws` (the Library page's "Approved / Raws" tabs).
|
|
@@ -2495,6 +2568,15 @@ On `/tools/image` these are one-tap preset chips under the prompt box; in chat o
|
|
|
2495
2568
|
- **move** — `POST /api/v1/user/me/directory/move` `{ path, to, file_id? }` · `vidfarm directory move <path> <to-folder> [--file-id <id>]`. Relocates a FILE into `to` (with `file_id`) or nests a whole FOLDER under `to`. **Same root only** (`/files`·`/temp`·`/raws`·`/approved`); metadata-only, so S3 objects are untouched.
|
|
2496
2569
|
- **copy** — `POST /api/v1/user/me/directory/copy` `{ path, to?, file_id?, new_name? }` · `vidfarm directory copy <path> [<to-folder>] [--file-id <id>] [--as <name>]`. Duplicates a file/folder sharing the same underlying S3 object (cheap, no re-upload). Same root only, `/files`·`/temp`·`/raws` (not `/approved` — a ready post is a single publishable unit; not `/projects` — read-only). Omit `to` to duplicate in place.
|
|
2497
2570
|
|
|
2571
|
+
**Vector notes — what makes a file findable by MEANING.** Every file in `/files` and `/raws`, and every **folder** in any annotatable root, carries one plain-text *vector note*: what the thing actually is. The note is embedded, so `directory/search` finds it from any phrasing — `IMG_4821.mp4` is worth nothing to a search, "founder holding the product in a kitchen, warm daylight" is worth everything. Folders are searched the same way, so a well-noted folder is itself a hit.
|
|
2572
|
+
|
|
2573
|
+
- **read** — `GET /api/v1/user/me/directory/note?path=<path>[&id=<file_id>]` → `{ note, can_edit }`.
|
|
2574
|
+
- **write** — `PUT /api/v1/user/me/directory/note` `{ path, id?, note }` → `{ note, embedded }`. Omit `id` to annotate the FOLDER at `path`; pass the file's backend id to annotate a file. An empty `note` clears it. `embedded: false` means it saved but only keyword search will find it — the account has no AI key for embeddings.
|
|
2575
|
+
- **through a share link** — same two routes at `/api/v1/share/:token/directory/note`. `read` links never write. **`upload` links may FILL an empty note but never overwrite one**, so a contributor says what they dropped without rewriting anyone else's meaning. `edit` links and the owner may always write.
|
|
2576
|
+
- **automatic** — on upload, when the account has its own AI key saved (Gemini / OpenAI / OpenRouter), one vision call on the file's poster frame writes the note for you. Skipped for files over **100 MB** and when there is no key — then a human types it. The model runs on the customer's own key; only the Lambda compute is wallet-billed, at 2x.
|
|
2577
|
+
|
|
2578
|
+
In the explorer each row shows a note badge: `✎ note` (missing) or `✓ note` (written). Click it to read or write the note.
|
|
2579
|
+
|
|
2498
2580
|
**Sharing a folder publicly (crowdsourcing + agent-readable libraries).** Any folder in **any** root can carry share tokens — one folder, as many links as you want, each with its own mode:
|
|
2499
2581
|
|
|
2500
2582
|
- `read` — browse **and vector-search** the folder. Nothing else.
|
|
@@ -2505,6 +2587,8 @@ Minting or editing a link is **paid** (unlike the rest of the directory routes,
|
|
|
2505
2587
|
|
|
2506
2588
|
A token is scoped to **exactly one subfolder and everything under it** — never a sibling, never a parent, so a leaked link cannot walk the rest of the drive. The shared base folder itself can never be renamed or deleted through its own token. Owner routes: `GET|POST /api/v1/user/me/directory/shares`, `PATCH|DELETE …/shares/<token>` (`{ path, mode, label }`; `disabled: true` cuts a link off instantly and is reversible). Public routes, no auth: `GET /api/v1/share/:token/directory` (`?path=`), `POST /api/v1/share/:token/directory/search` `{ query, path?, mode? }`, plus `…/directory/folders`, `…/directory/rename`, `…/directory/delete`, `…/attachments/upload`. The human page is `/directory/preview/<token>/<path…>`.
|
|
2507
2589
|
|
|
2590
|
+
**Which roots accept a share upload:** `/files`, `/temp` and `/raws`. A `/raws` share is the **contributor drop-box** — the bytes land as a `role: "source"` raw inside the shared folder (video/audio only), exactly as `POST /raws/import` writes one. `/projects` (fork storage) and `/approved` (published renders) stay browse + search.
|
|
2591
|
+
|
|
2508
2592
|
**Uploading over ~6 MB through a share link:** `…/attachments/upload` is a multipart POST through the API Lambda, whose request body caps at about 6 MB — a bigger file answers **413** before the handler runs. Use the two-step presigned path instead (the share page's own uploader does): `POST /api/v1/share/:token/attachments/presign` `{ file_name, content_type?, size_bytes?, folder_path? }` → `{ transport: "presigned", file_id, storage_key, folder_path, upload: { method, url, headers } }`; PUT the raw bytes to `upload.url` with those headers; then `POST /api/v1/share/:token/attachments/finalize` `{ file_id, file_name, content_type, size_bytes, storage_key, folder_path }` to record the file. A server with no presigned transport answers `transport: "server"` and you post the multipart form. Ceiling is the owner's own: 200 MB.
|
|
2509
2593
|
|
|
2510
2594
|
In the UI: the file explorer's folder kebab → **Share…**, the `⤴ Share` toolbar button, and the folder kebabs on `/library` (**Raws** and **Approved** tabs). `⤴ Manage Shares` lists every token you own.
|
|
@@ -2513,14 +2597,20 @@ In the UI: the file explorer's folder kebab → **Share…**, the `⤴ Share` to
|
|
|
2513
2597
|
|
|
2514
2598
|
```bash
|
|
2515
2599
|
vidfarm shared info "$LINK" # folder, mode, what you may do
|
|
2516
|
-
vidfarm shared ls "$LINK" [subfolder]
|
|
2600
|
+
vidfarm shared ls "$LINK" [subfolder] --tree # browse (--tree walks every subfolder)
|
|
2517
2601
|
vidfarm shared search "$LINK" "founder talking head, no captions"
|
|
2518
|
-
vidfarm shared
|
|
2602
|
+
vidfarm shared grab "$LINK" "kitchen b-roll, no faces" --limit 3 --out ./assets
|
|
2603
|
+
vidfarm shared note "$LINK" hero.mp4 # what IS this? (--set "…" to write)
|
|
2604
|
+
vidfarm shared get "$LINK" hero.mp4 --out ./work # one file (--all [--tree] for the folder)
|
|
2519
2605
|
vidfarm shared mkdir "$LINK" task-014-yvette # your own subfolder (upload/edit)
|
|
2520
|
-
vidfarm shared put "$LINK" final.mp4 --subfolder task-014-yvette
|
|
2606
|
+
vidfarm shared put "$LINK" final.mp4 --subfolder task-014-yvette --note "Final cut, 9:16"
|
|
2521
2607
|
```
|
|
2522
2608
|
|
|
2523
|
-
|
|
2609
|
+
**`shared grab` is the command to reach for.** It runs the vector search and downloads the hits in one step, so an agent goes from "I need kitchen b-roll" to files on disk without paging folders. `search` and `grab` match on each item's **vector note**, and folders are hits in their own right — so read notes before you pick (`shared ls --tree` prints them, `✓` = described, `✎` = not).
|
|
2610
|
+
|
|
2611
|
+
**Write a note when you upload** (`shared put --note`, or `shared note <file> --set`). An `upload` link may FILL an empty note but never overwrite one; an `edit` link and the owner may always write. That is what makes your deliverable findable by the client's own agent later.
|
|
2612
|
+
|
|
2613
|
+
`shared put` runs presign → PUT → finalize, so it is not bound by the ~6 MB multipart ceiling; it falls back to multipart automatically when the server has no presigned transport. A `get`/`grab` whose stored view URL points at a host that no longer answers is retried against the share link's own host.
|
|
2524
2614
|
|
|
2525
2615
|
**The crowdsourcing pattern (DollarPlatoon custom-request vending machine).** Two links per campaign, minted once and reused across every task:
|
|
2526
2616
|
|
|
@@ -3121,7 +3211,7 @@ What it flags:
|
|
|
3121
3211
|
| `clickable-element` | error/warn | `<button>`, `<form>`, `<input>`; `<a href>` warns |
|
|
3122
3212
|
| `web-framework-classes` | error/warn | Bootstrap/Tailwind class tokens (`btn`, `badge`, `card`, `hero`, `col-*`, `rounded-full`, `shadow-lg`, `backdrop-blur`, `bg-gradient-to-*`) or a linked CSS framework. A `<script>` CDN for GSAP/anime.js is fine |
|
|
3123
3213
|
| `page-structure` / `bullet-list` | error/warn | `<nav>`/`<header>`/`<footer>`/`<table>`; a `<ul>` with visible bullet markers |
|
|
3124
|
-
| `font-regime` | error/warn | A text layer in a website body font (Inter/Roboto/Arial/system-ui → **error**) or any family outside the imported regime (Montserrat, TikTok Sans, Abel, Source Code Pro, Yesteryear → warn, it silently falls back at render) |
|
|
3214
|
+
| `font-regime` | error/warn | A text layer in a website body font (Inter/Roboto/Arial/system-ui → **error**) or any family outside the imported regime (Montserrat, TikTok Sans, Abel, Source Code Pro, Yesteryear → warn, it silently falls back at render). **A custom family the composition DECLARES** (`@font-face` or a Google Fonts `@import` naming it) **is not flagged** — the rule targets the silent fallback, not your typography. Each regime family has a reference card at `https://vidfarm.cc/assets/fonts/caption-font-<family>.png`; compare a still against it after styling |
|
|
3125
3215
|
| `font-size` / `font-weight` | error/warn | `font-size:0` (invisible) is an error; sub-2.6%-of-canvas-width text and weight <600 warn |
|
|
3126
3216
|
| `caption-safe-zone` | warn | Text outside the 8%–85% band on a **portrait** canvas (landscape/square are exempt) |
|
|
3127
3217
|
| `caption-oversize` | warn | Display-size type (>7.5% of canvas width) on a line of **5+ words** — it runs edge-to-edge, wraps, covers the frame, and forces a full-width plate. Both signals required, so a giant 2-word hook card passes |
|
|
@@ -3425,11 +3515,13 @@ Read this when a director says **"give me content ideas"**, "what should I post"
|
|
|
3425
3515
|
|
|
3426
3516
|
| Axis | Bank | Question it answers |
|
|
3427
3517
|
|---|---|---|
|
|
3428
|
-
| **Subject** | the director's own pool of adjacent topics | *What is this one about
|
|
3518
|
+
| **Subject** | the director's own pool of adjacent topics, each at a **distance** | *What is this one about?* (settling the dinner argument · eating what you actually craved · **and further out**: why the best-reviewed place is rarely the best meal) |
|
|
3429
3519
|
| **Frame** | 50 content frames | *What is the video the story of?* (`the rise of`, `then vs now`, `one decision that changed everything`) |
|
|
3430
3520
|
| **Awareness** | 5 stages | *What does the viewer already know?* (unaware → problem-aware → solution-aware → product-aware → most-aware) |
|
|
3431
3521
|
| **Angle** | 44 problem angles | *From which side do we talk about the problem?* (the hidden cost, the myth, the confession, the honest downside) |
|
|
3432
3522
|
|
|
3523
|
+
The subject axis carries a **distance**: how far from the offer the video is allowed to sit. Distance is what produces genuinely different videos rather than differently-shaped ones, and the **turn back to the offer** is what keeps distance from becoming irrelevance — see *Distance* below. Freeze the subject at distance zero and no amount of frames or angles will save the list.
|
|
3524
|
+
|
|
3433
3525
|
A **frame** is a reusable shape for a video's subject — not a hook line, not a script. You pour the director's topic into it: `the rise of` + `dropshipping supplements` → *"The rise of the supplement dropship store"*. An **angle** decides which face of the problem that video shows; the same frame at two angles is two genuinely different videos. An **awareness stage** decides who it is allowed to be for — the same idea aimed at a stranger and at a buyer needs different first ten seconds and a different ask.
|
|
3434
3526
|
|
|
3435
3527
|
**The grid is where the volume comes from.** 50 frames × 44 angles × 5 stages, over a pool of adjacent subjects, is not a number to take literally, but it is why "I need 30 videos this month" is an easy ask, not a hard one. Vary **two** axes across a set and it never reads as repeats. Vary only one and it does — and the axis most often left frozen is the subject, because the offer line is right there and it is easy to paste.
|
|
@@ -3482,6 +3574,72 @@ Now the same four frames are four videos:
|
|
|
3482
3574
|
|
|
3483
3575
|
**One rule to check the finished list.** Read the titles with the frames covered up. If what is left is the same sentence 20 times, you produced one idea, not 20.
|
|
3484
3576
|
|
|
3577
|
+
### Distance — going further than the niche, safely
|
|
3578
|
+
|
|
3579
|
+
The pool above stays inside the niche, and the ❌ above bans a different business outright. **That ban is correct as long as nothing carries the video home.** Write the turn back to the offer first, and you can go considerably further out — which is where the strongest ideas live, because the viewer did not come to the feed looking for your category.
|
|
3580
|
+
|
|
3581
|
+
> "Why smart people make less money than dumb people… because nobody hands them the lessons — Prism gives you a lifetime of them from the best biographies, five minutes each."
|
|
3582
|
+
|
|
3583
|
+
That is a video about status and income, for a book summary app. It is not off topic, because the last clause is written before the video is.
|
|
3584
|
+
|
|
3585
|
+
**The dial.** Give every subject a distance, and quota the spread — do not ask for "variety", which returns everything at the mode:
|
|
3586
|
+
|
|
3587
|
+
| d | What it is | Share of 12 |
|
|
3588
|
+
|---|---|---|
|
|
3589
|
+
| 1 | Next door. The viewer sees it is adjacent to the category. | 5 |
|
|
3590
|
+
| 2 | The wider human subject. The category is one example inside it. | 5 |
|
|
3591
|
+
| 3 | Far. The video never names the category until the last line. | 2 |
|
|
3592
|
+
|
|
3593
|
+
Far-heavy was tried and rejected. At 2 / 4 / 6 you get a video about IKEA's wordless instructions for a book app, and a viewer needs two steps of explaining before anything sells.
|
|
3594
|
+
|
|
3595
|
+
**The one step rule.** The link from the subject to the offer must be ONE step. If a viewer needs two steps to see how they connect, the subject is too far — take a nearer one. A viewer will not do the work.
|
|
3596
|
+
|
|
3597
|
+
**The turn — the field that makes distance safe.** Every idea carries the line the video says when it stops being about the subject and reaches the offer:
|
|
3598
|
+
|
|
3599
|
+
- It NAMES the client, by name.
|
|
3600
|
+
- It states the concrete thing the viewer GETS.
|
|
3601
|
+
- It follows on in ONE move, with no new argument.
|
|
3602
|
+
- 12 to 25 words, sayable out loud.
|
|
3603
|
+
|
|
3604
|
+
| | |
|
|
3605
|
+
|---|---|
|
|
3606
|
+
| ✅ | "Churchill demanded one page. Prism applies the same discipline to whole books — habits, business, history, five minutes each." |
|
|
3607
|
+
| ❌ | "Nobody gives anything an hour any more." |
|
|
3608
|
+
|
|
3609
|
+
The second names nobody and gives nothing. It is a theme, not a turn. **If you cannot write the turn, drop the subject** — do not soften it, and do not pad the count. That discard rule is also what protects a thin client: some offers have a deep cultural hinterland and some do not, and a short honest pool beats a long strained one.
|
|
3610
|
+
|
|
3611
|
+
This is Gate 3 of the `hooks` harness — offer adjacency — applied one level up, at the subject rather than the hook.
|
|
3612
|
+
|
|
3613
|
+
**The proof type — or the whole list becomes trivia.** Demand concrete detail and every idea arrives as "in 1978, a man…". That is the same flatness in a new costume. Declare what kind of concrete thing carries each subject, and spread across all five:
|
|
3614
|
+
|
|
3615
|
+
| Proof type | What it is |
|
|
3616
|
+
|---|---|
|
|
3617
|
+
| `historical` | a dated event, a person from the past, an old record |
|
|
3618
|
+
| `present_number` | a number true today: a price, a count, a duration, a share |
|
|
3619
|
+
| `ranking` | an order: best to worst, a tier list, a bracket, a league table |
|
|
3620
|
+
| `named_person` | someone the audience can look up, alive now |
|
|
3621
|
+
| `everyday_observation` | something the viewer can check in their own life, no source |
|
|
3622
|
+
|
|
3623
|
+
Use each at least once; use none more than three times.
|
|
3624
|
+
|
|
3625
|
+
**Flag what needs checking.** A distant subject means dates, numbers and named people, and a video that states a false fact is the *client's* problem once it is posted. Mark every idea that carries one, and put the instruction on the brief: *"Confirm this before you film. If you cannot confirm it, film the story version and drop the claim."*
|
|
3626
|
+
|
|
3627
|
+
### Prompting a model for range
|
|
3628
|
+
|
|
3629
|
+
The clauses below are what actually move the output. Copy the wording, not the intent.
|
|
3630
|
+
|
|
3631
|
+
1. **Define the unit, and say what it is not.** *"It is not the product. It is not the problem. It is not the product's own category."* A negative definition beats three adjectives.
|
|
3632
|
+
2. **Make concreteness a required field, not an adjective.** *"Every item must carry `objects`: named people, years, numbers, records, institutions. An item with only abstract nouns is rejected."* A required field is a constraint; an instruction in prose is a suggestion.
|
|
3633
|
+
3. **Quota the axis you want spread on.** *"5 at distance 1, 5 at distance 2, 2 at distance 3."* Never "spread them out".
|
|
3634
|
+
4. **Withhold the product until last.** Order the prompt audience → what they already care about → offer. A prompt that opens with "this is a book summary app" returns fifty videos about book summaries whatever the rules underneath say. Free, and it changes the whole answer.
|
|
3635
|
+
5. **Ban the obvious answer by name.** *"A far title may not use these words: book, summary, audiobook."* The model's first three ideas are always the banned ones. Read the ban list off the offer, per client.
|
|
3636
|
+
6. **Require the return path, then push distance hard.** The turn, plus the discard rule.
|
|
3637
|
+
7. **Pass the titles already written, and forbid a repeat.** *"Do not write a video that would be the SAME VIDEO as any of them. A different frame on the same subject is the same video."* The only dedup that works across calls.
|
|
3638
|
+
|
|
3639
|
+
**Three that kill range:** one call per item — N calls are N samples from one mode, so generate a SET in one call · "be creative" — zero constraint, and the model already believes it is · an example with no anti-example — always show the flat version beside the good one.
|
|
3640
|
+
|
|
3641
|
+
**Count the result in code, not in the prompt.** The model proposes; a quota checked afterwards is the only one that holds. Report the misses by name and let the director decide — a counter that silently repairs is a counter nobody trusts.
|
|
3642
|
+
|
|
3485
3643
|
### The 50 frames
|
|
3486
3644
|
|
|
3487
3645
|
**Arc & subject frames — what the video is the story of**
|
|
@@ -3750,6 +3908,9 @@ The frame also suggests how to build it, which saves a planning round:
|
|
|
3750
3908
|
- **Do not treat the frame as the hook.** "The history of X" spoken flat at `start:0` is a banned opener shape — the hook still has to name a situation. `references/hooks-and-virality.md`.
|
|
3751
3909
|
- **Do not ship a set that sits at one awareness stage.** It is the single most common defect in a month of posts, and neither the frame nor the angle can repair it. Label the stage on every idea so the imbalance is visible before production, not after.
|
|
3752
3910
|
- **Do not sell at stage 1 or 2.** The ask belongs to the stage. A link in a stage-1 video costs the reach that made the video worth making.
|
|
3911
|
+
- **Do not hand over a distant idea with no turn written.** A video about Napoleon's field library, for a book app, is a great video and a dead one until somebody writes the line that reaches the offer. Write the turn with the idea, in the same breath, or drop the idea.
|
|
3912
|
+
- **Do not let the whole list become dated trivia.** "Be concrete" pushes a model straight into "in 1978, a man…". Fifty history videos is one idea fifty times, exactly like the frozen subject. Spread the proof types.
|
|
3913
|
+
- **Do not state a date, a number or a record without flagging it for checking.** The editor films what the brief says. A wrong figure is the director's problem the moment it posts, not the writer's.
|
|
3753
3914
|
|
|
3754
3915
|
## Primitive: image_remove_background
|
|
3755
3916
|
|
package/SKILL.md
CHANGED
|
@@ -69,6 +69,7 @@ vidfarm templates search "ugc testimonial" # find a published temp
|
|
|
69
69
|
vidfarm fork <templateId> # → forkId
|
|
70
70
|
vidfarm pull <forkId> --dir ./work # composition.html + .harness/ to disk
|
|
71
71
|
vidfarm public-raws --category greenscreen --limit 20 # source footage, $0
|
|
72
|
+
vidfarm public-raws --query actor_<uuid> # same person's other clips (id is in each card's summary)
|
|
72
73
|
# edit ./work/composition.html — parse, edit, re-serialize the DOM; never string-concat
|
|
73
74
|
vidfarm tts "Your cold emails are landing in spam." --dir ./work # free local voice
|
|
74
75
|
vidfarm qa ./work # optional free local slop check
|
|
@@ -93,6 +94,10 @@ vidfarm publish <forkId> # push edits back to th
|
|
|
93
94
|
| "Make a graphic / sticker / illustration" in cost mode `minimize` | free stock → IconScout → `vidfarm mask` → **generate it with YOUR OWN image tool if this subscription includes one** (Antigravity / Gemini CLI, Codex / ChatGPT — Claude Code has none): `vidfarm agent-image "<subject>"`. $0, and no `--yes` needed | `references/agent-included-imagegen.md` |
|
|
94
95
|
| A free plan / a 402 / cost mode `minimize` on any search or download | `vidfarm browser setup`, then `vidfarm browse videos\|images\|news\|page "<q>"`. Never answer a sourcing ask with "that needs a paid plan." | `references/browser-harness.md` |
|
|
95
96
|
| "Make a video about what just happened" | two stages: `vidfarm news-search "<topic>" --fresh w` for the STORY, then `video-search` for the VISUALS | `vidfarm.cc/experimental/google-news-to-video.md` |
|
|
97
|
+
| "Recaption this meme" / "make a meme for our product" / "a reaction video" | cast a MemeScreens raw for the caption's **verb** (`vidfarm public-raws --category greenscreen`), key it onto a background world, one static caption. $0 | `vidfarm.cc/experimental/meme-recaption.md` |
|
|
98
|
+
| "A UGC ad for my app" / "someone reacting, then the app" / a client's app demo you have to make watchable | three streams cut against each other: `ugc-reaction` raws (ONE actor, via `actor_<uuid>`), a Display Greenscreen device carrying their real demo on its screen, and the demo. Ships a voiceover-only cut to publish + a voiceover+music cut to review | `vidfarm.cc/experimental/ugc-reaction-greenscreen.md` |
|
|
99
|
+
| "A tips slideshow / carousel" / "5 tips for X" / a listicle post | N still slides at exactly 3.0s, die-cut cutouts on a page or a photo, a literal "Tips for…" cover, ONE slide shilled from the middle. **The slides are the deliverable; the MP4 is the preview.** $0–$0.15 | `vidfarm.cc/experimental/sticker-slideshow-tips.md` |
|
|
100
|
+
| "A confession / truth bomb over a POV or ambient scene" / "just text on a video" / a text-story post | ONE unbroken take + ONE static block of unplated type. Nothing animates, nothing is cut. `duration = words / 8` (min 8s) so one play reaches the HALFWAY mark and they loop to finish. Cast the scene DARK so the type needs no plate. $0 | `vidfarm.cc/experimental/wall-text-pov-ugc.md` |
|
|
96
101
|
| "Download this video from `<url>`" | `vidfarm download-video <url>` (paid). Free plan gets a 402 — `vidfarm browse page "<url>"` and save it from their Chrome yourself, else have them download it, then `vidfarm put-file`. Never answer "I can't." | `references/browser-harness.md` |
|
|
97
102
|
| "Turn this thread / subreddit / profile into a video" | `vidfarm recycle <source>` — returns the raw decomposition, unranked; you pick the hook (paid) | `references/assets-and-sourcing.md` |
|
|
98
103
|
| "Create an avatar / spokesperson / talking head" | `vidfarm avatar "<who>" --say "<line>"` — a greenscreen talking-head video, keyed in the same job | `references/primitives.md` |
|
|
@@ -146,7 +151,7 @@ The standards themselves are not optional. `vidfarm qa <dir>` is — it is a fre
|
|
|
146
151
|
- **Write the four charges before the timeline.** Hook: first line is a complete clause naming a situation, on screen at `start:0`. Loop: one open question by 0:10, closing inside this video. Payoff: shown, not summarized. Bait: one ask, final beat. Banned openers — throat-clearing, a logo, a title card, a fade from black.
|
|
147
152
|
- **No HTML slop.** Test: could you have made this element inside TikTok's own editor? If you reached past that — a padded capsule, border, gradient fill, blur panel, card, CTA "button", benefit chips, feature grid — cut it. Nothing in a video is clickable. Emphasize with size, weight, caps, an accent colour, or a drawn mark.
|
|
148
153
|
- **No layout templates — judge the whole frame.** The stack is the tell, not the box: headline + subheadline + CTA centred in a well is a landing page even with every border, fill and capsule stripped off. Banned at frame level — a modal/dialog floating over a dimmed or blurred backdrop, a full-frame wash used to stage a floating block, nav/hero/feature-row sections, a blurred website screenshot as the background plate, document margins. Unstack it into time: hook at t=0, support line on the next cut, CTA spoken or a bare caption. Are you arranging words relative to each other, or relative to the picture? Two text runs at once is the ceiling.
|
|
149
|
-
- **Captions**: imported display font
|
|
154
|
+
- **Captions**: an imported display font — **only 5 are allowed**: TikTok Sans, Montserrat (700–900 default), Abel, Source Code Pro, Yesteryear; anything else (Inter/Roboto/Arial/Georgia/a brand font) falls back to a web sans at render. The regime on one page, with a rendered specimen of each: <https://vidfarm.cc/fonts>. Size ~36–64px on a 1080 frame, inside the 8%–85% safe zone, and within that band placed in the **emptiest part of the frame**. Long narration pages into 3–5-word cues. Exactly one of four backgrounds: `outline`, `plain`, active-word `spotlight`/`karaoke`, or a tight `highlight-solid` band.
|
|
150
155
|
- **The first frame is the thumbnail.** A real visual and the hook words at `start:0`. No fade-up, no entrance transition on the first clip. Check with `vidfarm stills <dir> --at 0`.
|
|
151
156
|
- **Cut ruthlessly.** First assembly is 30–50% too long. Delete every beat that the video survives without, then ripple the hole closed. Length is an output, not a target.
|
|
152
157
|
- **Review the whole video, never one frame.** Tile ~12 stills with `vidfarm stills <dir> --sheet` and read it as an image — one type scale, one palette, deliberate pacing, clean joins. Verify audio by measurement (~12–15 dB speech-over-bed, peak <0 dBFS). Report what you measured separately from what you judged. Your own "looks good" is the least reliable signal here.
|
|
@@ -191,4 +196,6 @@ Otherwise fetch `https://vidfarm.cc/skill-pack/vidfarm/files/<path>`. Load one f
|
|
|
191
196
|
| `recipes/*.md` | `find-and-fork-template` · `retheme-template` · `local-edit-render-approve` · `onboard-a-new-director` · `bulk-scripting-with-a-harness` · `cutout-graphics-for-explainers` |
|
|
192
197
|
| `harnesses/README.md` | anything harness-shaped — start here. Bases: `short-form` · `hooks` · `ugc-testimonial` · `explainer` · `product-demo` · `product-explainer` |
|
|
193
198
|
|
|
194
|
-
Also served at `vidfarm.cc`: `/experiments.md` (ad testing), `/marketplace.md` (
|
|
199
|
+
Also served at `vidfarm.cc`: `/experiments.md` (ad testing), `/marketplace.md` (the marketplace manual — routes you to one of the two side harnesses below), `/marketplace-buyer.md` (**buyer side**: commission videos from the crowd), `/agentic-clipper.md` (**worker side**: "Agentic Clipper" mode — one orchestrator on a long-horizon earning mission, one subagent per task), `/update.md` (upgrade runbook), `/experimental` (format harnesses under live testing — the index a clipper routes tasks against), `/skill/vidfarm-platform` (architecture), `/skill/hyperframes` (composition-authoring craft — route broad "make me a video" asks here first).
|
|
200
|
+
|
|
201
|
+
**The two marketplace harnesses never mix.** A buyer's agent must never load the clipper loop and a clipper must never load the buyer loop. If the user's side is unclear, ask: *"Do you want videos made for you, or do you want to get paid to make videos for other people?"*
|
package/clipper.md
CHANGED
|
@@ -5,6 +5,15 @@ description: Run an agentic clipper loop — earn side income by having your own
|
|
|
5
5
|
|
|
6
6
|
# /clipper — the agentic clipper loop
|
|
7
7
|
|
|
8
|
+
> **This file is the beginner walkthrough for ONE gig**: phase by phase, with a scaffolded project
|
|
9
|
+
> the user can read six months later. It is the right file when someone hands you a single invite
|
|
10
|
+
> link and asks you to set it up with them.
|
|
11
|
+
>
|
|
12
|
+
> **For the full marketplace mission — one orchestrator working MANY machines over weeks, a subagent
|
|
13
|
+
> per task, format routing, risk-scoring the gigs, and the earnings ledger — read
|
|
14
|
+
> `https://vidfarm.cc/agentic-clipper.md`.** That is what "run in agentic clipper mode" means.
|
|
15
|
+
> This file and that one agree; that one is the wider loop and this one is the first gig inside it.
|
|
16
|
+
|
|
8
17
|
**You are the user's own AI agent, running on their computer.** Your job is to set up and then
|
|
9
18
|
run a loop that earns them money:
|
|
10
19
|
|
|
@@ -266,6 +275,19 @@ This is ordinary Vidfarm director work — **the full method is in
|
|
|
266
275
|
<https://vidfarm.cc/skill.md> and the director skill it points to. Follow that, not a shortcut.**
|
|
267
276
|
What matters for a clipper loop specifically:
|
|
268
277
|
|
|
278
|
+
**First, decide what FORMAT the task is, and follow that format's harness.** A product explainer
|
|
279
|
+
built like a meme recaption gets rejected, and so does the reverse. Read the live index at
|
|
280
|
+
<https://vidfarm.cc/experimental> and pick — a product/feature introduction routes to
|
|
281
|
+
`unique-product-explainers.md`, a recaption or reaction routes to `meme-recaption.md`, a timely
|
|
282
|
+
event routes to `google-news-to-video.md`. Nothing fits? Fall back to a CLI base
|
|
283
|
+
(`vidfarm harness list`) — and if no harness fits at all, **freestyle it against the best practices
|
|
284
|
+
in <https://vidfarm.cc/skill.md>.** That is a normal outcome, not a failure: harnesses reproduce a
|
|
285
|
+
*known* format, and a task outside every known format is still one you can deliver well. Never force
|
|
286
|
+
a task into the wrong harness, and never decline one just because no harness matched.
|
|
287
|
+
**If the task payload carries a `format_harness` field, that wins over your own judgement** — the
|
|
288
|
+
buyer chose. Record which harness you used (or `freestyle`), so over a month you learn which formats
|
|
289
|
+
actually get approved for this user.
|
|
290
|
+
|
|
269
291
|
**Set the cost posture once, at setup:**
|
|
270
292
|
|
|
271
293
|
```bash
|