@officexapp/vidfarm-devcli 0.21.59 โ 0.21.61
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 +2 -2
- package/.agents/skills/vidfarm/references/content-ideas.md +97 -14
- package/SKILL.director.md +99 -16
- package/SKILL.md +3 -1
- package/clipper.md +7 -3
- package/dist/src/cli.js +194 -15
- package/dist/src/devcli/clipper-panel.js +1828 -0
- package/dist/src/devcli/clipper-run.js +635 -126
- package/dist/src/devcli/marketplace-gigs.js +226 -2
- package/dist/src/devcli/skill-docs.js +14 -0
- package/experimental/meme-recaption.md +355 -54
- package/marketplace.md +67 -1
- package/package.json +3 -1
- package/public/assets/homepage-client-app.js +14 -14
|
@@ -54,7 +54,7 @@ Two ingredients, no more:
|
|
|
54
54
|
|
|
55
55
|
| Ingredient | What it is | Where it comes from |
|
|
56
56
|
|---|---|---|
|
|
57
|
-
| ๐ฌ **The meme** | A borrowed clip everyone already half-recognises, doing one absurd physical thing | `vidfarm.cc/explore` โ public raws, `sourceType: MemeScreens` (851 of them
|
|
57
|
+
| ๐ฌ **The meme** | A borrowed clip everyone already half-recognises, doing one absurd physical thing | `vidfarm.cc/explore` โ public raws, `sourceType: MemeScreens` (851 of them โ reached with `vector_q`, **not** with the CLI, which shows you 200 and does not say so) |
|
|
58
58
|
| โ๏ธ **The caption** | One sentence naming a specific lived experience, in the second person or the first | You. This is the only original work in the video, and it is 90% of the outcome |
|
|
59
59
|
|
|
60
60
|
**The offer is named on the video.** First choice: inside the caption, as a character in the setup
|
|
@@ -446,43 +446,201 @@ it is the cheapest route out of a caption that got safe.
|
|
|
446
446
|
|
|
447
447
|
## Casting the meme
|
|
448
448
|
|
|
449
|
-
###
|
|
449
|
+
### Two channels: the slug is the IDENTITY, the vector is the BODY
|
|
450
|
+
|
|
451
|
+
The shelf answers two completely different questions and it answers them through two different
|
|
452
|
+
fields. Casting badly is almost always the result of asking one of them the other's question.
|
|
453
|
+
|
|
454
|
+
| You want to know | Ask | Why |
|
|
455
|
+
|---|---|---|
|
|
456
|
+
| **which meme is this** | the `slugId` | The AI descriptions are blind to the meme's identity โ see the warning below |
|
|
457
|
+
| **what does the body DO** | `vector_q`, semantic search | The embedding is built from the description **plus the action / emotion / motion / subject tags**, which is exactly the verb axis this format casts on |
|
|
450
458
|
|
|
451
459
|
> โ ๏ธ **The descriptions on this shelf were written by a model that has never heard of the meme.**
|
|
452
|
-
> This is not a small problem โ it is the defining fact of sourcing this format.
|
|
453
|
-
>
|
|
454
460
|
> `my-brain-every-10-seconds` is described as *"A man in a suit gestures and looks at the camera
|
|
455
461
|
> with a neutral expression."* `jonah-jameson-laugh-you-serious` is described as *"A solid green
|
|
456
|
-
> screen background."*
|
|
457
|
-
> that contains a dozen clips of people eating.
|
|
462
|
+
> screen background."*
|
|
458
463
|
>
|
|
459
|
-
> **The `slugId` is the index. The description is noise
|
|
460
|
-
>
|
|
464
|
+
> **The `slugId` is the index of identity. The description is noise about identity.** But that same
|
|
465
|
+
> description says *what the body is doing*, and **that is the only thing this format casts on.** A
|
|
466
|
+
> model that does not know it is looking at J. Jonah Jameson can still tell you the man is laughing
|
|
467
|
+
> in your face โ so the descriptions are worthless for the question you were not going to ask and
|
|
468
|
+
> useful for the one you were.
|
|
469
|
+
|
|
470
|
+
#### ๐จ `--query` is LEXICAL. Semantic search is a different parameter, and the CLI has no flag for it
|
|
471
|
+
|
|
472
|
+
This is the single most expensive mistake in sourcing this format, and an earlier draft of this file
|
|
473
|
+
made it and drew the wrong conclusion from it.
|
|
474
|
+
|
|
475
|
+
`vidfarm public-raws --query "โฆ"` sends `?q=`, and `q` is a **substring filter that runs first**. It
|
|
476
|
+
throws away every clip whose text does not literally contain your words, and only *then* does the
|
|
477
|
+
server rank whatever survived. On a shelf whose descriptions never use your vocabulary, the filter
|
|
478
|
+
empties the set before the ranking gets to see it โ so a perfectly good semantic query returns
|
|
479
|
+
nothing, and it looks like semantic search is broken. It is not. It never ran.
|
|
461
480
|
|
|
462
481
|
```bash
|
|
463
|
-
|
|
464
|
-
vidfarm public-raws
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
vidfarm public-raws
|
|
482
|
+
# โ lexical filter. Measured: 0 results, on a shelf full of blank stares
|
|
483
|
+
vidfarm api GET "/api/v1/public-raws?category=greenscreen&limit=8&q=blank%20stare%20confused"
|
|
484
|
+
|
|
485
|
+
# โ
semantic. Same shelf, same words, ranked by MEANING
|
|
486
|
+
vidfarm api GET "/api/v1/public-raws?category=greenscreen&limit=8&vector_q=blank%20stare%20confused%20no%20comprehension"
|
|
487
|
+
# cat-staring ยท mrbean-wtf-greenscreen ยท james-franco-shocked ยท spongebob-panting โฆ
|
|
468
488
|
```
|
|
469
489
|
|
|
490
|
+
> **Pass `vector_q` and do NOT pass `q`.** The server defaults `vector_q` to `q` when you omit it,
|
|
491
|
+
> which means the CLI's `--query` gets you the lexical filter *and* the ranking โ and the filter has
|
|
492
|
+
> already deleted the answer. Sending only `vector_q` skips the filter and ranks the whole shelf.
|
|
493
|
+
>
|
|
494
|
+
> **`vidfarm public-raws` cannot do this.** It maps `--query` to `q` and has no vector flag. Every
|
|
495
|
+
> semantic cast in this file goes through `vidfarm api GET`.
|
|
496
|
+
|
|
497
|
+
**It needs a login and an embedding key, and it fails SILENTLY without one.** The ranking requires a
|
|
498
|
+
logged-in customer with a saved gemini or openai provider key. With no key โ or logged out โ the
|
|
499
|
+
server skips the ranking, returns **HTTP 200 with a full page of results**, and says nothing. You
|
|
500
|
+
get the shelf in recency order wearing the costume of a search result.
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
vidfarm whoami # must show a login
|
|
504
|
+
vidfarm api GET /api/v1/user/me/provider-keys | grep -iE "gemini|openai"
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**Confirm the ranking is live before you trust a cast:** query `arms up jumping celebration confetti`
|
|
508
|
+
and query `shoulders drop giving up defeated sigh`. If the top result is the same clip for both, the
|
|
509
|
+
embedder is not running and you are reading a recency list.
|
|
510
|
+
|
|
511
|
+
#### Semantic search reaches the whole shelf. The CLI reaches a quarter of it
|
|
512
|
+
|
|
470
513
|
> ๐จ **`vidfarm public-raws` shows you 200 clips and stops, and it does not tell you.** The server
|
|
471
514
|
> caps a shelf at 200 per call and returns a `next_cursor` โ but the CLI has **no `--cursor` flag**,
|
|
472
|
-
> so raising `--limit` to 1200 still returns exactly 200.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
515
|
+
> so raising `--limit` to 1200 still returns exactly 200.
|
|
516
|
+
|
|
517
|
+
`vector_q` on a **curated shelf** (`greenscreen`, `reaction`, `lifestyle`, `text-graphics`, โฆ) is
|
|
518
|
+
not a rerank of that 200. The server scans **1500 records** for a curated category and ranks all of
|
|
519
|
+
them, so one call sees essentially the entire MemeScreens library and hands you its top N.
|
|
520
|
+
|
|
521
|
+
Measured on the `greenscreen` shelf, ten verb queries against one plain `--limit 200` page:
|
|
522
|
+
|
|
523
|
+
| | |
|
|
524
|
+
|---|---|
|
|
525
|
+
| Distinct clips the ten queries returned | **143** |
|
|
526
|
+
| Of those, **not present** in the CLI's first 200 | **93 โ 65%** |
|
|
527
|
+
| Of those, hallucinated / not really on the shelf | **0.** All 143 verified against the fully-paged 693 |
|
|
528
|
+
|
|
529
|
+
**Two thirds of what semantic search finds is invisible to the CLI**, and the two thirds it hides
|
|
530
|
+
are not the boring ones โ they are the animal and anonymous clips (`cat-staring`, `cat-sleeping`,
|
|
531
|
+
`hangover-dog`) that are ๐ข **safe tier**, which is the tier client work is supposed to default to.
|
|
532
|
+
Casting from the first 200 quietly pushes you toward the celebrity clips you are not allowed to use.
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
vidfarm public-raws --categories # the shelves + live counts. Start here
|
|
536
|
+
vidfarm public-raws --category greenscreen --limit 200 # the first 200 only โ for a browse, not a cast
|
|
537
|
+
```
|
|
477
538
|
|
|
478
539
|
**Every shelf the CLI exposes is dominated by MemeScreens** โ `b-roll`, `lifestyle` and
|
|
479
540
|
`text-graphics` all come back as greenscreen memes. Good news for this format, bad news for
|
|
480
541
|
backgrounds (see below).
|
|
481
542
|
|
|
482
|
-
|
|
543
|
+
### The diversity sweep โ one query per verb, then dedupe
|
|
544
|
+
|
|
545
|
+
> **A single vector query is a relevance tool. It is the OPPOSITE of a diversity tool.** It returns
|
|
546
|
+
> the twenty clips nearest one point, and near-neighbours are near-duplicates: four `cat-sitting`
|
|
547
|
+
> variants of the same cat. Cast ten videos off one query and you will ship ten videos that look
|
|
548
|
+
> like one video.
|
|
549
|
+
|
|
550
|
+
Diversity comes from **querying ten different points and taking the union.** The ten points are
|
|
551
|
+
already written down โ they are the ten verb tokens in the table below, which is why the list is
|
|
552
|
+
closed. One query per verb, top 20 each, deduped, is a 143-clip casting pool spread across the whole
|
|
553
|
+
shelf by meaning instead of by upload date.
|
|
554
|
+
|
|
555
|
+
```python
|
|
556
|
+
#!/usr/bin/env python3
|
|
557
|
+
"""meme-sweep.py โ one vector query per verb token -> a deduped casting pool.
|
|
558
|
+
|
|
559
|
+
Needs a vidfarm login + a saved gemini/openai key, or the ranking silently
|
|
560
|
+
does not happen (see above). Run it once per session; it is ~10 API calls.
|
|
561
|
+
"""
|
|
562
|
+
import json, subprocess, urllib.parse
|
|
563
|
+
|
|
564
|
+
VERBS = {
|
|
565
|
+
"deflate": "shoulders drop eyes go flat accepting a bad outcome without a fight",
|
|
566
|
+
"blank": "total non-comprehension head tilt dead eyes nothing processing",
|
|
567
|
+
"loop": "the same thought going round again mechanically forever repetitive",
|
|
568
|
+
"smug": "arms folded self-satisfied already solved it insufferable",
|
|
569
|
+
"overreact": "full body screaming sobbing flailing at something trivial",
|
|
570
|
+
"correct": "turning to camera to tell you patiently that you are wrong",
|
|
571
|
+
"unbothered": "completely relaxed doing nothing while chaos happens calm",
|
|
572
|
+
"celebrate": "arms up jumping celebration confetti scale joy",
|
|
573
|
+
"stare": "holding eye contact with the camera saying nothing silent",
|
|
574
|
+
"wait": "still waiting visibly longer than is reasonable impatient",
|
|
575
|
+
}
|
|
576
|
+
SHELVES = ("greenscreen", "reaction", "lifestyle", "text-graphics")
|
|
577
|
+
|
|
578
|
+
def get(path):
|
|
579
|
+
out = subprocess.run(["vidfarm", "api", "GET", path], capture_output=True, text=True).stdout
|
|
580
|
+
brace = out.find("{") # first line is a coloured "GET โฆ โ 200" status line
|
|
581
|
+
return json.JSONDecoder().raw_decode(out[brace:])[0] if brace >= 0 else {}
|
|
582
|
+
|
|
583
|
+
pool, tops = {}, {}
|
|
584
|
+
for shelf in SHELVES:
|
|
585
|
+
for verb, q in VERBS.items():
|
|
586
|
+
d = get(f"/api/v1/public-raws?category={shelf}&limit=20"
|
|
587
|
+
f"&vector_q={urllib.parse.quote(q)}") # vector_q ONLY. Never q
|
|
588
|
+
raws = d.get("raws", [])
|
|
589
|
+
if raws:
|
|
590
|
+
tops.setdefault(shelf, []).append(raws[0]["rawId"])
|
|
591
|
+
for rank, raw in enumerate(raws):
|
|
592
|
+
slug = raw.get("slugId") or raw["rawId"]
|
|
593
|
+
pool.setdefault(slug, {"verb": verb, "rank": rank, "id": raw["rawId"],
|
|
594
|
+
"desc": (raw.get("description") or "")[:90]})
|
|
595
|
+
|
|
596
|
+
# The ranking fails SILENTLY (no login / no embedding key) and still returns a
|
|
597
|
+
# full page. The only reliable tell is differential: ten different queries that
|
|
598
|
+
# rank must not all return the same clip first. Do NOT test this by looking for
|
|
599
|
+
# a slugId โ several shelves legitimately return bare UUIDs.
|
|
600
|
+
for shelf, first in tops.items():
|
|
601
|
+
if len(set(first)) < len(first) / 2:
|
|
602
|
+
raise SystemExit(f"{shelf}: {len(set(first))} distinct top hits from {len(first)} queries โ "
|
|
603
|
+
"the embedder is not running. Check `vidfarm whoami` and your gemini/openai key.")
|
|
604
|
+
|
|
605
|
+
with open("casting-pool.tsv", "w") as f:
|
|
606
|
+
f.write("slug\tverb\trank\trawId\tdescription\n")
|
|
607
|
+
for slug, v in sorted(pool.items(), key=lambda kv: (kv[1]["verb"], kv[1]["rank"])):
|
|
608
|
+
f.write(f"{slug}\t{v['verb']}\t{v['rank']}\t{v['id']}\t{v['desc']}\n")
|
|
609
|
+
print(len(pool), "distinct clips -> casting-pool.tsv")
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**Then cast off the TSV, and cast off a verb you have not shipped this week:**
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
awk -F'\t' '$2=="unbothered"' casting-pool.tsv | cut -f1,5 # the whole neighbourhood, one verb
|
|
616
|
+
cut -f2 casting-pool.tsv | sort | uniq -c # how deep each verb actually goes
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**Three rules for keeping the pool diverse, all of them cheap:**
|
|
620
|
+
|
|
621
|
+
1. **Drop the near-duplicate families.** `cat-sitting-5e58โฆ` and `cat-sitting-8eddโฆ` are one casting
|
|
622
|
+
choice, not two. Collapse on the slug stem (the slug minus its trailing hex) before you count
|
|
623
|
+
your options, or you will believe you have 143 and be choosing among about 110.
|
|
624
|
+
2. **Take the top ~20 per verb, never the top 60 of one.** The tail of one query is further from
|
|
625
|
+
that verb than the head of the next query is โ depth on one point buys worse clips, breadth
|
|
626
|
+
across points buys better ones.
|
|
627
|
+
3. **Subtract the delivery log before you look, not after.** `join` the pool against
|
|
628
|
+
`DELIVERED.tsv` so a retired clip is never on the sheet you are choosing from. A clip you have
|
|
629
|
+
already sent is not an option you rejected, it is an option you should not have seen.
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
cut -f5 CLIPPER/DELIVERED.tsv | sort -u > /tmp/used.txt
|
|
633
|
+
grep -vFf /tmp/used.txt casting-pool.tsv > casting-pool.fresh.tsv # cast from THIS
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
> **The sweep is how "there was nothing else" stops being sayable.** Rule 11 asks you not to reuse a
|
|
637
|
+
> clip you have already delivered; the honest objection has always been that finding a fresh one
|
|
638
|
+
> meant paging a 693-clip shelf by hand. It is now ten API calls and a TSV.
|
|
639
|
+
|
|
640
|
+
> **Delivery work: open your delivery log in the same breath as the sweep.** Anything you already
|
|
483
641
|
> sent this client is out (Rule 11), and anything that has been passed on twice is out for everybody.
|
|
484
642
|
> **A clip you have already used is not a shortcut, it is the shelf you did not read** โ and the
|
|
485
|
-
>
|
|
643
|
+
> sweep hands you 143 candidates, so "there was nothing else" is almost never the real reason.
|
|
486
644
|
>
|
|
487
645
|
> ```bash
|
|
488
646
|
> cut -f1,5,8 CLIPPER/DELIVERED.tsv # client ยท clip ยท verdict โ read it BEFORE the contact sheet
|
|
@@ -510,14 +668,26 @@ string equality instead of a judgement, which is the only form of it a gate can
|
|
|
510
668
|
| `overreact` | a full-body scream, sob or flail at something trivial | `neon-screaming`, `jontron-nooooooo`, `girl-fake-crying` |
|
|
511
669
|
| `correct` | turning to camera to tell you, patiently, that you are wrong | `jonathan-frakes-telling-you-youre-wrong-for-47-seconds`, `women-mocking` |
|
|
512
670
|
| `unbothered` | the room is on fire and the body has not moved | `my-favorite-thing-in-the-world-to-do-is-nothing`, `boss-baby-chilling` |
|
|
513
|
-
| `celebrate` | arms up, jumping, confetti-scale joy |
|
|
514
|
-
| `stare` | holding eye contact with the camera and saying nothing |
|
|
515
|
-
| `wait` | still present, still waiting, visibly longer than is reasonable |
|
|
671
|
+
| `celebrate` | arms up, jumping, confetti-scale joy | ๐ `celebration-dance`, `girl-dancing`, `jojo-siwa-dancing` โ the natural win-pole verb |
|
|
672
|
+
| `stare` | holding eye contact with the camera and saying nothing | ๐ `cat-staring` (๐ข), `mortal-kombat-1-shang-tsung` (๐ด) โ the `<Entity> watching me โฆ` verb (Rule 1, tier 1) |
|
|
673
|
+
| `wait` | still present, still waiting, visibly longer than is reasonable | ๐ `wait-hold-on-wait` โ for captions whose collapse is elapsed time |
|
|
516
674
|
|
|
517
|
-
The
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
675
|
+
**The queries that produce this table are the `VERBS` dict in `meme-sweep.py` above** โ the table is
|
|
676
|
+
its cached output, not a separate list to maintain. Re-run the sweep and you regenerate it.
|
|
677
|
+
|
|
678
|
+
> ๐ **marks a slug found by the vector sweep and NOT yet confirmed on a contact sheet.** The first
|
|
679
|
+
> seven rows were cast by hand and shipped; the last three were empty until the sweep filled them,
|
|
680
|
+
> and the sweep ranks descriptions, not footage. **Build the sheet before you use one** โ that rule
|
|
681
|
+
> does not relax because a machine agreed with you.
|
|
682
|
+
|
|
683
|
+
**Do not add an eleventh token** to avoid the work of choosing among ten. If a clip genuinely does
|
|
684
|
+
something none of the ten covers, that is a real finding: add the token here, **with its body line
|
|
685
|
+
and its vector query**, before you use it.
|
|
686
|
+
|
|
687
|
+
> **The sweep also grades the token list.** `deflate` and `celebrate` came back with 20 and 19 clips
|
|
688
|
+
> the other nine queries had not already found; `stare` and `wait` came back with 9 each. A verb
|
|
689
|
+
> whose neighbourhood is that thin is a verb the shelf cannot really cast โ treat a low count as a
|
|
690
|
+
> warning that you are about to force a clip, not as a challenge.
|
|
521
691
|
|
|
522
692
|
**Then confirm by eye.** Build a contact sheet before you commit โ a slug promises an action the
|
|
523
693
|
clip does not always deliver, and half these clips are 16:9 with the subject small at the bottom:
|
|
@@ -646,21 +816,68 @@ class before you write a single ffmpeg command.**
|
|
|
646
816
|
| Where the caption goes | On the background, above the subject | On a plate above the clip, outside the picture |
|
|
647
817
|
| Share of the shelf | The large majority | A minority โ check for burned-in text on the contact sheet |
|
|
648
818
|
|
|
649
|
-
|
|
650
|
-
always what it looks like:
|
|
819
|
+
### Decide the class MECHANICALLY, then read the plate
|
|
651
820
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
821
|
+
> **The class is not a judgement call and it must not be one.** "Is there a plate" has to be
|
|
822
|
+
> answered before "what colour is the plate", or you key a Class B clip against a muddy average of
|
|
823
|
+
> somebody's living room and punch holes in it. **Nothing downstream catches that** โ the key
|
|
824
|
+
> "works", a subject is found, the render is quietly ruined, and `no_residual_green` passes because
|
|
825
|
+
> there was never any green.
|
|
826
|
+
|
|
827
|
+
Run this on every clip, before Appendix A. It answers both questions in the right order:
|
|
828
|
+
|
|
829
|
+
```python
|
|
830
|
+
#!/usr/bin/env python3
|
|
831
|
+
"""plate-check.py <meme.mp4> โ Class A or Class B, and the plate if there is one."""
|
|
832
|
+
import subprocess, sys, numpy as np
|
|
833
|
+
|
|
834
|
+
src, W, H = sys.argv[1], 320, 320
|
|
835
|
+
p = subprocess.run(["ffmpeg", "-v", "error", "-i", src, "-vf", f"fps=2,scale={W}:{H}",
|
|
836
|
+
"-pix_fmt", "rgb24", "-f", "rawvideo", "-"], capture_output=True)
|
|
837
|
+
a = np.frombuffer(p.stdout, np.uint8)
|
|
838
|
+
a = a[: len(a) // (W * H * 3) * (W * H * 3)].reshape(-1, H, W, 3).astype(np.int16)
|
|
839
|
+
|
|
840
|
+
# All four corners, not just the top-left: on a keyed clip every corner is plate,
|
|
841
|
+
# on a baked clip they are four different bits of a room.
|
|
842
|
+
c = np.concatenate([a[:, :24, :24], a[:, :24, -24:],
|
|
843
|
+
a[:, -24:, :24], a[:, -24:, -24:]], axis=2).reshape(-1, 3)
|
|
844
|
+
plate = c.mean(0).astype(int)
|
|
845
|
+
flat = c.std(0).mean() # one colour, or a scene
|
|
846
|
+
cover = (np.abs(a - plate).max(3) < 40).mean() # how much of the frame IS plate
|
|
847
|
+
sat = int(plate.max() - plate.min()) # letterbox bars read flat too
|
|
848
|
+
|
|
849
|
+
print(f"plate rgb {plate.tolist()} hex {'%02X%02X%02X' % tuple(plate)} "
|
|
850
|
+
f"corner stdev {flat:.1f} coverage {cover*100:.0f}% saturation {sat}")
|
|
851
|
+
|
|
852
|
+
if flat > 12 or cover < 0.25 or sat < 60:
|
|
853
|
+
sys.exit("CLASS B โ no saturated flat plate. Do NOT key. Layout A, crop the burned-in text")
|
|
854
|
+
|
|
855
|
+
digital = plate[1] > 240 and plate[0] < 40 and plate[2] < 40
|
|
856
|
+
print("CLASS A โ " + ("digital plate: chromakey 0.18:0.03, NO despill" if digital
|
|
857
|
+
else "filmed plate: chromakey 0.30:0.08, light despill"))
|
|
659
858
|
```
|
|
660
859
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
860
|
+
**The three thresholds and what each one is for:** `flat > 12` is a scene wearing a plate's
|
|
861
|
+
costume, `cover < 0.25` is a plate too small to be the background, `sat < 60` is black or white
|
|
862
|
+
letterbox bars, which are flat and are not a plate. All three are first-pass numbers measured on one
|
|
863
|
+
shelf โ **calibrate them against a clip you already agree about before you let them fail anything.**
|
|
864
|
+
|
|
865
|
+
**Then read the colour, because these clips are re-encodes and the plate is not always what it looks
|
|
866
|
+
like.** A pure `00FF00` reading means a digital plate: key tight (`0.18:0.03`) and **skip despill.**
|
|
867
|
+
A reading like `1F D3 1F` means a filmed plate: key looser and despill lightly. Getting this
|
|
868
|
+
backwards is the first defect in the list below.
|
|
869
|
+
|
|
870
|
+
> **The shelf is overwhelmingly green, and the gate only knows green.** `no_residual_green` (gate 2)
|
|
871
|
+
> tests a green hue, so it catches both defects that matter on this shelf โ a fringe from a tight
|
|
872
|
+
> key, and a key that never ran at all. `no_unkeyed_plate` sits beside it and is hue-agnostic, for
|
|
873
|
+
> the blue or magenta plate the green test is blind to. **If `plate-check.py` prints a plate that is
|
|
874
|
+
> not green, say so in the build log** โ the named check is no longer the one protecting you.
|
|
875
|
+
|
|
876
|
+
> **Why this harness does not use `vidfarm remove-greenscreen` for the video.** The skill's keying
|
|
877
|
+
> surface (`remove-greenscreen --local` / `--smart`, `cutout`, `sticker-pack`) is the right answer
|
|
878
|
+
> almost everywhere else, and it is a **transparent-output** surface. This format never needs
|
|
879
|
+
> transparency in its output โ it composites onto a background and ships an opaque MP4 โ and the
|
|
880
|
+
> alpha intermediate is where the free path breaks. See the warning under "Cost-saving mode".
|
|
664
881
|
|
|
665
882
|
### Class B โ dealing with the burned-in text
|
|
666
883
|
|
|
@@ -1183,8 +1400,13 @@ except you.
|
|
|
1183
1400
|
**The shelf is public, which makes this sharper than it looks.** You cast from `vidfarm public-raws`
|
|
1184
1401
|
and so does every other clipper on the marketplace. A slug that is easy to find is easy for everyone
|
|
1185
1402
|
to find, so the buyer may have seen it this week from somebody else โ and 851 MemeScreens means the
|
|
1186
|
-
excuse "there was nothing else" is almost never true.
|
|
1187
|
-
|
|
1403
|
+
excuse "there was nothing else" is almost never true. **Run the diversity sweep before you reach for
|
|
1404
|
+
a clip you have already used**; it costs ten API calls and it deletes the excuse.
|
|
1405
|
+
|
|
1406
|
+
**And note what "easy to find" now means.** Every clipper on this marketplace reads the same CLI, and
|
|
1407
|
+
the CLI shows everybody the same first 200. If you cast from that page you are competing for 200
|
|
1408
|
+
clips while 65% of the shelf sits unread. The sweep is not only how you avoid repeating *yourself* โ
|
|
1409
|
+
it is how you avoid arriving at the buyer with the same clip as everyone who did not run it.
|
|
1188
1410
|
|
|
1189
1411
|
**The number, if you want one:** the same `clip_slug` in more than about **3 of your last 20
|
|
1190
1412
|
deliveries** is a signature you did not choose. Either it has the approvals to justify it, or you
|
|
@@ -1261,8 +1483,16 @@ interchangeable.
|
|
|
1261
1483
|
is one ffmpeg command and the caption layer does not change. Do that before you write a second
|
|
1262
1484
|
caption โ you will learn more from five castings of one joke than from five jokes.
|
|
1263
1485
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1486
|
+
> โ ๏ธ **Take the five clips from five DIFFERENT verb neighbourhoods, not from the top five of one
|
|
1487
|
+
> query.** This is where a matrix quietly stops being an experiment. The top five of one vector
|
|
1488
|
+
> query are five near-neighbours โ often the same performer in four crops โ so the five videos
|
|
1489
|
+
> differ by almost nothing and the five data points are one data point with error bars. Pull one
|
|
1490
|
+
> clip per verb from `casting-pool.fresh.tsv` and the five castings actually span the shelf.
|
|
1491
|
+
|
|
1492
|
+
Keep three files: the identity index (Appendix C), a fresh `casting-pool.fresh.tsv` from the sweep,
|
|
1493
|
+
and a `captions.tsv` of rung-3 lines. **The sweep is the only one of the three that goes stale** โ
|
|
1494
|
+
re-run it per session, because it is also the file that has your delivery log subtracted from it.
|
|
1495
|
+
The pairing is the creative act; everything downstream is a loop.
|
|
1266
1496
|
|
|
1267
1497
|
### โ ๏ธ The matrix is YOUR test set. It is not one client's inbox
|
|
1268
1498
|
|
|
@@ -1347,7 +1577,8 @@ What gate 2 enforces, and the number it uses:
|
|
|
1347
1577
|
| Check | Threshold | The defect it exists for |
|
|
1348
1578
|
|---|---|---|
|
|
1349
1579
|
| `first_frame_has_subject` / `last_frame_has_subject` | lower-band stdev > 12 | Blank thumbnail / empty tail from un-trimmed plate |
|
|
1350
|
-
| `no_residual_green` | < 0.5% green pixels | A key that was too tight |
|
|
1580
|
+
| `no_residual_green` | < 0.5% green pixels | A key that was too tight โ a fringe on the shelf's usual `00FF00` plate |
|
|
1581
|
+
| `no_unkeyed_plate` | largest flat saturated colour < 25% of frame, **or** `--flat-bg-ok` | The key never ran, or it ran on a blue/magenta plate the green test is blind to. Pass `--flat-bg-ok` only on ladder tier 6, where a flat brand plate really is the background |
|
|
1351
1582
|
| `caption_horizontally_centred` | block centre 50% ยฑ 3% | The flush-left plate with a lake of dead space |
|
|
1352
1583
|
| `caption_side_margins` | > 4% and < 96% | A plate touching an edge |
|
|
1353
1584
|
| `caption_vertical_placement` | inside 8โ85% | Under the platform UI, or on the subject's face |
|
|
@@ -1415,7 +1646,11 @@ Run this **before** you build. An unchecked box is a rewrite, not a fix in the e
|
|
|
1415
1646
|
- [ ] **Reusing this clip on a DIFFERENT client?** It has an approval on it, or it is unproven and
|
|
1416
1647
|
this is its one outing. Passed on twice with no approval = retired
|
|
1417
1648
|
- [ ] This `clip_slug` is not in more than ~3 of your last 20 deliveries
|
|
1418
|
-
- [ ]
|
|
1649
|
+
- [ ] **The diversity sweep ran this session** and you cast off `casting-pool.fresh.tsv`, not off
|
|
1650
|
+
the CLI's first 200 and not off memory
|
|
1651
|
+
- [ ] The sweep's ranking was **live** โ `vidfarm whoami` shows a login, a gemini/openai key is
|
|
1652
|
+
saved, and two different verb queries returned two different top clips. No key = no ranking,
|
|
1653
|
+
silently, with a full page of results to hide it
|
|
1419
1654
|
- [ ] The style โ plate, font, band, grade, handle โ is **identical** to the last delivery. That part
|
|
1420
1655
|
is meant to repeat
|
|
1421
1656
|
|
|
@@ -1445,6 +1680,8 @@ Run this **before** you build. An unchecked box is a rewrite, not a fix in the e
|
|
|
1445
1680
|
|
|
1446
1681
|
**The clip**
|
|
1447
1682
|
- [ ] Rights tier declared. ๐ข for client work, or a ๐ข alternate is built alongside
|
|
1683
|
+
- [ ] **`plate-check.py` run, and the class it printed is the class you built** โ Class A keys and
|
|
1684
|
+
needs a background, Class B is never keyed and needs the burned-in text cropped
|
|
1448
1685
|
- [ ] Plate colour sampled, not assumed
|
|
1449
1686
|
- [ ] Contact sheet built and looked at
|
|
1450
1687
|
- [ ] The action lands 0.6โ1.0s in, and frame 0 has the subject on it (`-vframes 1`, looked at)
|
|
@@ -1462,6 +1699,8 @@ Run this **before** you build. An unchecked box is a rewrite, not a fix in the e
|
|
|
1462
1699
|
- [ ] 1080ร1920, 30fps
|
|
1463
1700
|
- [ ] The meme is NOT cropped to 9:16
|
|
1464
1701
|
- [ ] Layout A or B chosen deliberately; in B the subject is bottom-anchored and full width
|
|
1702
|
+
- [ ] **Class A: the plate is gone and a background is behind the subject.** No green edge on the
|
|
1703
|
+
hair or shoulders, no green in the gaps between limbs, nothing green in the last frame
|
|
1465
1704
|
- [ ] Caption **block centred**, words left-aligned, lines hand-broken to near-equal length
|
|
1466
1705
|
- [ ] Caption fully legible at frame 0, static, plated, clear of the subject's head
|
|
1467
1706
|
- [ ] Poster layer present, so frame 0 is never bare background
|
|
@@ -1573,9 +1812,19 @@ def frames(path, w, h, vf):
|
|
|
1573
1812
|
n = len(p.stdout) // (w * h * 3)
|
|
1574
1813
|
return np.frombuffer(p.stdout, np.uint8)[:n * w * h * 3].reshape(n, h, w, 3).astype(np.int16)
|
|
1575
1814
|
|
|
1576
|
-
# --- 1.
|
|
1815
|
+
# --- 1. confirm there IS a plate, THEN read its colour off the corner ---
|
|
1816
|
+
# The order is load-bearing. Handed a Class B clip (its own world, no plate),
|
|
1817
|
+
# every line below still runs: it keys against a muddy average of somebody's
|
|
1818
|
+
# living room, step 2 finds a "subject", and the render ships with holes punched
|
|
1819
|
+
# in it. Nothing catches that downstream โ no_residual_green passes, because
|
|
1820
|
+
# there was never any green. So refuse here, not at render.
|
|
1577
1821
|
corner = frames(meme, 200, 200, "fps=1,crop=200:200:20:20")
|
|
1822
|
+
flat = corner.reshape(-1, 3).std(0).mean()
|
|
1578
1823
|
plate = corner.reshape(-1, 3).mean(0).astype(int)
|
|
1824
|
+
if flat > 12 or int(plate.max() - plate.min()) < 60:
|
|
1825
|
+
sys.exit(f"corner stdev {flat:.1f}, plate {plate.tolist()} โ not a flat saturated "
|
|
1826
|
+
"chroma plate. This is CLASS B: do not key it. Use Layout A and crop "
|
|
1827
|
+
"the burned-in text. (Run plate-check.py for the full read.)")
|
|
1579
1828
|
digital = bool(plate[1] > 240 and plate[0] < 40 and plate[2] < 40)
|
|
1580
1829
|
key_hex = "0x%02X%02X%02X" % tuple(plate)
|
|
1581
1830
|
tol, soft, despill = (0.18, 0.03, False) if digital else (0.30, 0.08, True)
|
|
@@ -1651,11 +1900,21 @@ def probe_key(tol_try, soft_try, despill_try, t):
|
|
|
1651
1900
|
lum = flat.mean(1)
|
|
1652
1901
|
lit = flat[lum > max(lum.max() * 0.75, 1)] # the subject's bright side
|
|
1653
1902
|
green_left = ((flat[:, 1] - flat[:, 0] > 40) & (flat[:, 1] - flat[:, 2] > 40)).mean()
|
|
1654
|
-
|
|
1903
|
+
# A radius that erased the whole subject leaves NO bright pixels, and
|
|
1904
|
+
# `lit.mean()` on an empty slice is nan, not 0. nan then loses every
|
|
1905
|
+
# comparison below in silence: `nan > 0.88` is False, so the radius is
|
|
1906
|
+
# rejected for the right reason by accident โ but if the UN-keyed probe
|
|
1907
|
+
# comes back nan too, every `keep` is nan, `best` stays None, and the run
|
|
1908
|
+
# falls through to the sampled default with only a printed line. Return a
|
|
1909
|
+
# real 0.0 so a dead frame scores like a dead frame.
|
|
1910
|
+
return (float(lit.mean()) if lit.size else 0.0), green_left
|
|
1655
1911
|
|
|
1656
1912
|
t_probe = START + DUR / 2
|
|
1657
1913
|
# chromakey's similarity floor is 1e-05, not 0 โ a literal 0 is a hard filter error
|
|
1658
1914
|
src_lit, _ = probe_key(1e-5, 0.0, False, t_probe) # ~nothing keyed = the truth
|
|
1915
|
+
if not src_lit:
|
|
1916
|
+
sys.exit(f"the un-keyed probe frame at {t_probe:.2f}s has no bright pixels โ "
|
|
1917
|
+
"the clip is dark or the presence window is wrong. Check it by eye")
|
|
1659
1918
|
best = None
|
|
1660
1919
|
for tol_try in (0.34, 0.28, 0.22, 0.18, 0.14, 0.10, 0.07):
|
|
1661
1920
|
r = probe_key(tol_try, soft, despill, t_probe)
|
|
@@ -1804,16 +2063,24 @@ ffmpeg -i media/scene.mp4 -vframes 1 -update 1 media/poster.png # frame 0 of t
|
|
|
1804
2063
|
|
|
1805
2064
|
## Appendix C โ build the shelf index once
|
|
1806
2065
|
|
|
1807
|
-
|
|
1808
|
-
|
|
2066
|
+
> **This is the IDENTITY index, and it is the second of the two tools.** `meme-sweep.py` (in
|
|
2067
|
+
> *The diversity sweep*, above) is how you **cast** โ it ranks the whole shelf by what the body does
|
|
2068
|
+
> and hands you a fresh pool in ten calls. This appendix builds the full local mirror you **grep**:
|
|
2069
|
+
> for a slug you half-remember, for the rights tier of a name, for the ledger joins in Rule 11, and
|
|
2070
|
+
> for working with no login or no embedding key. Sweep to choose a clip. Index to look one up.
|
|
2071
|
+
|
|
2072
|
+
The shelf pages at 200, the CLI cannot follow the cursor, and the slugs are the index of identity โ
|
|
2073
|
+
so pull the whole thing once through the REST route and grep the file instead of re-querying.
|
|
1809
2074
|
|
|
1810
2075
|
```python
|
|
1811
2076
|
#!/usr/bin/env python3
|
|
1812
2077
|
"""meme-index.py โ pull every MemeScreens raw into one greppable TSV.
|
|
1813
2078
|
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2079
|
+
Note this pages with `q` unset entirely: the `q` filter is a LEXICAL substring
|
|
2080
|
+
match that runs BEFORE ranking, so any query here would silently shrink the
|
|
2081
|
+
mirror. Semantic search is a different parameter (`vector_q`) and lives in
|
|
2082
|
+
meme-sweep.py. The AI descriptions are blind to the meme's identity, so this
|
|
2083
|
+
file is indexed by slug.
|
|
1817
2084
|
|
|
1818
2085
|
Paging note: `vidfarm public-raws` has NO --cursor flag and the server caps a
|
|
1819
2086
|
shelf at 200 per call, so the CLI alone can only ever show you the first 200 of
|
|
@@ -2015,13 +2282,16 @@ and that its answers do not contradict each other. That is a smaller claim than
|
|
|
2015
2282
|
"the match is verified", and it is the one this script is entitled to make.
|
|
2016
2283
|
|
|
2017
2284
|
usage: meme-qa.py <render.mp4> <composition.html> [--offer dishcover.io]
|
|
2018
|
-
[--pole pain|win] [--silent-ok] [--json]
|
|
2285
|
+
[--pole pain|win] [--silent-ok] [--flat-bg-ok] [--json]
|
|
2019
2286
|
"""
|
|
2020
2287
|
import json, re, subprocess, sys
|
|
2021
2288
|
import numpy as np
|
|
2022
2289
|
|
|
2023
2290
|
render, comp = sys.argv[1], sys.argv[2]
|
|
2024
2291
|
SILENT_OK = "--silent-ok" in sys.argv
|
|
2292
|
+
# Ladder tier 6 only: a flat brand plate really is the background. Everywhere
|
|
2293
|
+
# else a slab of one saturated colour is a plate that never got keyed.
|
|
2294
|
+
FLAT_BG_OK = "--flat-bg-ok" in sys.argv
|
|
2025
2295
|
OFFER = (sys.argv[sys.argv.index("--offer") + 1].strip().lower()
|
|
2026
2296
|
if "--offer" in sys.argv else None)
|
|
2027
2297
|
# The pole changes what the payoff is allowed to do (Rule 2). Default to PAIN:
|
|
@@ -2120,7 +2390,21 @@ for label, fr in (("first", first), ("last", last)):
|
|
|
2120
2390
|
check(f"{label}_frame_has_subject", band.std() > 12,
|
|
2121
2391
|
f"lower-band stdev {band.std():.1f} (>12)")
|
|
2122
2392
|
|
|
2123
|
-
#
|
|
2393
|
+
# -------------------------------------------- 3. the plate is GONE, in any hue
|
|
2394
|
+
# Two checks, because these are two different defects. `no_residual_green` is a
|
|
2395
|
+
# FRINGE test on the shelf's usual 00FF00 plate โ a key that was slightly too
|
|
2396
|
+
# tight. `no_unkeyed_plate` is hue-agnostic and catches the bigger, dumber one:
|
|
2397
|
+
# the key never ran, or it ran on a blue/magenta plate the green test cannot see.
|
|
2398
|
+
#
|
|
2399
|
+
# Measured on four fixtures: an unkeyed 00FF00 plate fails both; a photographic
|
|
2400
|
+
# background passes no_unkeyed_plate at 15%; a flat brand plate fails it at 79%
|
|
2401
|
+
# and needs --flat-bg-ok; an unkeyed MAGENTA plate reads 0.00% green and is
|
|
2402
|
+
# caught ONLY by no_unkeyed_plate. That last row is the whole reason it exists.
|
|
2403
|
+
#
|
|
2404
|
+
# Watch the green check if the joke's world is a lawn, a forest or a pool table:
|
|
2405
|
+
# 0.5% is tight, and a genuinely green BACKGROUND can trip it. That is a false
|
|
2406
|
+
# positive, not a bad key โ confirm by eye, then pick a different world rather
|
|
2407
|
+
# than loosening the number.
|
|
2124
2408
|
mid = probe(render, "scale=270:480", 270, 480, pre=("-ss", str(dur / 2)))
|
|
2125
2409
|
if mid is not None:
|
|
2126
2410
|
a = mid[0]
|
|
@@ -2128,6 +2412,23 @@ if mid is not None:
|
|
|
2128
2412
|
green = ((g - r > 45) & (g - b > 45) & (g > 80)).mean()
|
|
2129
2413
|
check("no_residual_green", green < 0.005, f"{green*100:.2f}% green pixels (<0.5%)")
|
|
2130
2414
|
|
|
2415
|
+
# A quarter of the frame in ONE saturated colour is a plate, not a
|
|
2416
|
+
# background: a background is a photograph, and photographs vary. The
|
|
2417
|
+
# caption plate is white or black, so it is not saturated and not counted.
|
|
2418
|
+
# Ladder tier 6 (a flat brand plate as the background) is a legitimate
|
|
2419
|
+
# exception and the only one โ pass --flat-bg-ok, the same way --silent-ok
|
|
2420
|
+
# works, so the exception is a decision somebody made rather than a default.
|
|
2421
|
+
sat_px = (a.max(2) - a.min(2)) > 60
|
|
2422
|
+
if sat_px.any():
|
|
2423
|
+
q = a[sat_px] // 32 # 8^3 colour buckets
|
|
2424
|
+
keys = q[:, 0] * 64 + q[:, 1] * 8 + q[:, 2]
|
|
2425
|
+
biggest = float(np.bincount(keys).max()) / a[..., 0].size
|
|
2426
|
+
else:
|
|
2427
|
+
biggest = 0.0
|
|
2428
|
+
check("no_unkeyed_plate", FLAT_BG_OK or biggest < 0.25,
|
|
2429
|
+
f"largest flat saturated colour = {biggest*100:.0f}% of frame (<25%)"
|
|
2430
|
+
+ (" [--flat-bg-ok]" if FLAT_BG_OK else ""))
|
|
2431
|
+
|
|
2131
2432
|
# ------------------------------------- 4. caption block is horizontally CENTRED
|
|
2132
2433
|
# Find the caption plate in frame 0 by its own darkness against the background,
|
|
2133
2434
|
# then compare its horizontal centre to the canvas centre.
|