creator-editing-studio 1.0.20260930

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 (69) hide show
  1. package/README.md +15 -0
  2. package/bin/init.mjs +72 -0
  3. package/package.json +18 -0
  4. package/template/CLAUDE.md +341 -0
  5. package/template/STYLE.md +165 -0
  6. package/template/package.json +45 -0
  7. package/template/remotion.config.ts +7 -0
  8. package/template/requirements.txt +17 -0
  9. package/template/scripts/align_script.py +106 -0
  10. package/template/scripts/assemble.mjs +364 -0
  11. package/template/scripts/beats.mjs +273 -0
  12. package/template/scripts/blur_regions.py +40 -0
  13. package/template/scripts/check_pair.py +90 -0
  14. package/template/scripts/doctor.mjs +141 -0
  15. package/template/scripts/find_cuts.py +45 -0
  16. package/template/scripts/grade.py +202 -0
  17. package/template/scripts/lib/media.mjs +228 -0
  18. package/template/scripts/lib/media.py +99 -0
  19. package/template/scripts/make_cutout.py +68 -0
  20. package/template/scripts/motion-check.mjs +268 -0
  21. package/template/scripts/motion-lint.mjs +122 -0
  22. package/template/scripts/new-video.mjs +49 -0
  23. package/template/scripts/prep.mjs +287 -0
  24. package/template/scripts/ramp.mjs +149 -0
  25. package/template/scripts/refs.mjs +72 -0
  26. package/template/scripts/refstyle.mjs +177 -0
  27. package/template/scripts/sounddesign.mjs +332 -0
  28. package/template/scripts/stills.mjs +64 -0
  29. package/template/scripts/sync-audio.mjs +134 -0
  30. package/template/scripts/transcribe.py +103 -0
  31. package/template/scripts/trim.mjs +374 -0
  32. package/template/src/Root.tsx +42 -0
  33. package/template/src/components/BrandBackground.tsx +22 -0
  34. package/template/src/components/Captions.tsx +141 -0
  35. package/template/src/components/Cursor.tsx +93 -0
  36. package/template/src/components/DrawnCircle.tsx +55 -0
  37. package/template/src/components/FilmOverlay.tsx +139 -0
  38. package/template/src/components/Hero.tsx +114 -0
  39. package/template/src/components/HeroTag.tsx +92 -0
  40. package/template/src/components/HighlightMark.tsx +34 -0
  41. package/template/src/components/LogoIcon.tsx +45 -0
  42. package/template/src/components/MacWindow.tsx +94 -0
  43. package/template/src/components/MaskReveal.tsx +34 -0
  44. package/template/src/components/MotionBlur.tsx +34 -0
  45. package/template/src/components/PlatformTile.tsx +153 -0
  46. package/template/src/components/Presence.tsx +27 -0
  47. package/template/src/components/RollingNumber.tsx +89 -0
  48. package/template/src/components/ScreenView.tsx +52 -0
  49. package/template/src/components/Stage.tsx +103 -0
  50. package/template/src/components/Transition.tsx +100 -0
  51. package/template/src/components/VelocityBlur.tsx +53 -0
  52. package/template/src/compositions/FilmTest.tsx +67 -0
  53. package/template/src/compositions/PresetTest.tsx +52 -0
  54. package/template/src/design/color.ts +6 -0
  55. package/template/src/design/glow.ts +11 -0
  56. package/template/src/design/logos.json +18 -0
  57. package/template/src/design/motion.ts +90 -0
  58. package/template/src/design/overlays.ts +121 -0
  59. package/template/src/design/presets.ts +65 -0
  60. package/template/src/design/tokens.ts +200 -0
  61. package/template/src/index.ts +4 -0
  62. package/template/src/lib/animate.ts +194 -0
  63. package/template/src/lib/camera.ts +58 -0
  64. package/template/src/lib/continuity.ts +86 -0
  65. package/template/src/lib/project.ts +13 -0
  66. package/template/src/lib/spine.ts +37 -0
  67. package/template/src/videos/sample/Sample.tsx +58 -0
  68. package/template/templates/BRIEF.md +88 -0
  69. package/template/tsconfig.json +17 -0
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # Creator Editing OS — studio
2
+
3
+ Scaffolds the studio into a folder:
4
+
5
+ ```bash
6
+ npx creator-editing-studio init my-studio
7
+ cd my-studio
8
+ npm install
9
+ ```
10
+
11
+ Existing files are never overwritten. Everything it writes is plain source you can read
12
+ before running it — the components, the scripts and the rulebook the studio edits by.
13
+
14
+ Setup is normally driven for you by the Creator Editing OS assistant; this package is the
15
+ part that puts the files on disk.
package/bin/init.mjs ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Scaffold a Creator Editing OS studio into a folder.
4
+ *
5
+ * npx @yonkomedia/creator-editing-studio init [folder]
6
+ *
7
+ * This exists because the previous delivery — the MCP server handing an assistant 300KB
8
+ * of source as a JSON blob to write to disk and then execute — is exactly the pattern
9
+ * coding agents block as untrusted code integration. It is a fair block, and no amount of
10
+ * rewording gets past it, because the objection is to the shape of the thing rather than
11
+ * to what it says.
12
+ *
13
+ * A published npm package is the channel this is supposed to travel down: a version
14
+ * number, a public page, a tarball anyone can inspect before it runs. Same files, an
15
+ * audit trail, and nothing to argue with.
16
+ *
17
+ * Existing files are never overwritten. Run it twice and the second run reports what is
18
+ * already there and changes nothing.
19
+ */
20
+ import {cpSync, existsSync, mkdirSync, readdirSync, statSync} from 'node:fs';
21
+ import {dirname, join, relative, resolve} from 'node:path';
22
+ import {fileURLToPath} from 'node:url';
23
+
24
+ const HERE = dirname(fileURLToPath(import.meta.url));
25
+ const TEMPLATE = resolve(HERE, '..', 'template');
26
+
27
+ const args = process.argv.slice(2);
28
+ const command = args[0] === 'init' ? args.slice(1) : args;
29
+ const target = resolve(process.cwd(), command[0] || '.');
30
+ const force = args.includes('--force');
31
+
32
+ if (!existsSync(TEMPLATE)) {
33
+ console.error('This package is missing its template files — please report it.');
34
+ process.exit(1);
35
+ }
36
+
37
+ const walk = (dir) =>
38
+ readdirSync(dir, {withFileTypes: true}).flatMap((entry) => {
39
+ const p = join(dir, entry.name);
40
+ return entry.isDirectory() ? walk(p) : [p];
41
+ });
42
+
43
+ const files = walk(TEMPLATE).map((p) => relative(TEMPLATE, p).split('\\').join('/'));
44
+
45
+ mkdirSync(target, {recursive: true});
46
+
47
+ const written = [];
48
+ const skipped = [];
49
+
50
+ for (const rel of files) {
51
+ const from = join(TEMPLATE, rel);
52
+ const to = join(target, rel);
53
+ if (existsSync(to) && !force) {
54
+ skipped.push(rel);
55
+ continue;
56
+ }
57
+ mkdirSync(dirname(to), {recursive: true});
58
+ cpSync(from, to);
59
+ written.push(rel);
60
+ }
61
+
62
+ const kb = (n) => `${Math.round(n / 1024)} KB`;
63
+ const bytes = written.reduce((sum, rel) => sum + statSync(join(target, rel)).size, 0);
64
+
65
+ console.log(`\nCreator Editing OS — studio files\n`);
66
+ console.log(` into ${target}`);
67
+ console.log(` wrote ${written.length} files (${kb(bytes)})`);
68
+ if (skipped.length) {
69
+ console.log(` kept ${skipped.length} existing file(s) — nothing was overwritten`);
70
+ console.log(` (use --force to replace them)`);
71
+ }
72
+ console.log(`\nNext: npm install\n`);
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "creator-editing-studio",
3
+ "version": "1.0.20260930",
4
+ "description": "Scaffolds a Creator Editing OS studio — components, scripts and rules for editing video from a plain-English brief.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "bin": {
8
+ "creator-editing-studio": "bin/init.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "template",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20"
17
+ }
18
+ }
@@ -0,0 +1,341 @@
1
+ # Editing studio — rulebook
2
+
3
+ Read this at the start of every session. It is how this studio edits, and it is not
4
+ optional. Quality lives in the system — the tokens, the components, the lint, the
5
+ frame-by-frame checks — never in one-off hacks. Never bypass the system to get something
6
+ on screen faster; that is the one habit that makes every later video worse.
7
+
8
+ ## Project map
9
+
10
+ ```
11
+ src/design/ tokens.ts is the look — colour, type, layout. Everything visual reads from it.
12
+ motion.ts is the timing. presets.ts is the named transitions and caption looks.
13
+ overlays.ts is the film looks. Only tokens.ts is yours to change freely.
14
+ src/lib/ animate, continuity, camera, spine, project — the helpers every video uses.
15
+ src/components/ the effects library, as real components. Captions, Transition, Stage, and the rest.
16
+ src/compositions/ PresetTest and FilmTest — reference comps showing every preset side by side.
17
+ src/videos/<name>/ one folder per video. This is where your work goes.
18
+ scripts/ the tools: prep, beats, trim, assemble, sound, and the two checks.
19
+ assets/ footage, screenshots, audio, fonts. Reachable as staticFile('footage/clip.mp4').
20
+ work/<name>/ everything the scripts produce: transcripts, beat maps, cut lists, cue sheets.
21
+ out/ finished renders.
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ - `npm run studio` — preview everything
27
+ - `npm run new-video -- <name>` — create a video's folders and its BRIEF.md
28
+ - `npm run prep -- <name>` — probe and conform footage, transcribe, align the script, find jump cuts
29
+ - `npm run trim -- <clip> [--words <words.json>]` — cut silence and filler, assign punch-in framings
30
+ - `npm run beats -- <music>` — tempo, beat, downbeat and onset map
31
+ - `npm run assemble -- --beats <beats.json> --clips <dir>` — build a cut off the music
32
+ - `npm run sound -- --cuts <cut list>` — place whooshes, impacts and risers, ducked under the voice
33
+ - `npm run ramp -- <clip> --ramp "3-5:1->0.4"` — eased speed ramps
34
+ - `npm run sync -- <a> <b>` — line up a second angle or a separate mic
35
+ - `npm run refstyle -- <reference clip>` — measure a reference edit's pacing and colour
36
+ - `npm run stills -- <Comp> --every=45` — many stills at once, with contact sheets
37
+ - `npm run check` — typecheck plus motion lint. Must pass.
38
+ - `npm run motion-check -- <Comp> --scale=2` — frame-by-frame POP / CUT / STUTTER detection
39
+ - `npm run render <Comp> out/video.mp4` — render
40
+
41
+ ## Your look comes first
42
+
43
+ `src/design/tokens.ts` ships with neutral placeholder values. Before making anything real,
44
+ set it up — ask *"build my design system from &lt;url&gt;"* and point it at a brand you
45
+ like, or hand over your own colours, fonts and sizes. Put font files in `assets/fonts/`.
46
+
47
+ Everything else in `src/design/` is the system and should be left alone. If a video needs
48
+ a colour or a size that is not in tokens.ts, the answer is to add it to tokens.ts, never to
49
+ type it into a component.
50
+
51
+ ## Non-negotiables
52
+
53
+ 1. **No magic numbers.** Durations, curves, springs, staggers, blur come from `src/design/motion.ts`.
54
+ Colours, fonts, sizes, spacing come from `src/design/tokens.ts`. If a needed token does not
55
+ exist, add it to the token file (with a comment) instead of inlining a value.
56
+ 2. **Timing derives from the spine.** Animation start frames come from word timestamps, beats,
57
+ or other elements' timings via helpers — never hand-typed frame numbers.
58
+ 3. **`npm run check` and `motion-check --scale=2` must pass** before any render is shown to the user.
59
+ 4. **Verify visually before claiming done** (see Verification). Never say something "looks good"
60
+ without having rendered and viewed the frames.
61
+
62
+ ---
63
+
64
+ ## The 12 motion laws
65
+
66
+ 1. **No linear easing** — except constant-velocity loops/scrolls (mark with `// motion-ok`).
67
+ 2. **Entrances ease-out (`EASE.out`), exits ease-in (`EASE.in`), on-screen A-to-B moves ease-in-out (`EASE.inOut`).**
68
+ 3. **Exits are faster than entrances** — `EXIT_RATIO` (0.65). Use `exitFrames()`.
69
+ 4. **Max 2–3 animated properties per element.** No fade + scale + rotate + blur together.
70
+ 5. **Never opacity alone.** Pair with 8–24px travel or scale >= 0.94. Opacity completes at
71
+ `OPACITY_LEAD` (60%) of the transform.
72
+ 6. **Never scale from below 0.9.** Text scaling from zero is the #1 AI-edit tell.
73
+ 7. **Blur-in capped at 12px**, fully resolved before the transform ends.
74
+ 8. **Groups always stagger** (`STAGGER.tight/normal/dramatic`). Never simultaneous.
75
+ 9. **Distance couples to duration sub-linearly** (`travelFrames()`), never linearly.
76
+ 10. **Anything that moves fast is motion-blurred** (see Smoothness system). No hard edge may jump
77
+ across the frame unblurred.
78
+ 11. **Readable hold:** text stays fully visible >= `readFrames(text)` (min 0.9s, +55ms/word).
79
+ 12. **Continuity:** every transition inherits the outgoing motion (see below).
80
+
81
+ ### Continuity (what makes it feel connected, not cut together)
82
+
83
+ - **Shared elements never re-enter.** If an element exists across two beats, `morph()` its
84
+ position/size/radius between layouts; never exit and re-animate it. (TransitionTest: the
85
+ eyebrow dot stays on screen and grows into the next card's icon tile.)
86
+ - **Velocity handoff.** An exit toward a direction is answered by an entrance continuing that
87
+ direction — `handoff(exitTo)`.
88
+ - **Overlap, never gap.** The next beat starts before the previous one settles —
89
+ `nextBeatAt(prevSettled)`.
90
+ - **One camera over cuts.** Lay scenes out on one canvas and glide a camera between them
91
+ (`src/lib/camera.ts`) instead of hard-cutting between unrelated layouts. Content that should
92
+ stay put during a move (shared elements) lives in screen space, outside the camera.
93
+ - **Nothing is cut off.** Compute exit start times backward from the end so the slowest,
94
+ most-staggered exit completes on or before the last frame.
95
+ - **Sound glues cuts.** Whooshes start `TRANSITION.sfxLeadMs` before a visual change and tail
96
+ `sfxTailMs` after.
97
+
98
+ ---
99
+
100
+ ## Smoothness system (every one of these was a real defect found by motion-check)
101
+
102
+ - **Use the components, not raw transforms.** `<Presence>`, `<MaskReveal>`, `<HighlightMark>`,
103
+ `<RollingNumber>` add velocity motion blur automatically. Anything else that moves wraps its
104
+ moving element in `<VelocityBlur vx vy>` with `perFrame()` velocities.
105
+ - **Velocity motion blur** (`VelocityBlur`): directional blur sized to this frame's movement with
106
+ a 180-degree shutter; fades to zero as the element settles. Near-free to render.
107
+ - **Wipes** use `wipeMask(p, perFrame(...))`: the leading edge softens with speed. Never wipe with
108
+ a hard `clipPath` edge.
109
+ - **Camera moves**: wrap the camera canvas in a full-frame `VelocityBlur` with `bleed={0}` and
110
+ `overflow: hidden`, velocity from `cameraShot` (see TransitionTest).
111
+ - **Zooms / rotations** (not translations): use `<MotionBlur active>` (temporal sampling, centred
112
+ on the frame so toggling it never shifts timing). It costs 10x render time while active —
113
+ enable only during the move. Never use `@remotion/motion-blur`'s `CameraMotionBlur`: it samples
114
+ ahead of the frame, so switching it on/off causes a timing hitch.
115
+ - **Counters roll, never tick.** Use `<RollingNumber>`. A counting number changes glyphs in single
116
+ frames as it slows, which motion-check flags as pops.
117
+ - **Never `will-change`.** It makes each parallel render tab cache text at a different sub-pixel
118
+ position, so settled elements flicker between 4 versions. (Lint enforces this.)
119
+ - **Screen recordings used as b-roll usually scroll in steps** (they judder). Use a still frame with a
120
+ smooth `ScreenView` pan instead, and blur names on the still.
121
+ - **Presence `dur` sets entrance AND exit length.** `dur="instant"` makes exits last 3 frames (a snap) —
122
+ for containers that only fade in, use at least `"base"`.
123
+ - **Final renders are supersampled at `--scale=2`**, then downscaled to 1080x1920. At 1x, Chrome
124
+ snaps text to whole pixels, so the slow tail of every ease-out steps ("move 1px, hold, move 1px")
125
+ and motion-check reports stutters. Run motion-check with the same `--scale`.
126
+ - **Frame rate:** match the footage. If footage is 60fps, build at 60 — per-frame jumps halve.
127
+ All tokens are in ms, so compositions work at either rate.
128
+
129
+ ---
130
+
131
+ ## Design rules
132
+
133
+ **Read `STYLE.md` before any design decision.** It is the house style (type scale, layout, motion,
134
+ sound, approved and rejected beats) and wins over anything below or in a reference. Update it
135
+ whenever the user approves or rejects something.
136
+
137
+ - Everything visual comes from `src/design/tokens.ts`, which is yours to define.
138
+ - **Brand essentials:** your display typeface only, at every weight (headlines/stats 800). One accent: accent
139
+ `your accent` on ink and cream/white. No blue, no purple, no serif. Signature emphasis is the accent
140
+ marker behind the key phrase — use `<HighlightMark>`; don't invent other emphasis styles.
141
+ Headlines are short two-part fragments with the payoff highlighted. Numbers are shown raw and
142
+ bold ("$1.3M", "11x") and roll in. Eyebrows are uppercase pills with a accent-ringed dot.
143
+ Radii are generous, shadows soft, buttons/badges pills.
144
+ - Secondary text uses `COLOR.fgSecondary`; `COLOR.fgMuted` fails contrast on cream — decoration only.
145
+ - **User style decisions (first project):**
146
+ - Captions are **crisp white** (`CAPTION` token, tight + soft dark shadow). Never ink text with a light glow.
147
+ Over the cream background white is invisible, so in card layouts captions go **inside the video
148
+ card's bottom edge** on the card's dark scrim (`StageState.scrim`).
149
+ - Big headline text is **accent with a glow** (`HeroBlock` / `HeroTag`, `heroGlow()`).
150
+ - Pointer is a **solid accent arrow** like the reference (`Cursor`), ~68px.
151
+ - Brand logos are **real full-colour logos** (`LogoIcon`, SVG Logos CC0 via `src/design/logos.json` —
152
+ extract only the icons used). Never approximate a logo that isn't in the set; use a neutral
153
+ icon and ask for the official file (Klaviyo is missing).
154
+ - A corner tag (e.g. "11 MINUTES" pill) is removed when the video returns full screen.
155
+ - **Text behind the subject must stay readable:** the head covers only the lower ~20–30% of the
156
+ headline. Head height changes shot to shot (leaning forward lifts it ~80px) — check a still of
157
+ EVERY hero moment and raise the block (`top`; put labels above as `eyebrow`) where needed.
158
+ - Keep accent highlights away from the accent background glow so they don't disappear into it.
159
+ - Stats use proportional figures, not tabular (tabular makes "11" look gappy in your display typeface).
160
+ - **Web design systems do not map 1:1 to video.** Keep colour, font families/weights, type-scale
161
+ ratios, radii, spacing rhythm, iconography and imagery style — but rescale sizes for a
162
+ 1080-wide frame viewed on a phone (body roughly 40–48px, not 16px). Web UI components mostly
163
+ do not apply.
164
+ - Respect safe zones: `LAYOUT.safeTop` / `LAYOUT.safeBottom` for Reels/Shorts UI.
165
+ - Text contrast >= 4.5:1 against whatever is behind it, including over footage.
166
+ - Draw graphics in code (SVG/CSS) so every part can animate. Use flat images only for things
167
+ that must be real (logos, screenshots, photos). Avoid AI-generated imagery for anything the
168
+ viewer is meant to look at.
169
+ - Icons: SVG drawn in code or an installed icon library, never image files.
170
+
171
+ ---
172
+
173
+ ## Compositing: text behind the subject
174
+
175
+ Layer order, bottom to top:
176
+
177
+ ```
178
+ background plate (designed, or the original footage)
179
+ text / graphics <- 1–3px blur to sit on the background's focal plane
180
+ subject cutout <- same clip, frame-aligned
181
+ light wrap <- blurred background screened onto the subject's inner edge, 15–30%
182
+ grain + grade <- applied over everything so layers share one look
183
+ ```
184
+
185
+ - **The cutout must come from the same clip as its background plate**, frame-aligned. Never
186
+ combine a cutout from one take with footage from another.
187
+ - **Green screen:** key once to a transparent intermediate in `work/` (not per render). Clean up
188
+ with spill suppression, ~0.5px choke, 0.5–1px feather. Temporal smoothing on the alpha
189
+ prevents edge flicker.
190
+ - **Paired clips (the user's standard delivery):** Video 1 = original; Video 2 = the same clip with the
191
+ background replaced by flat green. Use Video 2 **only to build the matte (alpha)** and take the
192
+ subject's colour from Video 1 — this removes green fringe/spill entirely, because the subject pixels
193
+ never touched green. Before building, verify the pair: identical duration, fps, resolution and
194
+ frame count, and that the subject lines up on the same frame (a 1-frame offset makes a halo or ghost).
195
+ Any VFR conform must be applied identically to both. If the background-removal tool can export real
196
+ transparency, that is even better than green.
197
+ - **Supplied cutouts** must have real transparency (ProRes 4444, WebM with alpha, or PNG sequence).
198
+ An MP4 cannot hold transparency — if one arrives, stop and tell the user.
199
+ - Play transparent video with `<OffthreadVideo transparent src={staticFile(...)} />`.
200
+ - Text behind a moving subject gets 0.85–0.95x counter-parallax; static text reads as a sticker.
201
+ - Text should animate in while already partly occluded, not appear fully and then get covered.
202
+ - Pull text colour toward the footage's black/white points; pure white over graded footage looks
203
+ pasted on.
204
+
205
+ ---
206
+
207
+ ## Per-video pipeline
208
+
209
+ 1. **Ingest:** probe every clip (fps, resolution, colour, alpha).
210
+ - **Phone footage is usually variable frame rate (VFR).** Conform it to constant frame rate
211
+ with ffmpeg before use, or it stutters and drifts out of sync in Remotion.
212
+ - Conform everything to one fps. Never mix frame rates.
213
+ 2. **Spine:** word-level timings + music beats -> `work/<video>/spine.json`.
214
+ Cut pauses/filler from word gaps. All animation timing reads from the spine.
215
+ - If the user supplies a script or SRT, it is the **text truth** (spelling, names, brand words):
216
+ force-align that exact text to the audio with WhisperX's aligner. Never use SRT timings
217
+ directly — they are line-level and padded for readability, not word-accurate.
218
+ - With no script, transcribe with WhisperX, then show the transcript for correction before building.
219
+ 3. **Cutouts:** key green screen or validate supplied cutouts (see above).
220
+ 4. **Beat sheet:** from the script and the user's marked punch lines, write a short plan — which
221
+ moment gets which treatment — and confirm with the user before building.
222
+ 5. **Build** in `src/compositions/<Video>.tsx` from components + helpers + tokens only.
223
+ 6. **Verify** (below), then show the user.
224
+ 7. **Sound:** the user adds SFX and music themselves. Deliver a cue list (timestamps of every
225
+ transition and hit, with suggested whoosh start = `TRANSITION.sfxLeadMs` before the change) so
226
+ they can place sounds quickly. Render without music unless asked.
227
+ 8. **Render** at `--scale=2`, downscaled to 1080x1920 unless told otherwise.
228
+
229
+ ---
230
+
231
+ ## Audio standard (audio is half the reel — the user will not accept a bad mix)
232
+
233
+ What went wrong on an early video: the voice itself was bit-for-bit intact (measured against the source),
234
+ but 2.3–3.5s whoosh files and a 2.4s riser kept playing UNDER the speech, which made the voice sound
235
+ wrong. Claude cannot hear, so the mix must be engineered and measured, never guessed.
236
+
237
+ 1. **The voice is never touched.** Render the picture `--muted`. The voice in every deliverable is the
238
+ original recording's audio (stream-copied, or at most one encode), plus only a single gain change.
239
+ No limiter, no dynamic normaliser (ffmpeg `loudnorm` single-pass pumps), no repeated AAC encodes.
240
+ 2. **Sound effects are short and placed in gaps.** Trim every SFX to what the motion needs (whoosh
241
+ 0.4–0.8s, hits 0.3–0.6s) with a 60–120ms fade-out. Nothing longer than ~0.8s may sit under a spoken
242
+ word unless it is at least 24 dB under the voice. Risers go in pauses, not under speech.
243
+ 3. **Duck SFX under the voice**: mix with ffmpeg `sidechaincompress` keyed from the voice (SFX drop
244
+ ~6 dB while a word is spoken), and high-pass whooshes/impacts around 120–150 Hz so they don't muddy it.
245
+ 4. **Measure before delivering** (the window scan in the project folder used `f32le` PCM from ffmpeg):
246
+ voice-vs-mix difference per 0.5s window — any window where SFX energy is within 12 dB of the
247
+ voice while a word is spoken gets fixed. Final: -14 LUFS integrated, true peak <= -1 dBTP, one
248
+ AAC encode at 256k.
249
+ 5. **Always also deliver stems + a cue sheet**: `voice.wav`, `sfx.wav` (the placed, trimmed SFX bus),
250
+ and `SFX-CUES.txt` with numbered, timecoded SFX files — so the user can rebalance in Premiere in
251
+ minutes. Picture-only MP4 alongside.
252
+ 6. The user picks and judges sounds by ear; Claude says plainly that it cannot hear and asks for
253
+ timecoded audio notes on the first draft.
254
+
255
+ ---
256
+
257
+ ## Verification (required before saying anything is done)
258
+
259
+ Claude cannot watch video. Quality is checked with frames and maths, and the user makes the
260
+ final call at playback speed. Be explicit about that when reporting.
261
+
262
+ 1. `npm run check` passes.
263
+ 2. `npm run motion-check -- <Comp> --scale=2` reports **no POP, CUT or STUTTER**. Allow intentional
264
+ cuts with `--allow=<frame>`. For each FAST warning, render that frame and confirm the moving
265
+ edge is blurred. Read the motion timeline for dead air and rhythm.
266
+ 3. Render stills and **look at them**: first frame, each element mid-entrance, peak-speed frames
267
+ (blur visible), fully settled (crisp), mid-exit, and the **last frame** (clean).
268
+ 4. Check: no text collides with the subject's face, safe zones respected, contrast holds,
269
+ no clipped descenders (g, p, y, commas) in masked text.
270
+ 5. Report what was verified and what still needs the user's eyes at full speed.
271
+
272
+ ---
273
+
274
+ ## Remotion gotchas
275
+
276
+ - Never use CSS `transition`/`animation` — drive everything from `useCurrentFrame()`.
277
+ - Never use `Math.random()` — use `random(seed)` from `remotion`.
278
+ - Never use `will-change` (render-tab flicker, see Smoothness system).
279
+ - **`<Presence>` owns its element's `transform` and `opacity`.** Any extra transform (centring with
280
+ `translateX(-50%)`) or opacity (fading content) passed in `style` is silently overwritten — put it
281
+ on an inner wrapper. This bug appeared twice in the first project (off-centre tag, labels that
282
+ never faded).
283
+ - em-based spacing (gaps, padding) resolves against the element's own font-size: set `fontSize`
284
+ on the container, or word gaps collapse.
285
+ - Masked text needs travel > 100% and container padding, or descenders stay visible.
286
+ - Load fonts through `@remotion/fonts` (brand your display typeface is local in `assets/fonts/`) so renders wait for them.
287
+
288
+ ---
289
+
290
+ ## Feedback format from the user
291
+
292
+ Notes arrive as timecode + specific issue ("0:14 title lands 3 frames late, bounce too strong").
293
+ Translate each note into token or timing changes; if a note conflicts with a law, say so and ask.
294
+
295
+ ---
296
+
297
+ ## What is already installed
298
+
299
+ Everything needed to cut, render, handle footage and mix audio arrived with the studio.
300
+ Nothing else to set up, and nothing for the customer to think about.
301
+
302
+ ## The transcriber — installed on first use, not before
303
+
304
+ Word-by-word captions need a transcriber, and it is a large download. It is deliberately
305
+ **not** part of setup, because most people's first video does not need it and it would
306
+ double the time before they see something working.
307
+
308
+ The first time a video needs captions on the exact word, install it then — say plainly that
309
+ it is a one-time download of a couple of GB and will take a few minutes:
310
+
311
+ ```bash
312
+ python --version # needs 3.11 or 3.12 — not the newest, it is unsupported
313
+ pip install whisperx
314
+ ```
315
+
316
+ If Python is missing: **python.org/downloads**, version 3.11 or 3.12, and on Windows tick
317
+ **"Add Python to PATH"** on the first screen — missing that tickbox is the single most
318
+ common failure.
319
+
320
+ **How captions actually work, and why this matters:** transcribe to find out what was *said*,
321
+ then force-align the customer's written *script* to that audio. Never use the raw transcript's
322
+ timings — the script is what goes on screen, and the alignment is what makes each word land
323
+ on the exact frame it is spoken.
324
+
325
+ ## Hardware
326
+
327
+ Rendering is CPU-heavy and transcription is slower the first time while the model loads.
328
+ On a laptop, expect a 60-second video to take a few minutes to render at `--scale=2`.
329
+ If a render is too slow, drop to `--scale=1` while you are still judging the edit, and
330
+ only supersample for the final.
331
+
332
+ ## When you are stuck
333
+
334
+ Every named effect lives in `src/components/` and every one of them is real, working code —
335
+ read it before inventing anything. If something you want is not there, ask for a reference
336
+ clip and a timecode, work out the technique, build it as a component in `src/components/`,
337
+ and add its name to `templates/BRIEF.md` so it can be asked for by name next time.
338
+
339
+ The studio gets updates. When new components, scripts or rules ship, run `editing_os_update`
340
+ and they are written into this project alongside your own work, which is never touched.
341
+
@@ -0,0 +1,165 @@
1
+ <!-- This is YOUR style guide. It starts as craft rules that hold for any brand; as you
2
+ approve and reject things, write them down here so every future video inherits the
3
+ decision instead of re-litigating it. -->
4
+
5
+ # House style
6
+
7
+ The look and feel every edit must have. Built from the user's own decisions on real videos. CLAUDE.md says *how* to build; this file says *what it must look,
8
+ move and sound like*. When a decision here conflicts with a reference, this file wins unless the
9
+ user says otherwise. Add to it whenever the user approves or rejects something.
10
+
11
+ The user has a design background: judge every frame as a designer would. **Clean, calm, intentional —
12
+ never a video that looks stitched together.**
13
+
14
+ ---
15
+
16
+ ## 1. Identity
17
+
18
+ - **Brand:** Your own design system, defined in `src/design/tokens.ts`.
19
+ - **Type:** your display typeface only. Weight 800 for headlines, numbers and hero words; 600–700 for labels and pointers.
20
+ - **Colour:** your accent colour, ink, cream/white. No other accent colours.
21
+ - On footage: accent text with the soft accent glow (`heroGlow()`).
22
+ - On cream: ink text; emphasis with the accent marker (`HighlightMark`).
23
+ - Solid screens: accent background with ink text, or ink background with accent text.
24
+
25
+ ## 2. Type scale (restrained — oversized text was rejected as "not aesthetic")
26
+
27
+ | Role | 16:9 (1920x1080) | 9:16 (1080x1920) | Notes |
28
+ |---|---|---|---|
29
+ | Hero word (one per beat: STOP, FIRST) | 200–320px | 130–220px | Only one hero on screen at a time |
30
+ | Headline (SAME SCRIPT, DIFFERENT PERSON, REINVENT) | ~116px | ~100px | Tracking -0.03em |
31
+ | Big number (counter, stat) | ~170px | ~150px | Tabular digits while counting |
32
+ | Label (VIEWS, COMMENT, THE WHEEL, eyebrows) | ~40px | ~32px | Uppercase, tracking 0.14em, weight 600 |
33
+ | Pointer / list item | ~42px | ~36px | Accent check icon ~40px |
34
+ | Captions | 46px | 42px | Crisp white, bold, soft dark shadow |
35
+
36
+ If a word needs to be bigger than this to feel important, the layout is wrong, not the size.
37
+
38
+ ## 3. Layout
39
+
40
+ - **The speaker stays full screen by default.** No rounded boxes or frames around the talking head
41
+ just to make room ("looks very off"). A card is only allowed as a *transition* (the card swap).
42
+ - Put text in the clear space the shot gives you (sky, trees, empty wall), or **behind the subject**
43
+ when a green-screen twin exists. Check where the head goes on every jump cut.
44
+ - The head may cover at most the lower ~20–30% of a word behind it, and the word must still read.
45
+ - Captions live in one consistent zone per layout, over a dark area. They hide while the same words
46
+ are on screen as a graphic, and never linger into the next scene.
47
+ - Graphics are made of type, numbers, real screenshots and real clips. **No decorative gimmicks**
48
+ (a glowing wheel behind the speaker was rejected as a "weird vibe").
49
+
50
+ ## 4. Motion
51
+
52
+ - The 12 motion laws in CLAUDE.md, always. Smooth, eased, motion-blurred, nothing pops.
53
+ - **Jump cuts: zoom through them** (Stage `cutZoom`). The same settle when footage returns after a
54
+ full-screen graphic. Never a jittery snap.
55
+ - **Hard cuts only as a deliberate burst** (colour flip): a few beats of solid screens synced to the
56
+ phrases and sound, then back to smooth. Everything else morphs from one state to the next.
57
+ - **Cover anything off-camera** (reading from an iPad, looking away) with a graphic or a cut.
58
+ - Counters only go up and start from 0 — no grey placeholder digits.
59
+
60
+ ## 5. Sound (audio is half the reel)
61
+
62
+ - The voice is never processed — see the Audio standard in CLAUDE.md.
63
+ - Sound effects are short (whoosh 0.4–0.8s, hits 0.3–0.6s), faded, and sit in the gaps between words.
64
+ - Every new visual gets its own fitting sound; don't reuse a sound that belonged to a different look.
65
+ - Subtle beats loud: cinematic impacts on text were "way too impactful". Whooshes on big moves must
66
+ still be clearly audible.
67
+ - Always hand over voice + SFX stems and a numbered, timecoded SFX folder with a cue sheet.
68
+
69
+ ## 5b. Colour (the look is shot, then graded)
70
+
71
+ Target look (user's references, Sep 2026): warm golden light, olive-yellow greens, soft contrast,
72
+ creamy skin, shallow depth of field — sunlit / window-lit.
73
+
74
+ - **~80% of this look comes from the shoot**: golden hour or a single side window, the sun or light
75
+ behind/beside the subject, a warm/green background 3+ m away, a fast lens (EF 50mm f/1.8 at f/2–2.8),
76
+ white balance fixed (Daylight 5200K outdoors — never Auto, it removes the gold).
77
+ - Canon 200D: Neutral picture style (sharpness 0–1, contrast -2, saturation -1), 25fps + 1/50 under
78
+ 50Hz lighting (flicker), 30fps + 1/60 outdoors, ISO 100–400, expose skin bright but unclipped.
79
+ - **Automated LUT grading (`scripts/grade.py`) is a starting point only** and only when the footage's
80
+ light already resembles the reference. On mismatched footage (overcast rooftop vs sunlit refs) the
81
+ user judged it "very bad, nowhere near". Never present an auto-grade as a match; judge colour by
82
+ the user's eye, not by Claude's thumbnails. Final grading: DaVinci Resolve / Lumetri, shot by shot,
83
+ reference on split screen.
84
+ - Ask for a 20s test clip at the planned location/time before a graded shoot; check exposure,
85
+ clipping, white balance and flicker numerically.
86
+
87
+ ## 5c. Transitions and caption looks (presets)
88
+
89
+ Named in `src/design/presets.ts`. Ask for them by name in a brief.
90
+
91
+ | Transition | When |
92
+ |---|---|
93
+ | whipPan | Between unrelated shots. Never twice in a row. |
94
+ | push | Moving forward through a list or sequence. |
95
+ | maskWipe | Calm reveal. Pairs with a soft sound. |
96
+ | colourFlip | A burst on a phrase. Two or three, then back to smooth. |
97
+ | matchCut | Only when both shots share a shape or direction. |
98
+ | dip | Changing subject without drama. |
99
+
100
+ | Caption look | When |
101
+ |---|---|
102
+ | clean | Default. Works over almost anything. |
103
+ | block | Busy or bright footage, where white alone gets lost. |
104
+ | highlight | Keeps the eye on the word being said. |
105
+ | pop | Fast edits. Not over heavy motion. |
106
+
107
+ Speed ramps: eased only (`npm run ramp`), never a hard speed switch. Below 0.4x needs 50fps+ footage.
108
+
109
+ ## 5d. Film looks (overlays)
110
+
111
+ Named in `src/design/overlays.ts`, applied with `<FilmOverlay look="film">`. Reference
112
+ composition: `FilmTest`. The test is that the footage looks richer, never that it looks
113
+ filtered — if a viewer can name the effect, it is too strong.
114
+
115
+ | Look | When |
116
+ |---|---|
117
+ | none | Graphics, screen recordings, anything with text as the subject. |
118
+ | clean | Default for talking head. Just enough grain to stop flat areas banding. |
119
+ | film | B-roll and travel. The one to reach for first. |
120
+ | super8 | Memory / nostalgia moments only. Never a whole video. |
121
+ | cinema | A held, wide, quiet shot. Bars plus bloom, nothing that moves. |
122
+
123
+ Three things that were measured, and must not be quietly undone:
124
+
125
+ - Grain composites **normally**, not with `overlay`. On cream and ink there are no midtones,
126
+ and overlay grain measured 0.000 stddev on a flat area — the layer did nothing at all.
127
+ - Grain is paid for in black level (blacks measured 18 → 25 at `medium`). Turn it up per shot
128
+ with the `grain` prop when a nostalgia moment is worth milkier blacks; never raise the default.
129
+ - Halation blooms **highlights only** — the plate is crushed to black before it is blurred and
130
+ screened back, so the darks come through untouched. Without the crush a cream-heavy frame
131
+ blooms end to end and the blacks lift to brown.
132
+
133
+ Grain reseeds every frame. Holding a seed makes the frame byte-identical to the last one, which
134
+ motion-check reports as a STUTTER, so a grainy video would fail its own verification gate.
135
+
136
+ ## 6. Approved beats (the effects library)
137
+
138
+ Names match `templates/BRIEF.md`. "Source" is where the user approved it.
139
+
140
+ | Beat | What it is | Source |
141
+ |---|---|---|
142
+ | Text behind me | Headline behind the head (green-screen twin) | — |
143
+ | Card swap | Speaker shrinks into a card, becomes a creator clip mid-shrink, a row of clips slides in and drifts while the voice continues | approved as "perfect" |
144
+ | Colour flip | Solid accent/ink screens per phrase; a word rolls into the next and the colours swap | — |
145
+ | Counter race | Number races from 0 and lands (1,000,000+), then footage slams back | — |
146
+ | Zoom-through cut | Push-in through every jump cut | — |
147
+ | Gradient CTA | Bottom gradient, small label + big typed word, then ticked pointers after it leaves | — |
148
+ | Browser mockup | Screenshots in a clean browser window, panned and zoomed | — |
149
+ | Pointer click | Solid accent arrow glides and clicks with a ripple | — |
150
+ | Rolling number | Digits roll between values | — |
151
+ | Headline to pill | Big headline shrinks into a corner tag; removed when back to full screen | — |
152
+ | Word-by-word captions | Default | both |
153
+
154
+ ## 7. Rejected (don't do these again)
155
+
156
+ | What | Why | Video |
157
+ |---|---|---|
158
+ | Rounded card around the talking head at the end | "looks very off" | — |
159
+ | Glowing wheel graphic behind the speaker | "weird vibe" | — |
160
+ | Footage behind the colour-flip screens | "loses the impact" | — |
161
+ | Text at 190–280px | "way too big, not aesthetic" | — |
162
+ | Big cinematic impact on the hook word | "way too impactful" | — |
163
+ | Grey placeholder digits in a counter | "not coming out nicely" | — |
164
+ | Long whoosh / riser tails under speech | made the voice sound wrong | — |
165
+ | Black text with a white glow for captions | replaced by crisp white | — |
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "my-editing-studio",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "doctor": "node scripts/doctor.mjs",
8
+ "studio": "remotion studio",
9
+ "still": "remotion still",
10
+ "render": "remotion render",
11
+ "typecheck": "tsc --noEmit",
12
+ "lint:motion": "node scripts/motion-lint.mjs",
13
+ "check": "npm run typecheck && npm run lint:motion",
14
+ "motion-check": "node scripts/motion-check.mjs",
15
+ "new-video": "node scripts/new-video.mjs",
16
+ "prep": "node scripts/prep.mjs",
17
+ "stills": "node scripts/stills.mjs",
18
+ "refs": "node scripts/refs.mjs",
19
+ "refstyle": "node scripts/refstyle.mjs",
20
+ "beats": "node scripts/beats.mjs",
21
+ "sync": "node scripts/sync-audio.mjs",
22
+ "ramp": "node scripts/ramp.mjs",
23
+ "trim": "node scripts/trim.mjs",
24
+ "assemble": "node scripts/assemble.mjs",
25
+ "sound": "node scripts/sounddesign.mjs"
26
+ },
27
+ "dependencies": {
28
+ "ffmpeg-static": "5.3.0",
29
+ "ffprobe-static": "3.1.0",
30
+ "@remotion/cli": "4.0.525",
31
+ "@remotion/fonts": "4.0.525",
32
+ "@remotion/google-fonts": "4.0.525",
33
+ "@remotion/media-utils": "4.0.525",
34
+ "@remotion/motion-blur": "4.0.525",
35
+ "react": "19.3.0",
36
+ "react-dom": "19.3.0",
37
+ "remotion": "4.0.525"
38
+ },
39
+ "devDependencies": {
40
+ "@types/react": "19.3.0",
41
+ "@types/react-dom": "19.3.0",
42
+ "pngjs": "7.0.0",
43
+ "typescript": "5.9.2"
44
+ }
45
+ }