overlay-factory-worker 0.1.1 → 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.
Files changed (45) hide show
  1. package/README.md +67 -184
  2. package/package.json +1 -2
  3. package/scripts/overlay-worker.ts +90 -514
  4. package/scripts/render-overlay.ts +1 -1
  5. package/scripts/series.ts +292 -44
  6. package/scripts/state-dir.ts +12 -9
  7. package/src/Root.tsx +7 -81
  8. package/src/overlay/Composition.tsx +8 -90
  9. package/src/overlay/types.ts +11 -57
  10. package/worker/cli.mjs +3 -3
  11. package/worker/service.mjs +10 -24
  12. package/public/fonts/Handjet-variable.woff2 +0 -0
  13. package/scripts/check-legibility.ts +0 -284
  14. package/scripts/check-safe-area.ts +0 -148
  15. package/scripts/export.sh +0 -31
  16. package/scripts/ig-probe.ts +0 -83
  17. package/scripts/ig-sync.ts +0 -204
  18. package/scripts/ingest.sh +0 -34
  19. package/scripts/library.ts +0 -143
  20. package/scripts/look-card.ts +0 -107
  21. package/scripts/look-store.ts +0 -176
  22. package/scripts/make-card.ts +0 -237
  23. package/scripts/merge-index.ts +0 -74
  24. package/scripts/new-episode.ts +0 -149
  25. package/scripts/place-overlay.ts +0 -359
  26. package/scripts/prep-card.ts +0 -73
  27. package/scripts/quality.ts +0 -0
  28. package/scripts/report.ts +0 -127
  29. package/scripts/rerender-cards.ts +0 -116
  30. package/scripts/set-difficulty.ts +0 -62
  31. package/scripts/verify.ts +0 -149
  32. package/scripts/wp-restock.ts +0 -281
  33. package/src/lab/FontLab.tsx +0 -50
  34. package/src/lab/FontSheet.tsx +0 -188
  35. package/src/lab/PillLab.tsx +0 -121
  36. package/src/overlay/DifficultyMeter.tsx +0 -86
  37. package/src/overlay/PixelText.tsx +0 -134
  38. package/src/overlay/Title.tsx +0 -75
  39. package/src/overlay/cardLayout.ts +0 -94
  40. package/src/overlay/fonts.ts +0 -19
  41. package/src/overlay/look.ts +0 -155
  42. package/src/series/what-prints/CodeCard.tsx +0 -107
  43. package/src/series/what-prints/Composition.tsx +0 -106
  44. package/src/series/what-prints/codeCardTypes.ts +0 -105
  45. package/src/series/what-prints/types.ts +0 -22
package/README.md CHANGED
@@ -1,174 +1,93 @@
1
- # reel-factory
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
- Automated overlay editing for Erin's reel series. Feed it b-roll + a code card +
4
- an episode JSON, get a finished vertical MP4 with the "What **Prints?**" title,
5
- the code card, and a timed difficulty reveal.
6
-
7
- ## Flow
8
-
9
- ```bash
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
- ```bash
107
- npm run overlay -- clip.mov overlay.json out.mp4
108
- ```
50
+ One-offs (the Single tab) use the same generation with the text baked in and
51
+ nothing kept.
109
52
 
110
- Run the worker so `/dashboard/overlay` can send jobs to this machine. The token
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
- `overlay:install` writes `~/Library/LaunchAgents/com.goosetools.overlay.plist`,
121
- matching the Carousel/Caption/Brand agents. Logs land in
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, so an inherited-PATH assumption is what leaves one of these
125
- agents running but failing every job.
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 the overlay
132
- components stays in 1080x1920 space no matter the output size.
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
- three rectangles rather than one inset margin — the action rail only runs down
170
- the lower half, so a flat "keep the right 260px clear" would flag the title and
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
- `npx tsx scripts/check-safe-area.ts episodes/what-prints-00X.json` measures the
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.1.1",
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"