explainer-video 0.0.0-stage → 0.4.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 (150) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +152 -2
  4. package/cordis.patch.yml +7 -0
  5. package/lib/index.js +35 -0
  6. package/package.json +56 -4
  7. package/plugin.json +13 -0
  8. package/skills/coursework-author/.synced-from +5 -0
  9. package/skills/coursework-author/SKILL.md +209 -0
  10. package/skills/craft-codeflow-flows/SKILL.md +116 -0
  11. package/skills/craft-codeflow-mine/SKILL.md +207 -0
  12. package/skills/craft-codeflow-plan/SKILL.md +65 -0
  13. package/skills/craft-codeflow-scene/SKILL.md +168 -0
  14. package/skills/craft-video-align/SKILL.md +109 -0
  15. package/skills/craft-video-assemble/SKILL.md +69 -0
  16. package/skills/craft-video-audio/SKILL.md +59 -0
  17. package/skills/craft-video-enrich/SKILL.md +46 -0
  18. package/skills/craft-video-plan/SKILL.md +57 -0
  19. package/skills/craft-video-publish/SKILL.md +79 -0
  20. package/skills/craft-video-render/SKILL.md +68 -0
  21. package/skills/craft-video-scene/SKILL.md +112 -0
  22. package/skills/craft-video-script/SKILL.md +80 -0
  23. package/skills/explainer-video-from-codebase/FLOWS.md +577 -0
  24. package/skills/explainer-video-from-codebase/SKILL.md +659 -0
  25. package/skills/explainer-video-from-codebase/tools/apply-flows.mjs +531 -0
  26. package/skills/explainer-video-from-codebase/tools/approve-flows.mjs +56 -0
  27. package/skills/explainer-video-from-codebase/tools/codemap-md.mjs +116 -0
  28. package/skills/explainer-video-from-codebase/tools/config/dotenv.mjs +13 -0
  29. package/skills/explainer-video-from-codebase/tools/config/json.mjs +14 -0
  30. package/skills/explainer-video-from-codebase/tools/config/redact.mjs +16 -0
  31. package/skills/explainer-video-from-codebase/tools/config/toml-lite.mjs +13 -0
  32. package/skills/explainer-video-from-codebase/tools/config/yaml-lite.mjs +18 -0
  33. package/skills/explainer-video-from-codebase/tools/excerpt-check.mjs +186 -0
  34. package/skills/explainer-video-from-codebase/tools/excerpt.mjs +116 -0
  35. package/skills/explainer-video-from-codebase/tools/flow-diagram.mjs +168 -0
  36. package/skills/explainer-video-from-codebase/tools/flow-plan.mjs +129 -0
  37. package/skills/explainer-video-from-codebase/tools/flows-md.mjs +87 -0
  38. package/skills/explainer-video-from-codebase/tools/lang/c.mjs +684 -0
  39. package/skills/explainer-video-from-codebase/tools/lang/go.mjs +673 -0
  40. package/skills/explainer-video-from-codebase/tools/lang/python-config.mjs +143 -0
  41. package/skills/explainer-video-from-codebase/tools/lang/python-deps.mjs +62 -0
  42. package/skills/explainer-video-from-codebase/tools/lang/python-stdlib.mjs +24 -0
  43. package/skills/explainer-video-from-codebase/tools/lang/python.mjs +609 -0
  44. package/skills/explainer-video-from-codebase/tools/lang/typescript.mjs +963 -0
  45. package/skills/explainer-video-from-codebase/tools/lib/decompose.mjs +102 -0
  46. package/skills/explainer-video-from-codebase/tools/lib/groups.mjs +77 -0
  47. package/skills/explainer-video-from-codebase/tools/lib/observe-coverage.mjs +68 -0
  48. package/skills/explainer-video-from-codebase/tools/lib/observe-v8.mjs +232 -0
  49. package/skills/explainer-video-from-codebase/tools/lib/observe.mjs +162 -0
  50. package/skills/explainer-video-from-codebase/tools/lib/redact.mjs +28 -0
  51. package/skills/explainer-video-from-codebase/tools/lib/sandbox.mjs +97 -0
  52. package/skills/explainer-video-from-codebase/tools/lib/test-links.mjs +28 -0
  53. package/skills/explainer-video-from-codebase/tools/lib/toml.mjs +129 -0
  54. package/skills/explainer-video-from-codebase/tools/lib/trace-c.mjs +95 -0
  55. package/skills/explainer-video-from-codebase/tools/lib/trace-go.mjs +101 -0
  56. package/skills/explainer-video-from-codebase/tools/lib/trace-js.mjs +136 -0
  57. package/skills/explainer-video-from-codebase/tools/lib/tree-sitter.mjs +19 -0
  58. package/skills/explainer-video-from-codebase/tools/lib/walk.mjs +138 -0
  59. package/skills/explainer-video-from-codebase/tools/mine.mjs +409 -0
  60. package/skills/explainer-video-from-codebase/tools/propose.mjs +127 -0
  61. package/skills/explainer-video-from-codebase/tools/rank.mjs +119 -0
  62. package/skills/explainer-video-from-codebase/tools/ranking-md.mjs +76 -0
  63. package/skills/explainer-video-from-codebase/tools/rules/c-handlers.mjs +53 -0
  64. package/skills/explainer-video-from-codebase/tools/rules/celery-taskiq.mjs +48 -0
  65. package/skills/explainer-video-from-codebase/tools/rules/click-typer.mjs +6 -0
  66. package/skills/explainer-video-from-codebase/tools/rules/express.mjs +72 -0
  67. package/skills/explainer-video-from-codebase/tools/rules/fastapi.mjs +99 -0
  68. package/skills/explainer-video-from-codebase/tools/rules/go-http.mjs +173 -0
  69. package/skills/explainer-video-from-codebase/tools/rules/go-test.mjs +108 -0
  70. package/skills/explainer-video-from-codebase/tools/rules/grpc.mjs +57 -0
  71. package/skills/explainer-video-from-codebase/tools/rules/gtest.mjs +97 -0
  72. package/skills/explainer-video-from-codebase/tools/rules/haystack.mjs +55 -0
  73. package/skills/explainer-video-from-codebase/tools/rules/jest-vitest.mjs +100 -0
  74. package/skills/explainer-video-from-codebase/tools/rules/js-http-client.mjs +111 -0
  75. package/skills/explainer-video-from-codebase/tools/rules/nextjs.mjs +53 -0
  76. package/skills/explainer-video-from-codebase/tools/rules/process-spawn.mjs +49 -0
  77. package/skills/explainer-video-from-codebase/tools/rules/py-http-client.mjs +26 -0
  78. package/skills/explainer-video-from-codebase/tools/rules/pytest.mjs +107 -0
  79. package/skills/explainer-video-from-codebase/tools/rules/react-router.mjs +17 -0
  80. package/skills/explainer-video-from-codebase/tools/rules/registry.mjs +31 -0
  81. package/skills/explainer-video-from-codebase/tools/trace/c/fmtrace-sh +14 -0
  82. package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-net.cjs +33 -0
  83. package/skills/explainer-video-from-codebase/tools/trace/js/fmtrace-srcmap.cjs +41 -0
  84. package/skills/explainer-video-from-codebase/tools/trace/python/fmtrace_plugin.py +87 -0
  85. package/skills/explainer-video-from-codebase/tools/trace/python/sitecustomize.py +330 -0
  86. package/skills/explainer-video-from-codebase/tools/trace/sandbox/linux-bwrap.sh +73 -0
  87. package/skills/explainer-video-from-codebase/tools/trace/sandbox/macos-nonet.sb +41 -0
  88. package/skills/explainer-video-from-codebase/tools/trace.mjs +577 -0
  89. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-c.txt +21 -0
  90. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-cpp.txt +21 -0
  91. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-go.txt +21 -0
  92. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-javascript.txt +21 -0
  93. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-python.txt +21 -0
  94. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/tree-sitter-typescript.txt +21 -0
  95. package/skills/explainer-video-from-codebase/vendor/tree-sitter/LICENSES/web-tree-sitter.txt +21 -0
  96. package/skills/explainer-video-from-codebase/vendor/tree-sitter/MANIFEST.json +172 -0
  97. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-c.wasm +0 -0
  98. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-cpp.wasm +0 -0
  99. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-go.wasm +0 -0
  100. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-javascript.wasm +0 -0
  101. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-python.wasm +0 -0
  102. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-tsx.wasm +0 -0
  103. package/skills/explainer-video-from-codebase/vendor/tree-sitter/tree-sitter-typescript.wasm +0 -0
  104. package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.js +4081 -0
  105. package/skills/explainer-video-from-codebase/vendor/tree-sitter/web-tree-sitter.wasm +0 -0
  106. package/skills/explainer-video-from-coursework/ARCHITECTURE.md +59 -0
  107. package/skills/explainer-video-from-coursework/PIPELINE.md +343 -0
  108. package/skills/explainer-video-from-coursework/ROADMAP.md +725 -0
  109. package/skills/explainer-video-from-coursework/SERIES.md +232 -0
  110. package/skills/explainer-video-from-coursework/SETUP.md +105 -0
  111. package/skills/explainer-video-from-coursework/SKILL.md +858 -0
  112. package/skills/explainer-video-from-coursework/scenes/_pt-chart.js +12 -0
  113. package/skills/explainer-video-from-coursework/scenes/_pt-equation.js +10 -0
  114. package/skills/explainer-video-from-coursework/scenes/_pt-hubspoke.js +7 -0
  115. package/skills/explainer-video-from-coursework/scenes/_pt-loopflow.js +7 -0
  116. package/skills/explainer-video-from-coursework/scenes/_pt-sequence.js +12 -0
  117. package/skills/explainer-video-from-coursework/scenes/_pt-timeline.js +11 -0
  118. package/skills/explainer-video-from-coursework/src/scene-code.html +127 -0
  119. package/skills/explainer-video-from-coursework/src/scene-code.js +379 -0
  120. package/skills/explainer-video-from-coursework/src/scene-core.js +1250 -0
  121. package/skills/explainer-video-from-coursework/src/scene-diagram.html +50 -0
  122. package/skills/explainer-video-from-coursework/src/scene-svg.html +50 -0
  123. package/skills/explainer-video-from-coursework/src/timeline.js +297 -0
  124. package/skills/explainer-video-from-coursework/src/timing-chart.js +160 -0
  125. package/skills/explainer-video-from-coursework/tools/align.mjs +419 -0
  126. package/skills/explainer-video-from-coursework/tools/apply-enrichment.mjs +206 -0
  127. package/skills/explainer-video-from-coursework/tools/assemble.mjs +223 -0
  128. package/skills/explainer-video-from-coursework/tools/build-audio.mjs +234 -0
  129. package/skills/explainer-video-from-coursework/tools/chromium.mjs +73 -0
  130. package/skills/explainer-video-from-coursework/tools/debug-page.mjs +75 -0
  131. package/skills/explainer-video-from-coursework/tools/env.mjs +30 -0
  132. package/skills/explainer-video-from-coursework/tools/frame-dedup.mjs +131 -0
  133. package/skills/explainer-video-from-coursework/tools/key-source.mjs +151 -0
  134. package/skills/explainer-video-from-coursework/tools/model.mjs +21 -0
  135. package/skills/explainer-video-from-coursework/tools/naming.mjs +38 -0
  136. package/skills/explainer-video-from-coursework/tools/onset-audit.mjs +51 -0
  137. package/skills/explainer-video-from-coursework/tools/pacing-check.mjs +350 -0
  138. package/skills/explainer-video-from-coursework/tools/planner.mjs +616 -0
  139. package/skills/explainer-video-from-coursework/tools/publish.mjs +230 -0
  140. package/skills/explainer-video-from-coursework/tools/render.mjs +205 -0
  141. package/skills/explainer-video-from-coursework/tools/resolve-beats.mjs +239 -0
  142. package/skills/explainer-video-from-coursework/tools/script-check.mjs +174 -0
  143. package/skills/explainer-video-from-coursework/tools/stamp.mjs +71 -0
  144. package/skills/explainer-video-from-coursework/tools/timing-report.mjs +299 -0
  145. package/skills/explainer-video-from-coursework/tools/transcribe-gemini.mjs +96 -0
  146. package/skills/explainer-video-from-coursework/tools/tts-gemini.mjs +394 -0
  147. package/skills/explainer-video-from-coursework/tools/verify-audio.mjs +88 -0
  148. package/skills/explainer-video-from-coursework/tools/workdir.mjs +124 -0
  149. package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js +8 -0
  150. package/skills/explainer-video-from-coursework/vendor/plotly-basic.min.js.LICENSE.txt +16 -0
