overlay-factory-worker 0.1.0 → 0.2.0
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/README.md +67 -184
- package/package.json +1 -2
- package/scripts/overlay-worker.ts +123 -515
- package/scripts/render-overlay.ts +1 -1
- package/scripts/series.ts +292 -44
- package/scripts/state-dir.ts +12 -9
- package/scripts/worker-lock.ts +106 -0
- package/src/Root.tsx +7 -81
- package/src/overlay/Composition.tsx +8 -90
- package/src/overlay/types.ts +11 -57
- package/worker/cli.mjs +3 -3
- package/worker/service.mjs +10 -24
- package/public/fonts/Handjet-variable.woff2 +0 -0
- package/scripts/check-legibility.ts +0 -284
- package/scripts/check-safe-area.ts +0 -148
- package/scripts/export.sh +0 -31
- package/scripts/ig-probe.ts +0 -83
- package/scripts/ig-sync.ts +0 -204
- package/scripts/ingest.sh +0 -34
- package/scripts/library.ts +0 -143
- package/scripts/look-card.ts +0 -107
- package/scripts/look-store.ts +0 -176
- package/scripts/make-card.ts +0 -237
- package/scripts/merge-index.ts +0 -74
- package/scripts/new-episode.ts +0 -149
- package/scripts/place-overlay.ts +0 -359
- package/scripts/prep-card.ts +0 -73
- package/scripts/quality.ts +0 -0
- package/scripts/report.ts +0 -127
- package/scripts/rerender-cards.ts +0 -116
- package/scripts/set-difficulty.ts +0 -62
- package/scripts/verify.ts +0 -149
- package/scripts/wp-restock.ts +0 -281
- package/src/lab/FontLab.tsx +0 -50
- package/src/lab/FontSheet.tsx +0 -188
- package/src/lab/PillLab.tsx +0 -121
- package/src/overlay/DifficultyMeter.tsx +0 -86
- package/src/overlay/PixelText.tsx +0 -134
- package/src/overlay/Title.tsx +0 -75
- package/src/overlay/cardLayout.ts +0 -94
- package/src/overlay/fonts.ts +0 -19
- package/src/overlay/look.ts +0 -155
- package/src/series/what-prints/CodeCard.tsx +0 -107
- package/src/series/what-prints/Composition.tsx +0 -106
- package/src/series/what-prints/codeCardTypes.ts +0 -105
- package/src/series/what-prints/types.ts +0 -22
package/README.md
CHANGED
|
@@ -1,174 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# overlay-factory-worker
|
|
2
|
+
|
|
3
|
+
The Overlay Factory's worker: goosetools.com queues overlay jobs, this machine
|
|
4
|
+
writes the overlays with Claude, renders them with Remotion, and burns them onto
|
|
5
|
+
the uploaded clip with ffmpeg. Published to npm on its own; lives in the
|
|
6
|
+
goosetools repo at `workers/overlay` (it was the standalone `reel-factory` repo
|
|
7
|
+
until 2026-09-26 — the What Prints series that lived there is at the
|
|
8
|
+
`what-prints-final` tag of github.com/ernkerr/reel-factory).
|
|
9
|
+
|
|
10
|
+
## Series: every look is generated, and taught by notes
|
|
11
|
+
|
|
12
|
+
There are no hand-built formats. A series is made the same way a Carousel style
|
|
13
|
+
is: **add references, it tries, you adjust in words, it learns.**
|
|
14
|
+
|
|
15
|
+
1. **"+ New series"** on Goose Tools queues a `series` job. This machine reads
|
|
16
|
+
the references with Claude and writes `series/<slug>/`:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
series/hot-take/
|
|
20
|
+
series.json the fields the form asks for, where each layer sits, and
|
|
21
|
+
the content brief that lets it write its own episodes
|
|
22
|
+
claim.html self-contained HTML+CSS with {{field}} placeholders
|
|
23
|
+
verdict.html
|
|
24
|
+
notes.md every correction the user has made, oldest first
|
|
25
|
+
episodes.json researched episodes waiting to be used (optional)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
2. **Preview** draws the series as a still (`preview` job, no clip).
|
|
29
|
+
3. **A note on the preview** ("bigger title", "use the yellow from reference 2")
|
|
30
|
+
queues a `preview` job with `feedback`. `reviseSeries` in
|
|
31
|
+
`scripts/series.ts` gives Claude the current templates, a render of them, the
|
|
32
|
+
original brief and references, every earlier note, and the new one; checks
|
|
33
|
+
the rewrite against the note and retries once with whatever it missed; then
|
|
34
|
+
writes the result as the series. The note is appended to `notes.md`, so it
|
|
35
|
+
keeps applying — to later revisions and to a full rebuild.
|
|
36
|
+
4. The previous 5 versions of each series are kept in `series/.history/<slug>/`,
|
|
37
|
+
so a bad revision can be put back by hand.
|
|
38
|
+
|
|
39
|
+
`series/` is gitignored and per-machine (in an npm install it lives in
|
|
40
|
+
`~/.goosetools/overlay/series`, see `scripts/state-dir.ts`).
|
|
2
41
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
the
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
npx tsx scripts/make-card.ts puzzles/x.json # write a NEW code card from a puzzle JSON
|
|
11
|
-
./scripts/ingest.sh # pull assets from ~/Desktop/Automate Editing (via Finder)
|
|
12
|
-
npx tsx scripts/prep-card.ts # code-card JPGs -> transparent rounded PNGs
|
|
13
|
-
npx tsx scripts/new-episode.ts # picks next card + b-roll, places the meter
|
|
14
|
-
npx remotion render WhatPrints out/what-prints-XXX.mp4 --props=episodes/what-prints-XXX.json
|
|
15
|
-
./scripts/export.sh out/what-prints-XXX.mp4 # copy to Desktop/Automate Editing/exports
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Preview while tweaking: `npm run dev` (Remotion Studio).
|
|
19
|
-
|
|
20
|
-
## Episode JSON
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"broll": "assets/b-roll/IMG_4858.MOV",
|
|
25
|
-
"card": "assets/cards/IMG_4030.png",
|
|
26
|
-
"titleLine1": "What",
|
|
27
|
-
"titleLine2": "Prints?",
|
|
28
|
-
"difficulty": "hard", // easy | medium | hard
|
|
29
|
-
"durationSec": 10,
|
|
30
|
-
"revealAtSec": 4, // when the difficulty meter pops in
|
|
31
|
-
"trimBeforeSec": 0, // skip into the b-roll clip
|
|
32
|
-
"meterX": 70, // set by scripts/place-overlay.ts
|
|
33
|
-
"meterY": 670
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
`episodes/index.json` maps every code card to its episode metadata (answer, why,
|
|
38
|
-
caption) extracted from the caption screenshots. `episodes/used.json` tracks
|
|
39
|
-
which cards/b-roll are consumed.
|
|
40
|
-
|
|
41
|
-
## Generating code cards
|
|
42
|
-
|
|
43
|
-
The original cards were phone screenshots of a chat app's code block, cropped by
|
|
44
|
-
`prep-card.ts`. New ones are rendered directly: write a puzzle JSON in `puzzles/`
|
|
45
|
-
and run `scripts/make-card.ts`. It highlights the snippet with Shiki (any
|
|
46
|
-
language), renders the `CodeCard` composition to a transparent rounded PNG at
|
|
47
|
-
`public/assets/cards/<slug>.png`, and upserts the entry into `cards.json` and
|
|
48
|
-
`index.json` — so re-running the same slug edits in place.
|
|
49
|
-
|
|
50
|
-
```json
|
|
51
|
-
{
|
|
52
|
-
"slug": "nan-trap",
|
|
53
|
-
"name": "Bowser's NaN Trap",
|
|
54
|
-
"language": "Python",
|
|
55
|
-
"code": "score = float(\"nan\")\nbest = score\n\nprint(score == best)",
|
|
56
|
-
"answer": "False",
|
|
57
|
-
"why": "NaN is never equal to itself...",
|
|
58
|
-
"difficulty": "easy"
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Every card is executed before it renders and put through `scripts/quality.ts`:
|
|
63
|
-
the snippet must print exactly what `answer` claims, print the *same thing*
|
|
64
|
-
three runs in a row and on every installed Python 3.11-3.14, be answerable in a
|
|
65
|
-
comment (no memory addresses, nothing over 120 chars), not duplicate an existing
|
|
66
|
-
card, and carry a real `why`. Any failure stops the render; `--no-verify` and
|
|
67
|
-
`--no-gates` override. Audit the whole library with `npx tsx scripts/quality.ts`
|
|
68
|
-
or `npx tsx scripts/verify.ts`.
|
|
69
|
-
|
|
70
|
-
Cards that fail get a `retired` note in `index.json` rather than being deleted —
|
|
71
|
-
`new-episode.ts` skips them, and the audits ignore them.
|
|
72
|
-
|
|
73
|
-
Keep lines under ~45 characters and snippets under ~8 lines — beyond that the
|
|
74
|
-
script shrinks the type to fit and warns. Colors and geometry live in
|
|
75
|
-
`src/series/what-prints/codeCardTypes.ts`; preview with `npm run dev`.
|
|
76
|
-
|
|
77
|
-
The `/what-prints-card` skill writes the puzzles and drives this end to end.
|
|
78
|
-
|
|
79
|
-
## Engagement data
|
|
80
|
-
|
|
81
|
-
`npx tsx scripts/ig-sync.ts` pulls the Instagram history for `@erin.codes` into
|
|
82
|
-
`episodes/performance.json` — captions, likes, comments, and per-post insights
|
|
83
|
-
(reach, saves, shares, views). Read-only; the token comes from
|
|
84
|
-
`~/code/crossposter/.env` (the `ig-auto-dm` one has expired).
|
|
85
|
-
|
|
86
|
-
Insights calls are rate-limited, so by default it only fetches them for posts
|
|
87
|
-
whose caption looks like a series episode. Use `--since=2026-06-01` for a date
|
|
88
|
-
range or `--all` for every reel; results already fetched are reused.
|
|
89
|
-
|
|
90
|
-
`npx tsx scripts/report.ts` then ranks the published episodes against the
|
|
91
|
-
account's own baseline and links each post back to the card it came from
|
|
92
|
-
(captions are the join key; set `permalink` on an `index.json` entry to force it).
|
|
93
|
-
|
|
94
|
-
Judge cards by **comments per 1,000 reach** — reach swings by orders of magnitude
|
|
95
|
-
and likes track reach, so raw likes say more about distribution than about
|
|
96
|
-
whether the puzzle landed.
|
|
97
|
-
|
|
98
|
-
## Overlay Factory
|
|
99
|
-
|
|
100
|
-
The overlay components (`src/overlay/`) are shared: What Prints composites them
|
|
101
|
-
over its own b-roll, and the Overlay Factory tool on Goose Tools burns them onto
|
|
102
|
-
any clip uploaded from a phone.
|
|
103
|
-
|
|
104
|
-
Burn overlays onto a clip locally:
|
|
42
|
+
The hard rule for templates is **no CSS animation**. Remotion renders by seeking
|
|
43
|
+
to each frame and screenshotting, so a CSS animation or transition freezes on
|
|
44
|
+
whatever the first frame painted. Motion comes from the layer's `enter` value
|
|
45
|
+
(`fade`, `slide-up`, `slide-down`, `pop`), which the composition drives with
|
|
46
|
+
Remotion's own spring. Templates also can't carry `<script>` or any external
|
|
47
|
+
URL; `scripts/series.ts` rejects a generation that does, and the composition
|
|
48
|
+
strips scripts again at render time rather than trusting it.
|
|
105
49
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
50
|
+
One-offs (the Single tab) use the same generation with the text baked in and
|
|
51
|
+
nothing kept.
|
|
109
52
|
|
|
110
|
-
|
|
111
|
-
comes from `~/.goosetools/env`, where the Carousel Maker's installer already
|
|
112
|
-
put it — one token serves every Goose Tools worker on a machine:
|
|
53
|
+
## Running it
|
|
113
54
|
|
|
114
55
|
```bash
|
|
115
56
|
npm run overlay:worker # foreground, stops when the terminal closes
|
|
116
57
|
npm run overlay:install # keep it running across reboots (launchd)
|
|
117
58
|
npm run overlay:uninstall # stop it and remove the agent
|
|
59
|
+
npm run overlay -- clip.mov overlay.json out.mp4 # burn a layer file locally
|
|
60
|
+
npm run dev # Remotion Studio
|
|
118
61
|
```
|
|
119
62
|
|
|
120
|
-
|
|
121
|
-
|
|
63
|
+
The token comes from `~/.goosetools/env`, shared by every Goose Tools worker on
|
|
64
|
+
the machine. `overlay:install` writes
|
|
65
|
+
`~/Library/LaunchAgents/com.goosetools.overlay.plist`; logs land in
|
|
122
66
|
`~/.goosetools/overlay-worker.log`. The plist sets PATH explicitly — launchd
|
|
123
67
|
starts with an almost-empty environment, and the render shells out to ffmpeg,
|
|
124
|
-
npx and claude
|
|
125
|
-
|
|
68
|
+
npx and claude.
|
|
69
|
+
|
|
70
|
+
Stop the daemon before running a checkout, or the two split the queue:
|
|
71
|
+
`launchctl bootout gui/$(id -u)/com.goosetools.overlay`.
|
|
72
|
+
|
|
73
|
+
## Rendering
|
|
126
74
|
|
|
127
75
|
Two passes, and the split is the point. Remotion renders **only the overlay**,
|
|
128
76
|
alpha-only to ProRes 4444, at `--scale` so the 1080x1920 composition rasterizes
|
|
129
77
|
natively at the output resolution. ffmpeg then composites that over the
|
|
130
78
|
untouched source. The footage never enters Chrome, so a 4K clip stays 4K and
|
|
131
|
-
renders in seconds rather than minutes — and every coordinate in
|
|
132
|
-
|
|
79
|
+
renders in seconds rather than minutes — and every coordinate stays in
|
|
80
|
+
1080x1920 space no matter the output size.
|
|
133
81
|
|
|
134
82
|
Output is HEVC in `yuv420p`. That pixel format isn't cosmetic: the overlay
|
|
135
83
|
filter hands back BGRA, and an HEVC file in BGRA imports into iOS Photos as a
|
|
136
84
|
file that won't play.
|
|
137
85
|
|
|
138
|
-
## Series
|
|
139
|
-
|
|
140
|
-
What Prints? is special-cased in the worker — it runs your snippet, checks the
|
|
141
|
-
answer against a real run, applies the quality gates, and places the meter
|
|
142
|
-
against the footage. Nothing generated can do that, which is why it stays code.
|
|
143
|
-
|
|
144
|
-
Every other series is generated. Tapping "+ New series" on Goose Tools queues a
|
|
145
|
-
job; this machine reads your reference images with Claude and writes
|
|
146
|
-
`series/<slug>/`:
|
|
147
|
-
|
|
148
|
-
```
|
|
149
|
-
series/hot-take/
|
|
150
|
-
series.json the fields the form asks for, and where each layer sits
|
|
151
|
-
claim.html self-contained HTML+CSS, {{field}} placeholders
|
|
152
|
-
verdict.html
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
`series/` is gitignored — each worker builds its own from its own user's
|
|
156
|
-
references, so no two machines have the same set.
|
|
157
|
-
|
|
158
|
-
The hard rule for templates is **no CSS animation**. Remotion renders by seeking
|
|
159
|
-
to each frame and screenshotting, so a CSS animation or transition freezes on
|
|
160
|
-
whatever the first frame painted. Motion comes from the layer's `enter` value
|
|
161
|
-
(`fade`, `slide-up`, `slide-down`, `pop`), which the composition drives with
|
|
162
|
-
Remotion's own spring. Templates also can't carry `<script>` or any external
|
|
163
|
-
URL; `scripts/series.ts` rejects a generation that does, and the composition
|
|
164
|
-
strips scripts again at render time rather than trusting it.
|
|
165
|
-
|
|
166
86
|
## Instagram's safe area
|
|
167
87
|
|
|
168
|
-
`src/overlay/safeArea.ts` holds where Instagram draws over a reel, as
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
card as unsafe when nothing is near them.
|
|
88
|
+
`src/overlay/safeArea.ts` holds where Instagram draws over a reel, as three
|
|
89
|
+
rectangles rather than one inset margin — the action rail only runs down the
|
|
90
|
+
lower half. Layer anchors (`top`, `center`, `bottom`) already respect it.
|
|
172
91
|
|
|
173
92
|
| Zone | Area |
|
|
174
93
|
|---|---|
|
|
@@ -176,40 +95,4 @@ card as unsafe when nothing is near them.
|
|
|
176
95
|
| action rail | x 820-1080, y 1080-1700 |
|
|
177
96
|
| caption | x 0-820, y 1500-1920 |
|
|
178
97
|
|
|
179
|
-
|
|
180
|
-
real overlay bounding boxes out of a render (same two-render diff as the
|
|
181
|
-
legibility check) and reports anything intersecting a zone. It also flags
|
|
182
|
-
elements outside the 3:4 centre crop that the profile grid uses for thumbnails.
|
|
183
|
-
|
|
184
|
-
Numbers are conservative — IG moves its chrome between app versions and notched
|
|
185
|
-
phones differ from flat ones. If something looks wrong on a real phone, adjust
|
|
186
|
-
the zones there and every check follows.
|
|
187
|
-
|
|
188
|
-
## Does it read on a phone?
|
|
189
|
-
|
|
190
|
-
`npx tsx scripts/check-legibility.ts episodes/what-prints-00X.json` renders three
|
|
191
|
-
frames twice — once whole, once with `hideOverlays` so only the background
|
|
192
|
-
draws — diffs them to find the overlay pixels, and measures real contrast.
|
|
193
|
-
|
|
194
|
-
Text (the title) is scored on *local* contrast: strokes against what's
|
|
195
|
-
immediately around them in the finished frame, so the dark halo counts. Panels
|
|
196
|
-
(the code card, the difficulty pill) are scored on how well the opaque block
|
|
197
|
-
separates from the footage, because their internal contrast is fixed by design.
|
|
198
|
-
Thresholds are calibrated against the known-bad title, which measured 4.3:1 and
|
|
199
|
-
would have passed a textbook WCAG bar; the fixed one measures 12.3:1.
|
|
200
|
-
|
|
201
|
-
`new-episode.ts` runs this automatically after placing the meter.
|
|
202
|
-
|
|
203
|
-
## Notes
|
|
204
|
-
|
|
205
|
-
- macOS blocks direct shell reads of `~/Desktop` (TCC). All Desktop I/O goes
|
|
206
|
-
through Finder AppleScript — that's why ingest/export use `osascript`.
|
|
207
|
-
- Exports are silent; add trending audio in Instagram.
|
|
208
|
-
- The title and code card are in fixed positions every episode (title centred
|
|
209
|
-
200px from the top, card centred at 86% width). The difficulty meter has a
|
|
210
|
-
house position too — lower-left at (70, 1400) — and `scripts/place-overlay.ts`
|
|
211
|
-
only moves it when the footage there is more than 25% busier than the calmest
|
|
212
|
-
spot available. The comparison is relative because footage with Erin in frame
|
|
213
|
-
scores high everywhere. Override by editing meterX/meterY.
|
|
214
|
-
- New series (Interviewer, Games): add `src/series/<name>/` with its own
|
|
215
|
-
composition, register it in `src/Root.tsx`, same episode-JSON pattern.
|
|
98
|
+
Numbers are conservative — IG moves its chrome between app versions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overlay-factory-worker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The Goose Tools Overlay Factory worker — your computer renders reel overlays for goosetools.com with Remotion and ffmpeg.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
"worker",
|
|
57
57
|
"scripts",
|
|
58
58
|
"src",
|
|
59
|
-
"public/fonts/Handjet-variable.woff2",
|
|
60
59
|
"remotion.config.ts",
|
|
61
60
|
"tsconfig.json",
|
|
62
61
|
"README.md"
|