@officexapp/vidfarm-devcli 0.21.31 → 0.21.32
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 +1 -0
- package/.agents/skills/vidfarm/recipes/bulk-scripting-with-a-regime.md +12 -0
- package/.agents/skills/vidfarm/recipes/local-edit-render-approve.md +2 -1
- package/.agents/skills/vidfarm/references/automation-and-local-dev.md +13 -1
- package/.agents/skills/vidfarm/references/core-workflows.md +62 -5
- package/.agents/skills/vidfarm/references/primitives.md +75 -32
- package/SKILL.director.md +165 -39
- package/SKILL.md +2 -0
- package/dist/src/cli.js +486 -3
- package/dist/src/devcli/dedupe-local.js +209 -0
- package/dist/src/lib/dedupe-recipe.js +420 -0
- package/package.json +3 -1
|
@@ -319,6 +319,7 @@ Choose the narrowest path that satisfies the request.
|
|
|
319
319
|
4. If the task is “find footage” or “use our existing assets,” read `references/assets-and-sourcing.md`.
|
|
320
320
|
4b. If the task is **“download this video/audio off a website”** (a pasted YouTube / TikTok / Instagram / X post URL the user wants the actual file from), Vidfarm does that for you on a **paid plan** — `POST /api/v1/primitives/videos/download` (or `/audio/download`), devcli `vidfarm download-video <url>` / `vidfarm download-audio <url>`. **Free plan → do not call it; walk the user through opening the URL in Chrome and downloading it from the page, then `vidfarm put-file` the local file in for $0.** Details in `references/assets-and-sourcing.md` → `references/primitives.md`.
|
|
321
321
|
4c. If the task is **“turn this Reddit/X thread, subreddit, or account into a video”** — “tweet to TikTok”, “Reddit to TikTok”, “make a video from this thread”, “what are the top comments saying” — run `vidfarm recycle <source>` (or `POST /api/v1/primitives/social/recycle`) with the URL. It **decomposes** the source into raw JSON (text, comment tree, media URLs, author pics, stats) and hands it back unranked so YOU pick what to remix. **Paid plan; `max_records` is the spend ceiling.** Brokers the reddit-lead-gen / x-lead-gen OfficeX apps, so it waits out their async job for you. Details in `references/assets-and-sourcing.md` → `references/primitives.md`.
|
|
322
|
+
4d. If the task is **“post this again / to several accounts / on another platform”**, or you are about to publish or bulk-produce at all — that is **deduplication**. Run `vidfarm dedupe <mp4> [--variants N]` on the **exported file** (free, local ffmpeg, no re-render), then approve/schedule each variant. **Ask the operator whether they want deduplicated copies, and how many, BEFORE the render/bulk run** — deciding after means paying for a second render. Details in `references/core-workflows.md` → *Deduplicate before you publish* and `references/primitives.md` → *Primitive: media_dedupe*.
|
|
322
323
|
5. If the task is scripted, local, CI-driven, or `vidfarm serve`-based, read `references/automation-and-local-dev.md`.
|
|
323
324
|
6. If the task explicitly asks for a primitive or needs specialized generation/transcription work, read `references/primitives.md`.
|
|
324
325
|
7. If the task is the MARKETPLACE (ordering videos from specialist agents): browsing is web-only for paying customers — send the human to https://vidfarm.cc/marketplace, never render it locally. Placing/listing orders is the thin REST wrapper in `references/core-workflows.md` (§ Marketplace); anything deeper on a gig (inbox, proofs, payouts) needs the external Dollar Platoon skill — `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` — the same way FlockPoster work beyond scheduling needs `npx skills add https://github.com/OfficeXApp/flockposter-skill`.
|
|
@@ -52,6 +52,18 @@ done
|
|
|
52
52
|
|
|
53
53
|
`vidfarm qa` still exits 0 on findings — the `jq -e` line is **your** gate, in your script, made explicit. Keep it that way; a hard gate inside the tool would quietly train the loop to stop trying anything unusual.
|
|
54
54
|
|
|
55
|
+
### 5b. Deduplicate the renders (ask first)
|
|
56
|
+
|
|
57
|
+
A bulk run exists to put volume across accounts and platforms — which is exactly the shape a platform's duplicate-content detector flags. **Ask the director up front: "deduplicated copies for posting, and how many slots?"** Ask before the loop, not after: dedupe is a post-render ffmpeg pass, so answering early keeps it at *render once → dedupe N* rather than a second render per slot.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
for MP4 in renders/*.mp4; do
|
|
61
|
+
vidfarm dedupe "$MP4" --variants "$SLOTS" --seed "$(basename "$MP4" .mp4)" --out-dir ./posts
|
|
62
|
+
done
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Free, offline, no wallet. Variant 1 is the `standard` preset as authored (skew 2%, zoom 3%, rotate 2°, speed +2%, saturation +4%); later variants get jittered magnitudes and flipped signs, so they differ from the original **and from each other**. One variant per account — two accounts posting the same variant defeats the point. Reuse one `--seed` per source so the batch is reproducible.
|
|
66
|
+
|
|
55
67
|
### 6. Answer the review items — don't skip this
|
|
56
68
|
|
|
57
69
|
The regime's `- [ ]` checklist comes back on every run because the CLI *can't* settle it. Machine checks catch a 13-word hook or a black first frame; only you can answer "is this variant genuinely different from its siblings?" or "can the viewer guess the withheld answer?" **Report both halves honestly**: what the machine checked, and what you judged. A batch report claiming a clean pass on the judgment half is worse than no report.
|
|
@@ -9,7 +9,8 @@ Use this when a coding agent is doing the work locally or the user wants a repro
|
|
|
9
9
|
5. Validate with `vidfarm lint` or `vidfarm stills` when useful. **Always look at `vidfarm stills ./work --at 0`** — that frame becomes the thumbnail, so it must not be black, empty, or mid-fade.
|
|
10
10
|
6. **QA before you render: `vidfarm qa ./work`.** Free, instant, devcli-only. It blocklists HTML slop (CTA buttons, benefit chip rows, frosted cards, gradient text, web-page classes/fonts), checks the caption font regime + safe zone, and flags a blank/fading first frame (the thumbnail). Feedback only — exit 0 even on findings, never automatic — but it catches the #1 tell of an agent-made video, so run it on every production. Fix what's real, ignore what's a deliberate style call, then render.
|
|
11
11
|
7. Render with `vidfarm render <forkId> --dir ./work --wait`.
|
|
12
|
-
8.
|
|
12
|
+
8. **Ask about deduplication before you approve** — "is this going out more than once (several accounts, another platform, a re-post later)?" If yes, run `vidfarm dedupe ./final.mp4 [--variants N]` on the **exported** MP4 (free, local ffmpeg, no re-render) and approve each variant separately. Asking here rather than after publication is what avoids paying for a second render. See `references/core-workflows.md` → *Deduplicate before you publish*.
|
|
13
|
+
9. Approve the finished MP4 with `vidfarm approve --video <url|./final.mp4> --caption "..."`. This prints the shareable `share_url`.
|
|
13
14
|
|
|
14
15
|
**Approving a locally rendered file → cloud preview link.** Approve takes media by **URL**, not bytes, and an approved post is **permanent** — so the local MP4 must land in **durable My Files**, not the 30-day temp store (a temp video would 404 the share page after 30 days). The devcli presigns, PUTs the bytes **direct to S3**, finalizes, then approves with that durable URL — so `vidfarm approve --video ./final.mp4` handles files up to **200 MB**, bypasses the ~6 MB Lambda request-body limit, and the share link never breaks. By raw REST: `POST /api/v1/user/me/attachments/presign` → PUT to the returned S3 URL → `POST /api/v1/user/me/attachments` (finalize) → pass the returned `viewUrl` in the approve `media` array. Do not multipart-POST a big file to `.../attachments/upload` against the cloud host (Lambda-bound, ~6 MB cap). Add `vidfarm approve --temp` only when you want a disposable 30-day preview.
|
|
15
16
|
|
|
@@ -78,6 +78,17 @@ for VARIANT in "${VARIANTS[@]}"; do
|
|
|
78
78
|
done
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
**Before you start a bulk run, ask the operator whether the output should be deduplicated, and for how many posting slots.** A bulk run's whole point is volume across accounts/platforms, which is exactly the shape platforms flag as duplicate content. Dedupe is a post-render ffmpeg pass, so asking up front is what keeps it at *render once → dedupe N* instead of a second render per slot:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# after the loop: one distinct copy per posting slot, free and offline
|
|
85
|
+
for MP4 in renders/*.mp4; do
|
|
86
|
+
vidfarm dedupe "$MP4" --variants "$SLOTS" --seed "$(basename "$MP4" .mp4)" --out-dir ./posts
|
|
87
|
+
done
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Reuse one `--seed` per source so a batch is reproducible, and post each variant to a **different** account — two accounts posting the same variant defeats the point. See `references/core-workflows.md` → *Deduplicate before you publish*.
|
|
91
|
+
|
|
81
92
|
`vidfarm qa` still exits 0 on findings — the gate above is the *script's* choice, made explicit with `jq`, not a behavior change in the tool. Keep it that way: an agent that can't ship a deliberately weird variant will quietly stop trying weird variants.
|
|
82
93
|
|
|
83
94
|
This section is for a **desktop/local coding agent**, not the web copilot. A local Codex/Claude agent may use its shell and filesystem to write JavaScript/TypeScript/Python/shell scripts, fetch every API page, join and score catalog/library data, calculate statistics, emit CSV/JSON/Markdown reports, manipulate composition DOM files, and run iterative render/inspection loops. The web copilot cannot inherit those abilities from this document: it may only call its declared tools and bounded REST routes. If web chat prepares work for this flow, consume its My Files handoff document as input; do not claim the web chat itself executed the script.
|
|
@@ -214,6 +225,7 @@ The licensed harness also carries the **generative build workflow** guidance (ch
|
|
|
214
225
|
| `vidfarm inpaint <image> --mask <png> --prompt "…" [--region "label=…"] [--ref …] [--out <f>]` | `POST /api/v1/primitives/images/inpaint` (polls job) | masked image EDIT — replace ONLY the transparent-mask region, keep everything else (devcli twin of the /inpaint page) |
|
|
215
226
|
| `vidfarm create-overlay "<subject>" [--key-color #00FF00] [--aspect-ratio 1:1] [--place <dir>] [--out <f>]` | `POST /api/v1/primitives/images/create-overlay` (polls job) | **Vox-style** transparent OVERLAY — AI image on a forced key-color background, chroma-keyed out in one job → ready-to-composite transparent PNG |
|
|
216
227
|
| `vidfarm remove-greenscreen <image\|video> [--preset green\|blue\|white\|black\|digital-green\|magenta] [--key-color #00FF00] [--tolerance 0.3] [--local] [--gif] [--out <f>]` | `POST /api/v1/primitives/remove-greenscreen` (polls job) | chroma-key a FLAT solid background → transparent PNG/WebP (image) or WebM/VP9-alpha (video); auto-detects media kind. `--local` runs it FREE in-process (sharp/ffmpeg, no wallet); default cloud is billed at real compute × 1.2. **`--gif` writes a transparent GIF instead** (ANIMATED for a clip; `--gif-fps`/`--gif-width`/`--gif-alpha`) — local-only, 1-bit alpha, for GIF-only sticker surfaces; prefer PNG/WebP/WebM for compositing. Aliases: `greenscreen`, `remove-background-greenscreen`. |
|
|
228
|
+
| `vidfarm dedupe <video\|image\|url> [--preset light\|standard\|strong] [--variants N] [--seed <s>] [--zoom/--rotate/--skew/--speed/--saturation/--hue/--noise/--flip] [--local\|--cloud] [--out <f>\|--out-dir <d>]` | **local, free, ffmpeg-only** by default (no job); `--cloud` = `POST /api/v1/primitives/media/dedupe` (polls job) | **DEDUPLICATION — the publish-safety pass.** Makes a finished render read as a NEW upload to a platform's duplicate-content detector, invisibly to a viewer. Default preset `standard` = skew 2%, zoom 3%, rotate 2°, speed +2%, saturation +4%, plus contrast/brightness/hue/grain, a container-metadata strip and a per-variant CRF walk. **Runs on the EXPORTED file — never re-render for this.** `--variants N` mints N copies that differ from the original AND from each other (jittered magnitudes, alternating signs), one per account/posting slot; `--seed` makes a batch reproducible. A rotate forces a bigger centre-crop to hide the black corners (~6.7% on a tall frame at 2°) and says so — pass `--rotate 0` when framing matters more. `--flip` is the strongest single knob but visibly reverses on-screen text. **Ask the operator whether they want this BEFORE publishing or bulk-producing.** Aliases: `dedup`, `deduplicate`, `uniquify`. |
|
|
217
229
|
| `vidfarm cutout <image\|url> [--generate "<prompt>"] [--preset green] [--pad <px>] [--alpha-threshold <n>] [--no-trim] [--output-format png\|webp] [--out <f>]` | **local, free, ffmpeg-only** (no job) — key + `alphaextract`/`cropdetect` trim | **The transparent explainer-STICKER maker.** Keys out the flat plate **and then shrinks the canvas to the cutout's true min width/height** (a 1024² mostly-empty plate → a snug sticker whose pixel size IS the subject) so you can scale/position it precisely. `--generate` AI-generates the graphic first on a matching chroma plate (that step is the billed image primitive), then keys+trims in one shot; without it, keys+trims a file/url you already have. **IMAGE-only** (a moving subject has no single bounding box — key a clip with `remove-greenscreen`). Prefer this over `create-overlay` locally: same idea, but free and auto-trimmed. `--pad` keeps transparent breathing room; `--json` reports final `width`/`height`/`area_reduced_pct`, plus `hole_pct`/`hollow` — the "the key ate the fill" check (outline-only art keys into a rim around a transparent hole; `--generate` prompts against it automatically, and the console prints a `Hollow:` warning with the fix). Alias: `sticker`. See recipe `cutout-graphics-for-explainers.md`. |
|
|
218
230
|
| `vidfarm mask <image\|url> [--crop x,y,w,h] [--flat <hex>] [--pad <px>] [--alpha-threshold <n>] [--no-trim] [--output-format png\|webp] [--keep-region <f>] [--out <f>]` | **local, free** (no job) — ffmpeg crop + ONNX matting (or ffmpeg chroma-key) + `cropdetect` trim | **Lift an illustration OUT of an image you already have** (infographic / poster / marketing graphic / brand sheet / screenshot) → snug transparent PNG, the same reusable explainer sticker `cutout` makes but with **$0 and zero AI generation** — the cost-saving move whenever source art exists. `--crop x,y,w,h` (pixels **or** %) isolates ONE element from a multi-illustration source before masking (re-run with different rects to grab each). Background removed by **local ONNX matting** (any/busy background) by default, or **`--flat <hexcolor>`** chroma-keys a solid fill for crisper edges (an infographic's cream/white paper); then trims to the subject's true min width/height. **IMAGE-only** (matte a clip with `remove-background`). Aliases: `isolate`, `extract`. See recipe `cutout-graphics-for-explainers.md` → "Mask from an image you already have". |
|
|
219
231
|
| `vidfarm sticker-pack [sheet\|url] [--generate "<theme>"] [--items "a,b,c"] [--count <n>] [--dry-run] [--gap <pct>] [--min-area <pct>] [--output-format png\|webp\|gif] [--out-dir <d>]` | **local, free, ffmpeg-only** (no job; only `--generate` bills, ONCE for the whole set) — key + alpha-channel segmentation + per-item trim | **The STICKER-PACK maker — the answer whenever a director asks for "a sticker pack" / prop set / icon set.** A pack is ONE greenscreen sheet holding every item, keyed once and then masked apart: 1/N the cost of N `cutout` calls, and the only way a cast stays on-style. Finds each item **automatically** by segmenting the keyed sheet's alpha into connected islands — no hand-measured `--crop` rects — and writes one snug transparent file per item (named from `--items`, reading order) plus a `stickers.json` manifest. `--dry-run` prints the detected boxes first; `--gap` merges (lower) or splits (raise) items that came out joined/broken; items have **no maximum size** — a full-frame landscape/backdrop is as valid a sticker as a 3% icon. **Plate color is chosen for you:** when generating it reads the subject and moves the plate off any hue the art uses (green → magenta → blue → black → white — a pack of leaves/frogs/money on GREEN would key holes through the art), and when splitting an existing sheet it DETECTS the plate from the sheet's four corners, so a red/purple sheet handed back from a web tool just works. Pin it with `--key-color`/`--preset`, or `--no-auto-key` for plain green. **The ART is made key-safe too:** the generation prompt is auto-appended with "closed, solidly filled shapes, no outline-only/hollow art, nothing in the plate hue or a near-shade, fully opaque, no glow/translucency" — the fix for stickers that come back as a rim around a transparent hole — and after keying each item reports `holes`/`hole_pct`/`hollow` (console `⚠ N% hollow` at ≥20%, plus `--json` and `stickers.json`). It **warns, never blocks** (a ring/frame/donut reads identically); re-generate with the fill clause, or lift that one item with `vidfarm mask --crop …`. `--output-format gif` emits 1-bit-alpha GIFs for GIF-only surfaces. IMAGE-only. Aliases: `stickers`, `sticker-sheet`. See recipe `cutout-graphics-for-explainers.md` → "A sticker pack". |
|
|
@@ -337,7 +349,7 @@ The four modes, quoted as **cost per finished video**. The first two are spend p
|
|
|
337
349
|
|
|
338
350
|
**All of it bills to the user's own AI provider keys (BYOK)** — the keys saved with `vidfarm add-provider-key <provider> <key>` or at **Settings → Bring your own keys** (<https://vidfarm.cc/settings/developer>). The model providers charge those keys directly; Vidfarm wallet credits only come into play when the user deliberately runs on the platform key instead of their own. So `minimize` isn't "cheap", it's **zero**: nothing reaches a paid key at all.
|
|
339
351
|
|
|
340
|
-
`vidfarm cost-mode <minimize|hybrid|rich-ai|pure-videogen>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, `cutout --generate` (only the generation step; a bare `cutout` on an existing graphic is free), and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`)
|
|
352
|
+
`vidfarm cost-mode <minimize|hybrid|rich-ai|pure-videogen>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, `cutout --generate` (only the generation step; a bare `cutout` on an existing graphic is free), and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`), `dedupe --cloud`. FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, `dedupe --local`, `cutout` on a file/url, all the file-editing verbs). `vidfarm media search` (the free stock catalog — pixabay/openverse/iconify music, SFX, images, icons, stock video) is also free and never gates.
|
|
341
353
|
|
|
342
354
|
- **minimize ($0 videos)** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative (which now includes the matching `vidfarm media search` for music/SFX/image/video). Use this to guarantee no surprise AI spend. Before paying to generate music, sound effects, or images, try `vidfarm media search "<meaning>" --type <bgm|sfx|image|vector|icon|video>` first — free royalty-free assets instead of a billed `music`/`generate` call. **Check the keyless sources first — Openverse (CC/CC0 music, SFX, images) and iconify (icons) need no account at all**, so they always work in `minimize`. Photos/vectors/stock-video need a **free Pixabay key** that **may already be saved** — check `vidfarm provider-keys` (or web **Settings → Bring your own keys** / <https://vidfarm.cc/settings/developer>) before assuming a short result means "no key." If absent, save one once: `vidfarm add-provider-key pixabay <key>` (free key from <https://pixabay.com/api/docs/>), the Settings surface, or hand it to the desktop AI agent to run that command.
|
|
343
355
|
- **minimize still gets CUSTOM images — via a free manual generator.** A refused `generate` is not the end of the road. Offer the user the manual loop (ask once, then make it the session default): **you write the prompt → they run it free in <https://meta.ai>, free-tier ChatGPT, or a free Hugging Face image Space (<https://huggingface.co/spaces>) → they hand the PNG back** via `vidfarm put-file ./sheet.png` or web **My Files**. Ask for **one sheet holding every graphic you need**, gridded on a **flat pure-green plate** (`#00FF00`), no text — one round trip instead of N, which saves the user's time and your tokens. Then split it locally for $0: `vidfarm mask ./sheet.png --crop x,y,w,h --flat "#00FF00" --out prop-a.png`, once per element (drop `--flat` and let local ONNX matting handle it if the tool ignored the green background). Full prompt template + loop: recipe `recipes/cutout-graphics-for-explainers.md` (“Free manual image-gen”).
|
|
@@ -137,6 +137,25 @@ Every publish creates an immutable version snapshot at `versions/<N>/composition
|
|
|
137
137
|
|
|
138
138
|
The Web UI **Render** button and devcli render both use this same endpoint. The fast `202` response includes the deterministic `expectedOutputPublicUrl` so a caller can store or pass along the final public S3 URL before the render has completed, then poll by `renderId` until `status` settles.
|
|
139
139
|
|
|
140
|
+
## Deduplicate before you publish (ASK FIRST)
|
|
141
|
+
|
|
142
|
+
Social platforms fingerprint every upload. If a render is going out **more than once** — to several accounts, to a second platform, or again in a few weeks — the later copies get suppressed as duplicate/reused content unless each one carries a distinct fingerprint.
|
|
143
|
+
|
|
144
|
+
**Before you render for publication, and before any bulk run, ask the operator: "Do you want deduplicated copies for posting? How many?"** Ask *then*, not after — dedupe runs on the exported MP4, so the order is **render once → dedupe N times**. Getting the answer up front is what stops you paying for a second render later.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# free, offline, no wallet, no re-render — the default path
|
|
148
|
+
vidfarm dedupe ./out/final.mp4 # one distinct copy
|
|
149
|
+
vidfarm dedupe ./out/final.mp4 --variants 3 --out-dir ./out/posts
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- Default preset `standard` = skew 2%, zoom 3%, rotate 2°, speed +2%, saturation +4%, plus contrast/brightness/hue/grain, a container-metadata strip and a per-variant CRF walk. Invisible to a viewer.
|
|
153
|
+
- `--variants N` mints N copies that differ from the original **and from each other** — one per account/slot. Two accounts posting the same variant defeats the point.
|
|
154
|
+
- `--preset light|standard|strong` for how hard to push; `--rotate 0` if the forced corner-hiding crop (~6.7% on a tall frame at 2°) matters more than fingerprint distance.
|
|
155
|
+
- Cloud equivalent: `POST /api/v1/primitives/media/dedupe` — same transforms, billed. See `references/primitives.md` → *Primitive: media_dedupe*.
|
|
156
|
+
|
|
157
|
+
Dedupe the **finished MP4**, then approve/schedule each variant separately. Do not dedupe the composition and re-render.
|
|
158
|
+
|
|
140
159
|
## Approve a finished post
|
|
141
160
|
|
|
142
161
|
A render produces a bare MP4 URL. **Approving** wraps that MP4 (plus caption, title, pinned comment, and any carousel slides) into a shareable preview page — the phone-mockup page a human opens to review and copy the post.
|
|
@@ -158,21 +177,59 @@ devcli: `vidfarm approve --video <mp4-url> --caption "..."` prints the `share_ur
|
|
|
158
177
|
|
|
159
178
|
## Schedule a post
|
|
160
179
|
|
|
161
|
-
|
|
180
|
+
A **destination** is one connected channel. There are exactly two kinds, and they are billed and owned differently:
|
|
181
|
+
|
|
182
|
+
- **`email`** — a verified email address on the vidfarm account. Delivered by vidfarm itself. Your vidfarm API key is the only credential involved.
|
|
183
|
+
- **`flockposter`** — a social account (TikTok/IG/X/…) connected through FlockPoster, a separate product. Needs the customer's FlockPoster key saved in vidfarm Settings → Channels. Vidfarm brokers the call; FlockPoster does the posting.
|
|
184
|
+
|
|
185
|
+
Every account always has at least one email destination: its own signup address, pre-verified, created automatically. So `destination_type: "email"` works on a fresh account with no setup at all.
|
|
186
|
+
|
|
187
|
+
**List destinations first** (or just send an address — see below):
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
GET /api/v1/user/me/channels
|
|
191
|
+
→ { "channels": [ { "destination_type": "email", "destination_id": "cus_…:default-email",
|
|
192
|
+
"handle": "operator@example.com", "status": "verified", "schedulable": true,
|
|
193
|
+
"accepts": ["cus_…:default-email", "operator@example.com", "operator"] } ],
|
|
194
|
+
"flockposter_connected": false, "flockposter_error": null }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Then schedule:
|
|
162
198
|
|
|
163
199
|
```
|
|
164
200
|
POST /api/v1/approved/posts/:postId/schedules
|
|
165
201
|
Content-Type: application/json
|
|
166
202
|
|
|
167
|
-
{ "destination_type": "flockposter" | "email", "destination_id": "<
|
|
203
|
+
{ "destination_type": "flockposter" | "email", "destination_id": "<see below>",
|
|
168
204
|
"scheduled_at": "2026-07-10T14:00:00Z", "timezone": "America/New_York", "additional_notes": "optional" }
|
|
169
205
|
```
|
|
170
206
|
|
|
171
|
-
|
|
207
|
+
**`destination_id` accepts whatever you have** — it is resolved server-side, so you do not need to look up an id:
|
|
208
|
+
|
|
209
|
+
| For `email` | For `flockposter` |
|
|
210
|
+
|---|---|
|
|
211
|
+
| the channel id (`cus_…:default-email`, or `email:<id>`) | the integration id |
|
|
212
|
+
| the address (`operator@example.com`) | the handle (`@brandname` or `brandname`) |
|
|
213
|
+
| the local part (`operator`) | the channel title |
|
|
214
|
+
| the channel title | the platform (`tiktok`) when exactly one is connected |
|
|
215
|
+
|
|
216
|
+
Matching is case-insensitive. When nothing matches, the `400` names what was searched for and lists the channels that do exist — read it rather than guessing another id.
|
|
217
|
+
|
|
218
|
+
Minimum 10-minute lead time. Response (`201`) is the schedule record.
|
|
219
|
+
|
|
220
|
+
**Managing a schedule** (all authed with the same vidfarm API key — an agent has full control):
|
|
221
|
+
|
|
222
|
+
- `GET /api/v1/approved/posts/:postId/schedules` — browse this post's schedules
|
|
223
|
+
- `PATCH /api/v1/approved/posts/:postId/schedules/:scheduleId` — reschedule; same body as POST. Cancels the queued send and re-queues it.
|
|
224
|
+
- `DELETE /api/v1/approved/posts/:postId/schedules/:scheduleId` — cancel the queued send
|
|
225
|
+
|
|
226
|
+
Cancel/reschedule reaches through to the provider (Resend for email, FlockPoster for social), so a `400` here means the send was **not** stopped — the email or post is still queued. Never report a failed cancel as cancelled.
|
|
227
|
+
|
|
228
|
+
Schedules created outside vidfarm (`managed_by: "external"`) are read-only here and return `409`; change those in FlockPoster.
|
|
172
229
|
|
|
173
|
-
devcli: `vidfarm schedule <postId> --at <iso> --to <
|
|
230
|
+
devcli: `vidfarm channels` lists destinations, `vidfarm schedule <postId> --at <iso> --to <destination> [--type flockposter|email]` schedules, `vidfarm schedules <postId>` browses.
|
|
174
231
|
|
|
175
|
-
Deeper FlockPoster work (channel management, direct posting/analytics outside vidfarm's schedule wrapper) is FlockPoster's own API — grab its skill first: `npx skills add https://github.com/OfficeXApp/flockposter-skill` (mirrored as `vidfarm skills add flockposter`).
|
|
232
|
+
Deeper FlockPoster work (connecting accounts, channel management, direct posting/analytics outside vidfarm's schedule wrapper) is FlockPoster's own API, not vidfarm's — grab its skill first: `npx skills add https://github.com/OfficeXApp/flockposter-skill` (mirrored as `vidfarm skills add flockposter`).
|
|
176
233
|
|
|
177
234
|
## Marketplace — order videos from specialist agents
|
|
178
235
|
|
|
@@ -237,38 +237,83 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/videos/remove-captions" \
|
|
|
237
237
|
|
|
238
238
|
## Primitive: media_dedupe
|
|
239
239
|
|
|
240
|
-
|
|
240
|
+
**Deduplication** — make a finished image or video read as a **new upload** to a social platform's duplicate-content detector, while staying invisible to a viewer. Platforms fingerprint every upload; posting the same render twice (across accounts, or again next month) gets the later copy suppressed as duplicate/reused content. This primitive nudges geometry, color, timing and grain by a couple of percent, strips container metadata, and walks the encoder's CRF, so each copy carries a distinct fingerprint.
|
|
241
|
+
|
|
242
|
+
**Ask the operator before you publish or bulk-produce.** Dedupe runs on the EXPORTED file, so the correct order is *render once → dedupe N times*, never *render N times*. Deciding up front avoids paying for a second render later.
|
|
241
243
|
|
|
242
244
|
- `POST /api/v1/primitives/media/dedupe`
|
|
243
245
|
- Body: `{ "tracer": "...", "payload": { ...fields... }, "webhook_url"?: "..." }`
|
|
244
246
|
- Note: webhook delivery is not yet active — `webhook_url` is accepted and persisted on the job but never fired. Poll the job endpoints (`GET /api/v1/primitives/jobs/:jobId`) for completion.
|
|
245
|
-
- Response: standard primitive job. Poll to completion, then read `primary_file_url` (also `video.file_url` for MP4 or `image.file_url` for stills)
|
|
246
|
-
- Billing: metered
|
|
247
|
+
- Response: standard primitive job. Poll to completion, then read `primary_file_url` (also `video.file_url` for MP4 or `image.file_url` for stills). `output.dedupe` carries the resolved effects, the effective zoom, the CRF and a human-readable `notes` list.
|
|
248
|
+
- Billing: one ffmpeg pass, metered at real compute (much cheaper than a render). Free on a local serve box — and free ANYWHERE via `vidfarm dedupe --local`, which runs the identical filter graph on bundled ffmpeg.
|
|
249
|
+
|
|
250
|
+
### Presets
|
|
251
|
+
|
|
252
|
+
`preset` picks a calibrated transformation set. Default `standard`.
|
|
253
|
+
|
|
254
|
+
| preset | skew | zoom | rotate | speed | saturation | when |
|
|
255
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
256
|
+
| `none` | — | — | — | — | — | no-op; re-encode only |
|
|
257
|
+
| `light` | 1% | 2% | 0.75° | +1% | +2% | lightly reused footage, or tight framing you can't crop |
|
|
258
|
+
| `standard` | 2% | 3% | 2° | +2% | +4% | **the default** — the house standard |
|
|
259
|
+
| `strong` | 3.5% | 6% | 3° | +5% | +8% | an Nth re-post, or an account that already ran this clip |
|
|
260
|
+
| `legacy` | — | 4% | 3° | +5% | +5% | the pre-ffmpeg composition-renderer defaults |
|
|
261
|
+
|
|
262
|
+
`light`/`standard`/`strong` also move contrast, brightness, hue and grain. Any individual knob in `effects` overrides the preset.
|
|
247
263
|
|
|
248
|
-
|
|
264
|
+
### Minting N distinct copies
|
|
265
|
+
|
|
266
|
+
`variant` (1-based) is what makes bulk posting work. Variant 1 is the preset as authored; later variants get deterministically jittered magnitudes and **alternating signs** (a sign flip moves a perceptual hash much further than a magnitude nudge), so N copies differ from the original *and from each other*. Reuse one `seed` across the batch, and post each variant to a different account/slot.
|
|
267
|
+
|
|
268
|
+
### Payload fields
|
|
249
269
|
|
|
250
270
|
- `source_media_url` (required, URL) — the image or video to transform
|
|
251
271
|
- `media_type` (`"image" | "video"`, optional) — auto-detected from URL extension if omitted (`.mp4/.mov/.webm/.m4v` → video, else image)
|
|
252
|
-
- `
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
- `
|
|
258
|
-
- `
|
|
259
|
-
- `
|
|
260
|
-
- `
|
|
261
|
-
- `
|
|
262
|
-
- `
|
|
263
|
-
- `
|
|
264
|
-
- `
|
|
265
|
-
- `
|
|
266
|
-
- `
|
|
267
|
-
- `
|
|
268
|
-
- `
|
|
272
|
+
- `preset` (`"none" | "light" | "standard" | "strong" | "legacy"`, default `"standard"`)
|
|
273
|
+
- `engine` (`"ffmpeg" | "composition"`, default `"ffmpeg"`) — `ffmpeg` is a real pixel/timing transform on the source file (true shear, honest playback speed, grain, metadata strip) and is both cheaper and stronger. `composition` is the legacy HyperFrames-render path, kept only for callers that depend on its exact output.
|
|
274
|
+
- `variant` (int ≥ 1, default `1`), `seed` (string, optional), `jitter` (bool, optional — defaults on for `variant > 1`)
|
|
275
|
+
- `strip_metadata` (default `true`) — drop creation time / encoder / source handler. Several platforms compare that **before** they compare pixels.
|
|
276
|
+
- `effects` (optional object). Every field optional; each one overrides the preset:
|
|
277
|
+
- `zoom` — scale factor, centre-cropped back (`1.03` = 3% punch-in)
|
|
278
|
+
- `skew` — horizontal shear as a **percent of frame width** (ffmpeg engine only)
|
|
279
|
+
- `rotate` — degrees of 2D rotation
|
|
280
|
+
- `tilt` — degrees of 3D X-axis tilt on the composition engine; folded into the shear budget on ffmpeg
|
|
281
|
+
- `speed` — playback multiplier, video only; changes duration **and** pitch-preserved audio tempo
|
|
282
|
+
- `saturation`, `contrast`, `brightness` — multipliers around `1`
|
|
283
|
+
- `hue_rotate` — degrees
|
|
284
|
+
- `noise` — film grain `0..100` (ffmpeg engine only). Cheap, invisible, moves a lot of hash.
|
|
285
|
+
- `blur` — gaussian sigma in px. Usually `0` — blur is the one knob viewers notice.
|
|
286
|
+
- `volume` — audio gain multiplier
|
|
287
|
+
- `horizontal_flip` (default `false`) — the strongest single knob, but it visibly reverses on-screen text. Opt in deliberately.
|
|
288
|
+
- `tint_color` (default `"#FF8C00"`), `tint_opacity` (default `0.08`) — flat color wash; set opacity `0` to skip
|
|
289
|
+
- `width` / `height` — **optional on the ffmpeg engine**; omit to keep the source's own frame size (forcing 1080×1920 onto a 16:9 source would squash it). The composition engine falls back to 1080×1920.
|
|
290
|
+
- `crf` — base x264 quality; jittered ±1 per variant so the coded bitstream differs too
|
|
269
291
|
- `output_format` (`"png" | "jpeg" | "webp"`, default `"png"`) — image mode only; video mode always outputs MP4
|
|
292
|
+
- Composition-engine only: `duration_ms` / `fallback_duration_ms` (default `5000`), `object_fit`, `object_position`, `background_color`, `muted`
|
|
293
|
+
|
|
294
|
+
**A rotate forces a bigger crop than you asked for.** Black corners have to go somewhere, so the primitive raises `zoom` to the smallest value that covers the rotation and says so in `output.dedupe.notes`. On a tall 1080×1920 frame a 2° rotate costs ~6.7% of the frame. If framing matters more than fingerprint distance, pass `effects.rotate: 0`.
|
|
295
|
+
|
|
296
|
+
Video example — three copies of one render, one per account:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
for V in 1 2 3; do
|
|
300
|
+
curl -X POST "$VIDFARM_BASE/api/v1/primitives/media/dedupe" \
|
|
301
|
+
-H "vidfarm-api-key: $VIDFARM_API_KEY" \
|
|
302
|
+
-H "content-type: application/json" \
|
|
303
|
+
-d "{
|
|
304
|
+
\"tracer\": \"dedupe-launch-reel-v$V\",
|
|
305
|
+
\"payload\": {
|
|
306
|
+
\"source_media_url\": \"https://cdn.example.com/reel.mp4\",
|
|
307
|
+
\"media_type\": \"video\",
|
|
308
|
+
\"preset\": \"standard\",
|
|
309
|
+
\"variant\": $V,
|
|
310
|
+
\"seed\": \"launch-reel\"
|
|
311
|
+
}
|
|
312
|
+
}"
|
|
313
|
+
done
|
|
314
|
+
```
|
|
270
315
|
|
|
271
|
-
|
|
316
|
+
Custom knobs (keep the framing, lean on color and timing instead):
|
|
272
317
|
|
|
273
318
|
```bash
|
|
274
319
|
curl -X POST "$VIDFARM_BASE/api/v1/primitives/media/dedupe" \
|
|
@@ -279,15 +324,7 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/media/dedupe" \
|
|
|
279
324
|
"payload": {
|
|
280
325
|
"source_media_url": "https://cdn.example.com/reel.mp4",
|
|
281
326
|
"media_type": "video",
|
|
282
|
-
"
|
|
283
|
-
"effects": {
|
|
284
|
-
"zoom": 1.05,
|
|
285
|
-
"tilt": 2,
|
|
286
|
-
"rotate": -2,
|
|
287
|
-
"speed": 1.03,
|
|
288
|
-
"hue_rotate": 4,
|
|
289
|
-
"horizontal_flip": true
|
|
290
|
-
},
|
|
327
|
+
"effects": { "rotate": 0, "skew": 1.5, "zoom": 1.02, "speed": 1.03, "hue_rotate": 6, "noise": 2 },
|
|
291
328
|
"tint_color": "#00A3FF",
|
|
292
329
|
"tint_opacity": 0.06
|
|
293
330
|
}
|
|
@@ -305,12 +342,18 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/media/dedupe" \
|
|
|
305
342
|
"payload": {
|
|
306
343
|
"source_media_url": "https://cdn.example.com/photo.jpg",
|
|
307
344
|
"media_type": "image",
|
|
308
|
-
"
|
|
345
|
+
"preset": "light",
|
|
309
346
|
"output_format": "webp"
|
|
310
347
|
}
|
|
311
348
|
}'
|
|
312
349
|
```
|
|
313
350
|
|
|
351
|
+
Local equivalent (free, offline, identical transforms — prefer this):
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
vidfarm dedupe ./out/final.mp4 --variants 3 --out-dir ./out/posts
|
|
355
|
+
```
|
|
356
|
+
|
|
314
357
|
## Primitive: music (text → music)
|
|
315
358
|
|
|
316
359
|
Generate music (instrumental, songs with lyrics, background beds, jingles, scores) via ElevenLabs. **Default this on freely — music is a core primitive.** `use_wallet_credits` defaults **true**: it runs on vidfarm's platform ElevenLabs key and bills the customer's wallet. Recommend keeping it on; set it false only to save wallet credits or to use the customer's OWN saved ElevenLabs key.
|