@@ -0,0 +1,232 @@
1
+ # SERIES.md — running a multi-video series
2
+
3
+ Stages 1–2 run once over the whole notes directory. Stages 3–8 run **per video**.
4
+ This file defines how a series run is organised, recorded, and resumed.
5
+
6
+ It exists because nothing else did. A nine-video run had no durable record of which
7
+ videos were selected, no per-video progress state, and no defined execution topology,
8
+ so the orchestrating agent hand-rolled a queue in `/tmp` and kept the remaining list in
9
+ its own context. When the session was interrupted, the pipeline's state was interrupted
10
+ with it, and the queue had to be re-derived three times. **A resumed session must be
11
+ able to continue from the filesystem alone.**
12
+
13
+ ---
14
+
15
+ ## Topology
16
+
17
+ ```
18
+ assemble the run (once)
19
+ ├─ read plan.enriched.json + the chosen indices → write series.json (once, never rewritten)
20
+ └─ loop, lazily:
21
+ acquire a free port → spawn ONE per-video agent → on settle, read its state.json
22
+ ```
23
+
24
+ **Two roles, strictly separated.**
25
+
26
+ | Role | Does | Never does |
27
+ |---|---|---|
28
+ | **Orchestrator** (the main session) | Writes `series.json`. Dispatches one agent per free port. Reads `state.json` and the timing ledger. Reports. | Runs any skill stage. Renders anything. Polls anything. |
29
+
30
+ | Role | Does |
31
+ |---|---|
32
+ | **Per-video agent** (one subagent per video) | Stages 3→8 for its own video, end to end, including script authoring and the visual QA loop. Owns its work dir. Writes its own `state.json`. |
33
+
34
+ **The port is the admission token.** A port in the pool means "one more video may be in
35
+ flight". The orchestrator spawns only when a port is free, so an agent never queues and
36
+ never waits — which is the entire point of lazy dispatch, and why no lock can starve.
37
+ A per-video agent's lifetime is its render window bound: author → audio → align → stills
38
+ → render → QA → assemble, typically 1.5–2.5 hours.
39
+
40
+ **Consequence, stated plainly:** total in-flight videos ≤ `parallel`. On an 8-core,
41
+ 8 GB machine that is 2–3. Authoring capacity is therefore bounded by render capacity.
42
+ Whether that costs throughput depends on how well concurrent renders scale, which is
43
+ unmeasured — see *Open parameter* at the end.
44
+
45
+ ---
46
+
47
+ ## Files
48
+
49
+ ### `out/<name>/series.json` — written once by the orchestrator
50
+
51
+ ```json
52
+ {
53
+ "plan": "out/<name>/plan.enriched.json",
54
+ "destination": "videos/",
55
+ "parallel": 2,
56
+ "portBase": 9333,
57
+ "videos": [
58
+ { "index": 1, "work": "out/<name>/01", "script": "out/<name>/scripts/01-<slug>.json",
59
+ "slug": "1-the-instruction-layer-the-constitution-your-agent-runs-on",
60
+ "name": "the-instruction-layer" }
61
+ ]
62
+ }
63
+ ```
64
+
65
+ `parallel` is the **one** concurrency number: it is simultaneously the pool size, the
66
+ batch size, and the maximum number of concurrent per-video agents — because the port *is*
67
+ the admission token, so one in-flight video means one port means one subagent. Ports are
68
+ derived, never stored: slot *i* renders on `portBase + i`. Storing a port list as well
69
+ would let the two drift, and a resumed session would then hand out a port a live agent
70
+ already holds.
71
+
72
+ It lives in this file rather than being a flag because a resumed session must not pick a
73
+ different value: `parallel` determines port allocation, and changing it mid-run
74
+ invalidates every port already claimed.
75
+
76
+ Titles are **not** duplicated here — they live in `plan.enriched.json`, which stays the
77
+ single source of truth for content. Two copies would drift.
78
+
79
+ ### `out/<name>/<NN>/state.json` — written by that video's agent, and only by it
80
+
81
+ ```json
82
+ {
83
+ "videoIndex": 1,
84
+ "status": "rendering",
85
+ "stages": {
86
+ "3": { "done": true, "attempts": 1, "ended_at": "…", "by": "agent-abc" },
87
+ "4": { "done": true, "attempts": 1, "ended_at": "…" },
88
+ "4b": { "done": true, "attempts": 1, "ended_at": "…" },
89
+ "5": { "done": true, "attempts": 1, "ended_at": "…" },
90
+ "6": { "done": true, "attempts": 1, "ended_at": "…" },
91
+ "7": { "done": true, "attempts": 1, "ended_at": "…" },
92
+ "8": { "done": false, "attempts": 1, "last_error": null }
93
+ },
94
+ "artefacts": {
95
+ "script": "…", "lines": "…", "beats": "…", "manifest": "…",
96
+ "mp4": "videos/<slug>/<name>.mp4",
97
+ "description": "videos/<slug>/description.md", "captions": "videos/<slug>/captions.srt"
98
+ },
99
+ "render": { "port": 9333, "attempts": 1 },
100
+ "qa": { "stills_checked": true, "beats_audited": 48, "verdict": "accept" }
101
+ }
102
+ ```
103
+
104
+ `status` ∈ `queued · authoring · audio · aligning · scenes · publishing · rendering ·
105
+ qa · done · failed · needs-fresh`.
106
+
107
+ **One writer per file** — that video's agent. Write atomically (temp + `renameSync`).
108
+ No shared file has nine writers, so there is nothing to merge and no lost updates.
109
+
110
+ `state.json` is a *claim about* progress, not proof of it: on resume, verify the
111
+ artefacts actually exist before trusting a `"done": true`.
112
+
113
+ ### `out/<name>/<NN>/.video-identity` — written by the *tools*, not the agent
114
+
115
+ Stamped by `tools/workdir.mjs` on the first stage that touches the dir; refuses a
116
+ `videoIndex` mismatch with exit 3. Identity answers *whose dir is this*; `state.json`
117
+ answers *how far along*. Keep them separate: the guard must work for a human running a
118
+ single stage by hand, with no agent and no state file.
119
+
120
+ ### `out/<name>/<NN>/intent.md` — written by the agent at stage 5
121
+
122
+ One line per beat: what should be on screen, and on which words.
123
+
124
+ ```
125
+ beat 3 "gate" on "No authorization check" three red pills appear left
126
+ beat 4 "gate" on "No confirmation" fourth pill, right column
127
+ beat 9 "fix3" on "Only the tool…can guarantee" final line lands, amber
128
+ ```
129
+
130
+ Its purpose is QA fidelity — an agent judging frames against its *own recorded intent*
131
+ rather than against a memory of it — and anti-leniency, since a checklist resists
132
+ rubber-stamping far better than an impression does. Read it fresh at QA time; do not
133
+ recall it.
134
+
135
+ ---
136
+
137
+ ## The per-video agent's duties
138
+
139
+ Each stage, in order, with its gate:
140
+
141
+ | Stage | Duty | Gate before moving on |
142
+ |---|---|---|
143
+ | **3** | Write `scripts/<NN>-<slug>.json`. **Read the plan's `sectionSpans` for this video and the source markdown itself** — a summary is not enough for a faithful script | `script-check` exits 0 |
144
+ | **4** | `build-audio --out out/<name>/<NN>` — measured durations, tiled windows | exits 0; every segment has a window |
145
+ | **4b** | `align` — word and line timings | exits 0; a line for every segment |
146
+ | **5** | Author `scenes/` + `manifest.json` + `intent.md`; `resolve-beats`; `pacing-check` per segment | pacing is sound for every segment |
147
+ | **5·QA** | **Stills gate** — `render --shots` at 3–5 mid-segment times on the assigned port, then **look at them** | no collision, no missing heading, nothing cropped |
148
+ | **6** | `publish.json` → `publish` | chapters and cues validate |
149
+ | **7–8** | `assemble --dest videos` | exits 0 with `audio ok` |
150
+
151
+ Then: sample the finished MP4, look, and either accept or fix-and-re-render.
152
+
153
+ **Fix loops are bounded.** Two render attempts, two QA rounds. On the second failure
154
+ write `status: "needs-fresh"` with the reason and stop — a drifting session that keeps
155
+ re-rendering is worse than a fresh one that resumes from `state.json`.
156
+
157
+ **Release the port between attempts**, not at the end of the agent's life, so a fix loop
158
+ cannot starve the other videos waiting to be admitted.
159
+
160
+ ---
161
+
162
+ ## Hard rules
163
+
164
+ 1. **Never write into another video's work dir.** One dir per video; the guard enforces
165
+ it, and the refusal (exit 3) is not to be worked around by deleting the marker.
166
+ 2. **One port per agent.** Never omit `--port`; a second render on 9333 does not fail
167
+ cleanly, it attaches to the running browser's page. `assemble.mjs --port <n>` forwards
168
+ the port to its per-segment renders and **refuses a port already in use**, so the
169
+ per-video agent can own stage 8 end to end on its own port — and the `parallel`
170
+ batch applies to stage 8, not just to the stage-7 QA window.
171
+ 3. **Never run `build-audio --only` against a finished video.** Partial runs are isolated
172
+ now, but the intent of `--only` is a smoke test on a video you have not finished.
173
+ 4. **Scope every per-video command at `out/<name>/<NN>`.** `stamp`, `planner` and
174
+ `timing-report` legitimately take the plan dir; nothing else does.
175
+ 5. **`timing.jsonl` is append-only and already concurrency-safe** — `appendFileSync` of a
176
+ sub-4KB line is atomic. Do not rewrite it as read-modify-write.
177
+ 6. **Scratch is disposable, deliverables are not.** `out/` is git-ignored; **`videos/`
178
+ currently is not** — a `git clean -fd` in the notes repo deletes the finished MP4s.
179
+
180
+ ---
181
+
182
+ ## Acceptance tests
183
+
184
+ Design against these; they are also how you would verify a re-implementation.
185
+
186
+ 1. **Cold resume.** Kill every session mid-run. A fresh agent with no conversation
187
+ history reads `series.json` + the `state.json` files and continues correctly.
188
+ 2. **No cross-contamination.** Two videos, one directory → exit 3, and the victim's
189
+ `timeline.json` byte-identical afterwards.
190
+ 3. **Port reclamation.** An agent killed while holding a port does not block the series;
191
+ the port is reclaimable by pid-liveness or age.
192
+ 4. **No silent partial.** A `--only` run leaves `timeline.json` and `narration.wav`
193
+ untouched and writes `timeline.<key>.json`.
194
+ 5. **Untouched happy path.** A dir holding plan files with no marker is accepted — the
195
+ guard must not fire on legitimate layouts.
196
+
197
+ ---
198
+
199
+ ## Batch size — measured
200
+
201
+ Measured on this machine (8 cores, 8 GB), one fixed 612-frame window of one real scene,
202
+ distinct work-dir copies, distinct ports, nothing else running:
203
+
204
+ | trial | per-slot wall fps | aggregate | vs solo |
205
+ |---|---|---|---|
206
+ | N=1 | 12.21 | 12.21 f/s | 100% |
207
+ | N=1 (repeat) | 12.46 | 12.46 f/s | 102% — noise is ~2% |
208
+ | **N=2** | 12.05 | **24.10 f/s** | **197%** |
209
+ | **N=3** | 10.77 | **32.31 f/s** | **265%** |
210
+
211
+ **Set `parallel: 3`.** A second render costs almost nothing (1.3% per slot); a third
212
+ costs ~12% per slot and still nets +34% aggregate over N=2. Concurrency buys *throughput*
213
+ here, not just latency, so the batch is justified on its own merits. Cap at 3 on 8 GB —
214
+ each slot is a Chromium plus an ffmpeg.
215
+
216
+ ### What the profile actually says
217
+
218
+ `render.mjs` reports 13.4 / 14.2 / 13.9 fps at N=1/2/3 while wall clock rises, so the
219
+ per-frame capture loop is **not** what degrades under contention — the extra time is
220
+ process startup and the ffmpeg encode. Overhead, computed per trial as wall minus
221
+ (612 ÷ reported fps): **N=1 ≈ 4.4 s, N=2 ≈ 7.7 s, N=3 ≈ 12.8 s**.
222
+
223
+ That overhead is paid **per segment, not per video**, because `assemble` invokes
224
+ `render.mjs` once per segment and each invocation launches a fresh Chromium and reloads
225
+ the page. On a 48-segment video that is ~48 × 4.4 s ≈ **3.5 minutes of pure relaunch**,
226
+ about 7% of a 50-minute render — a cheaper win than a codec or pipe change, and it needs
227
+ no change to how frames are captured. (An earlier draft of this section said ~5 minutes /
228
+ 10%, from flatly assuming 14 fps; the per-trial figures above are the measured ones.)
229
+
230
+ **Consequence for this topology:** `frames/` and `preview/` are derived from `--work`, so
231
+ two concurrent renders can never share a work dir even on different ports. One dir per
232
+ video satisfies this by construction; a benchmark or smoke test must copy the dir per slot.
@@ -0,0 +1,105 @@
1
+ # Setup — what this skill needs on the machine
2
+
3
+ One-time environment prerequisites. `SKILL.md` and the per-stage files point here; run
4
+ everything else from the **scratch root** (the directory holding `out/`, which Stage 0
5
+ fixes and which defaults beside your notes), not the skill install folder. Everything the
6
+ pipeline produces beyond this it makes itself under `out/`.
7
+
8
+ ## Node ≥ 22
9
+
10
+ `render.mjs` and `debug-page.mjs` rely on Node's built-in global `WebSocket`. Nothing
11
+ declares it, so on older Node it fails cryptically. Check with `node --version`.
12
+
13
+ ## ffmpeg + ffprobe
14
+
15
+ Frame → H.264 encoding, muxing, and the `ffprobe` duration measurement. Both must be on
16
+ `PATH`.
17
+
18
+ ## Headless Chromium
19
+
20
+ The render stage captures frames through the headless shell. One command covers all three
21
+ OSes; Playwright puts the binary in the per-OS cache folder:
22
+
23
+ ```bash
24
+ npx playwright install --with-deps --only-shell
25
+ ```
26
+
27
+ - `--only-shell` downloads just the headless shell, not the full Chromium browser.
28
+ - `--with-deps` also installs the OS system libraries — needed on a bare Linux box; on
29
+ macOS and Windows there are no extra dependencies to install.
30
+
31
+ `tools/chromium.mjs` finds it in the cache — `~/Library/Caches/ms-playwright` (macOS) ·
32
+ `~/.cache/ms-playwright` (Linux) · `%LOCALAPPDATA%\ms-playwright` (Windows) — or honours
33
+ `CHROME_BIN` / `PLAYWRIGHT_BROWSERS_PATH` if the browser lives somewhere else.
34
+
35
+ ([source](https://playwright.dev/docs/browsers#chromium-headless-shell))
36
+
37
+ ## Transcribe quota (stage 4b)
38
+
39
+ `tools/align.mjs` calls `gemini-3.5-transcribe`, which has a **hard per-day cap** that
40
+ is separate from the TTS model and cannot be batched around:
41
+
42
+ | Limit | Value | Source |
43
+ |---|---|---|
44
+ | Requests/day per project per model | **100** | API `QuotaFailure` → `GenerateRequestsPerDayPerProjectPerModel`, `quotaValue: "100"` |
45
+ | Audio per request | 1 hour — **30 minutes when word-level timestamps are enabled** | model card |
46
+ | Batch API | **not supported** | model card |
47
+ | Custom vocabulary + word timestamps | mutually exclusive | transcribe guide |
48
+
49
+ Exceeding the daily cap returns HTTP **429** with a `RetryInfo.retryDelay` (~18 h) and
50
+ nothing else in the pipeline is affected — TTS uses a different model with its own
51
+ quota.
52
+
53
+ Practical consequences:
54
+
55
+ - `align.mjs` transcribes the **whole narration track in one request** by default
56
+ (per-segment would be ~30 requests per video, i.e. ~3 videos/day). Above ~28 minutes
57
+ of track it cuts at segment boundaries into multiple passes, because 30 minutes is
58
+ the model ceiling with word timings.
59
+ - `--per-segment` remains available as a fallback; it is chosen automatically when
60
+ only a few segments are missing, where it is cheaper than a full pass.
61
+ - A quota failure **stops the run with exit 3** rather than falling back and burning
62
+ the rest of the budget on doomed requests.
63
+ - Alignment is a **one-time cost per video** — `narration/<key>.words.json` is cached
64
+ and keyed on the WAV plus the script text. **Commit those files** and alignment is
65
+ never paid for twice.
66
+
67
+ ## Gemini API key
68
+
69
+ Read from the OS's native secret store — **never env, never a plaintext file**. Store it
70
+ once (a free key: <https://aistudio.google.com/apikey>).
71
+
72
+ **macOS — Keychain.** Either way stores the same item:
73
+
74
+ - GUI: **Keychain Access** (Applications → Utilities) → File → **New Password Item…** →
75
+ *Keychain Item Name* `gemini-tts` · *Account Name* your username · *Password* the key →
76
+ **Add**. ([source](https://its.uiowa.edu/services/macos/how-use-keychain-access-macos))
77
+ - CLI: `security add-generic-password -U -s gemini-tts -a "$USER" -w` (prompts for the key).
78
+
79
+ The script looks the item up by its **service** attribute, which the GUI dialog does not
80
+ expose. After creating it in the GUI, verify with `security find-generic-password -s
81
+ gemini-tts -w`; if that prints nothing, use the CLI command instead.
82
+
83
+ **Linux — Secret Service.** Either way:
84
+
85
+ - GUI: **Passwords and Keys** (Seahorse) → **+** → **Password** → pick a keyring, put
86
+ `gemini-tts` in *Description* and the key in *Password* → **Add**.
87
+ ([source](https://help.gnome.org/seahorse/passwords-stored-create.html))
88
+ - CLI: `secret-tool store --label="Gemini TTS" service gemini-tts` (reads the key from
89
+ stdin — paste it, then Ctrl-D).
90
+
91
+ Same caveat: the GUI stores a *Description*, not the `service` attribute the script keys
92
+ on. Verify with `secret-tool lookup service gemini-tts`; if empty, use the CLI.
93
+
94
+ **Windows — Credential Manager** (GUI only — there is no command-line store, and the
95
+ pipeline reads the credential with the Windows CredMan API):
96
+
97
+ Control Panel → **User Accounts** → **Credential Manager** → **Windows Credentials** →
98
+ **Add a generic credential** → *Internet or network address* `gemini-tts` · *User name*
99
+ anything (e.g. your username) · *Password* the key → **OK**.
100
+
101
+ ([Credential Manager in Windows](https://support.microsoft.com/en-us/windows/security/credential-manager-in-windows) ·
102
+ [CredRead — the retrieval API](https://learn.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credreadw))
103
+
104
+ One-off override: `--key <value>` on any tool — visible in the process list, so not for
105
+ everyday use.