reelme 0.3.0 → 0.5.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 (69) hide show
  1. package/README.md +10 -3
  2. package/assets/audio/PROVENANCE.md +18 -11
  3. package/assets/audio/generate-sfx.mjs +128 -0
  4. package/assets/audio/light-steps.mp3 +0 -0
  5. package/assets/audio/lofi-dusk.mp3 +0 -0
  6. package/assets/audio/manifest.json +56 -63
  7. package/assets/audio/neon-pulse.mp3 +0 -0
  8. package/assets/audio/night-drive.mp3 +0 -0
  9. package/assets/audio/sfx/SOURCES.md +16 -0
  10. package/assets/audio/sfx/pop.mp3 +0 -0
  11. package/assets/audio/sfx/rise.mp3 +0 -0
  12. package/assets/audio/sfx/whoosh.mp3 +0 -0
  13. package/assets/audio/sunny-bounce.mp3 +0 -0
  14. package/assets/audio/warm-memories.mp3 +0 -0
  15. package/package.json +8 -2
  16. package/src/cache.mjs +246 -16
  17. package/src/cli.mjs +20 -11
  18. package/src/render.mjs +98 -19
  19. package/template/package.json +6 -6
  20. package/template/pnpm-lock.yaml +177 -122
  21. package/template/pnpm-workspace.yaml +2 -0
  22. package/template/src/Root.tsx +101 -21
  23. package/template/src/brief.json +6 -6
  24. package/template/src/brief.ts +39 -6
  25. package/template/src/cinematic/Atmosphere.tsx +176 -30
  26. package/template/src/cinematic/Camera.tsx +22 -10
  27. package/template/src/cinematic/look.ts +51 -10
  28. package/template/src/cinematic/transitions.tsx +29 -9
  29. package/template/src/components/primitives/Arrow.tsx +8 -5
  30. package/template/src/components/primitives/Bar.tsx +6 -2
  31. package/template/src/components/primitives/Caption.tsx +2 -2
  32. package/template/src/components/primitives/CodeBlock.tsx +21 -7
  33. package/template/src/components/primitives/Icon.tsx +7 -0
  34. package/template/src/components/primitives/KeyPill.tsx +1 -1
  35. package/template/src/components/primitives/Label.tsx +4 -1
  36. package/template/src/components/primitives/Loop.tsx +65 -0
  37. package/template/src/components/primitives/RevealText.tsx +35 -7
  38. package/template/src/components/primitives/Stage.tsx +62 -0
  39. package/template/src/components/primitives/Terminal.tsx +24 -7
  40. package/template/src/components/scenes/BrowserFrame.tsx +13 -6
  41. package/template/src/components/scenes/CTA.tsx +47 -82
  42. package/template/src/components/scenes/Clip.tsx +31 -12
  43. package/template/src/components/scenes/CodeReveal.tsx +12 -13
  44. package/template/src/components/scenes/DataFlow.tsx +47 -19
  45. package/template/src/components/scenes/FeatureList.tsx +74 -77
  46. package/template/src/components/scenes/FileTree.tsx +23 -7
  47. package/template/src/components/scenes/Hook.tsx +4 -3
  48. package/template/src/components/scenes/HotkeyScene.tsx +4 -3
  49. package/template/src/components/scenes/MobileScreen.tsx +225 -118
  50. package/template/src/components/scenes/OSWindowScene.tsx +5 -5
  51. package/template/src/components/scenes/Problem.tsx +1 -1
  52. package/template/src/components/scenes/SplitComparison.tsx +41 -14
  53. package/template/src/components/scenes/StatCallout.tsx +123 -68
  54. package/template/src/components/scenes/TerminalScene.tsx +12 -11
  55. package/template/src/custom-scenes.ts +19 -0
  56. package/template/src/duration.ts +54 -5
  57. package/template/src/index.ts +16 -10
  58. package/template/src/theme.ts +10 -1
  59. package/template/src/timing.ts +49 -0
  60. package/assets/audio/bright-sparks.mp3 +0 -0
  61. package/assets/audio/calm-keys.mp3 +0 -0
  62. package/assets/audio/circuit-pulse.mp3 +0 -0
  63. package/assets/audio/clean-horizon.mp3 +0 -0
  64. package/assets/audio/generate-tracks.mjs +0 -213
  65. package/assets/audio/midnight-protocol.mp3 +0 -0
  66. package/assets/audio/pixel-bounce.mp3 +0 -0
  67. package/assets/audio/steady-launch.mp3 +0 -0
  68. package/assets/audio/sunny-loop.mp3 +0 -0
  69. package/assets/audio/vector-grid.mp3 +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # reelme
2
2
 
3
- Turn your repo into launch videos for social platforms — rendered locally with [Remotion](https://www.remotion.dev), zero cloud, zero API keys.
3
+ Turn your repo into launch videos for social platforms — rendered on your machine with [Remotion](https://www.remotion.dev). No accounts, no API keys, nothing uploaded.
4
4
 
5
5
  `reelme` is the rendering engine behind the [reelme agent skill](https://github.com/RubenGlez/reelme). The skill does the thinking (it reads your repo, interviews you, and writes a `reelme.json` brief at your repo root); this CLI does the work: it scaffolds a Remotion project in a global cache and renders one video variant per publishing platform.
6
6
 
@@ -24,12 +24,13 @@ npx skills add RubenGlez/reelme
24
24
  |---|---|
25
25
  | `npx reelme render` | Renders every platform selected in `reelme.json` to `./reelme-out/` |
26
26
  | `npx reelme studio` | Opens Remotion Studio against the cached project, for previewing and tweaking |
27
- | `npx reelme clean` | Removes the reelme cache (`~/.reelme/cache`) |
27
+ | `npx reelme validate` | Checks `reelme.json` (schema, platforms, scene types and fields) without rendering |
28
+ | `npx reelme clean` | Removes this project's cache (add `--all` to wipe every project's cache in `~/.reelme/cache`) |
28
29
 
29
30
  ## How it works
30
31
 
31
32
  - Your repo keeps only two things: `reelme.json` (the brief — commit it, it's your video's source of truth across releases) and `reelme-out/` (rendered videos; add it to `.gitignore`).
32
- - The heavy Remotion project lives in `~/.reelme/cache/<project-hash>/`, scaffolded on first render and reused afterwards. Re-renders don't reinstall anything; upgrading the CLI rebuilds the scaffold automatically.
33
+ - The heavy Remotion project lives in `~/.reelme/cache/<project-hash>/`, scaffolded on first render and reused afterwards. Re-renders don't reinstall anything; upgrading the CLI (or changing the template's dependencies) rebuilds the scaffold automatically.
33
34
  - Each platform you select (X, LinkedIn, YouTube, TikTok, Instagram Reel/Story/Feed, GitHub README) maps to a preset that resolves dimensions, duration ceilings, and safe areas internally — you never deal with aspect ratios. GitHub README outputs a GIF; everything else is MP4. Briefs with a `teaser` cut also get a short `<platform>-teaser.mp4` per social platform.
34
35
 
35
36
  ## Requirements
@@ -37,6 +38,12 @@ npx skills add RubenGlez/reelme
37
38
  - Node.js >= 18
38
39
  - [pnpm](https://pnpm.io) (used to install render dependencies inside the cache)
39
40
 
41
+ The first render needs network access: it installs the Remotion project into the
42
+ cache (npm registry), downloads a headless Chrome build (hundreds of MB, once),
43
+ and fetches the on-screen fonts from Google Fonts. After the first successful
44
+ render most of this is cached, but font loading still runs per render — nothing
45
+ you author is ever uploaded.
46
+
40
47
  ## License
41
48
 
42
49
  MIT
@@ -1,16 +1,23 @@
1
1
  # ReelMe Bundled Audio Provenance
2
2
 
3
- The bundled tracks in this directory are instrumental loops generated by
4
- `generate-tracks.mjs` for ReelMe. They do not sample third-party recordings,
5
- loops, MIDI files, or stems.
3
+ The bundled music beds in this directory are curated **CC0 (public domain)**
4
+ loops from [Freesound](https://freesound.org). Each `manifest.json` entry lists
5
+ the uploader (`artist`) and the sound's Freesound page (`source`); CC0 requires
6
+ no attribution, but the credits are kept as a courtesy. The files were loudness-
7
+ normalized (`loudnorm I=-16:TP=-1.5`) to sit under the mix and re-encoded as MP3;
8
+ they are otherwise unedited.
6
9
 
7
- The generated tracks are dedicated to the public domain under CC0 1.0. The
8
- manifest source URLs point to the generator definition used to create each
9
- track. Regenerate the MP3 files with:
10
+ | file | Freesound | uploader |
11
+ |---|---|---|
12
+ | `lofi-dusk.mp3` | https://freesound.org/s/852231/ | holizna |
13
+ | `warm-memories.mp3` | https://freesound.org/s/826622/ | xkeril |
14
+ | `neon-pulse.mp3` | https://freesound.org/s/415922/ | newagesoup |
15
+ | `night-drive.mp3` | https://freesound.org/s/211555/ | waveplaySFX |
16
+ | `sunny-bounce.mp3` | https://freesound.org/s/512340/ | mistermender |
17
+ | `light-steps.mp3` | https://freesound.org/s/410520/ | Jamessi |
10
18
 
11
- ```bash
12
- node cli/assets/audio/generate-tracks.mjs
13
- ```
19
+ **Beat sync.** Each bed is an exact whole-beat loop at the `bpm` recorded in
20
+ `manifest.json`, so the CLI can quantize scene cuts to the beat and the loop
21
+ stays locked when Remotion repeats it (`sunny-bounce` is within ~0.1 beat).
14
22
 
15
- Each track is 32 seconds long, encoded as MP3, and kept below 2 MB for package
16
- size.
23
+ Each file is kept below 2 MB for package size.
@@ -0,0 +1,128 @@
1
+ // Procedural SFX generator — FALLBACK ONLY. The shipped cues in sfx/ are
2
+ // curated ElevenLabs sound effects (see sfx/SOURCES.md); this script exists so
3
+ // the cues can be regenerated from nothing if those files are ever removed.
4
+ // It REFUSES to overwrite existing files unless run with --force.
5
+ //
6
+ // whoosh.mp3 — air sweep for fast lateral cuts (whip/wipe/flip)
7
+ // pop.mp3 — soft low knock for settle cuts (zoom/punch/rise/fade)
8
+ // rise.mp3 — swell into the end card (CTA), peak at 0.6s
9
+ //
10
+ // node cli/assets/audio/generate-sfx.mjs --force (requires ffmpeg on PATH)
11
+ import { writeFileSync, rmSync, existsSync } from "node:fs";
12
+ import { spawnSync } from "node:child_process";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ const SAMPLE_RATE = 44100;
17
+ const TWO_PI = Math.PI * 2;
18
+
19
+ // Deterministic noise so regeneration is reproducible.
20
+ function makeNoise() {
21
+ let seed = 0x9e3779b9;
22
+ return () => {
23
+ seed ^= seed << 13;
24
+ seed ^= seed >>> 17;
25
+ seed ^= seed << 5;
26
+ seed >>>= 0;
27
+ return (seed / 0xffffffff) * 2 - 1;
28
+ };
29
+ }
30
+
31
+ // One-pole band-ish filter state per render call.
32
+ function renderWhoosh(t, dur, noise, state) {
33
+ // Noise through a swept low-pass: dark → bright → gone. Reads as air, not tape hiss.
34
+ const p = t / dur;
35
+ const cutoff = 400 + 5200 * Math.sin(Math.PI * p) ** 2;
36
+ const alpha = Math.min(1, cutoff / (SAMPLE_RATE / 2 / Math.PI));
37
+ state.lp += alpha * (noise() - state.lp);
38
+ const body = state.lp - state.lp2;
39
+ state.lp2 += 0.02 * (state.lp - state.lp2); // high-pass against rumble
40
+ const envelope = Math.sin(Math.PI * p) ** 1.6;
41
+ return body * envelope * 2.2;
42
+ }
43
+
44
+ function renderPop(t, dur) {
45
+ // A soft knock: quickly decaying low sine with a faint click transient.
46
+ const knock = Math.sin(TWO_PI * 130 * t * (1 - t * 1.6)) * Math.exp(-t * 42);
47
+ const click = Math.sin(TWO_PI * 1900 * t) * Math.exp(-t * 300) * 0.25;
48
+ return (knock + click) * 0.9;
49
+ }
50
+
51
+ function renderRise(t, dur, noise, state) {
52
+ // Swell: filtered noise + a fifth dyad crescendo that stops just before the
53
+ // peak — the visual landing supplies the actual accent.
54
+ const p = t / dur;
55
+ const cutoff = 250 + 2600 * p * p;
56
+ const alpha = Math.min(1, cutoff / (SAMPLE_RATE / 2 / Math.PI));
57
+ state.lp += alpha * (noise() - state.lp);
58
+ const air = state.lp * 0.7;
59
+ const dyad = (Math.sin(TWO_PI * 220 * t) + Math.sin(TWO_PI * 330 * t) * 0.6) * 0.18;
60
+ const envelope = p * p * (p < 0.92 ? 1 : (1 - p) / 0.08);
61
+ return (air + dyad) * envelope * 1.4;
62
+ }
63
+
64
+ const SFX = [
65
+ { file: "whoosh.mp3", dur: 0.42, render: renderWhoosh },
66
+ { file: "pop.mp3", dur: 0.18, render: renderPop },
67
+ { file: "rise.mp3", dur: 0.85, render: renderRise },
68
+ ];
69
+
70
+ function writeWav(path, sfx) {
71
+ const frames = Math.round(SAMPLE_RATE * sfx.dur);
72
+ const dataSize = frames * 2;
73
+ const buffer = Buffer.alloc(44 + dataSize);
74
+ buffer.write("RIFF", 0);
75
+ buffer.writeUInt32LE(36 + dataSize, 4);
76
+ buffer.write("WAVE", 8);
77
+ buffer.write("fmt ", 12);
78
+ buffer.writeUInt32LE(16, 16);
79
+ buffer.writeUInt16LE(1, 20);
80
+ buffer.writeUInt16LE(1, 22);
81
+ buffer.writeUInt32LE(SAMPLE_RATE, 24);
82
+ buffer.writeUInt32LE(SAMPLE_RATE * 2, 28);
83
+ buffer.writeUInt16LE(2, 32);
84
+ buffer.writeUInt16LE(16, 34);
85
+ buffer.write("data", 36);
86
+ buffer.writeUInt32LE(dataSize, 40);
87
+
88
+ const noise = makeNoise();
89
+ const state = { lp: 0, lp2: 0 };
90
+ for (let i = 0; i < frames; i += 1) {
91
+ const t = i / SAMPLE_RATE;
92
+ const s = Math.max(-0.98, Math.min(0.98, sfx.render(t, sfx.dur, noise, state)));
93
+ buffer.writeInt16LE(Math.round(s * 32767), 44 + i * 2);
94
+ }
95
+ writeFileSync(path, buffer);
96
+ }
97
+
98
+ const outDir = join(dirname(fileURLToPath(import.meta.url)), "sfx");
99
+ spawnSync("mkdir", ["-p", outDir]);
100
+
101
+ const force = process.argv.includes("--force");
102
+ const existing = SFX.filter((s) => existsSync(join(outDir, s.file)));
103
+ if (existing.length > 0 && !force) {
104
+ console.error(
105
+ `refusing to overwrite curated SFX (${existing.map((s) => s.file).join(", ")}). ` +
106
+ `These are hand-picked ElevenLabs cues (sfx/SOURCES.md); rerun with --force to replace them with the procedural fallback.`
107
+ );
108
+ process.exit(1);
109
+ }
110
+
111
+ for (const sfx of SFX) {
112
+ const wavPath = join(outDir, sfx.file.replace(".mp3", ".wav"));
113
+ const mp3Path = join(outDir, sfx.file);
114
+ writeWav(wavPath, sfx);
115
+ const result = spawnSync(
116
+ "ffmpeg",
117
+ ["-y", "-hide_banner", "-loglevel", "error", "-i", wavPath, "-af", "loudnorm=I=-23:TP=-2", "-codec:a", "libmp3lame", "-b:a", "96k", mp3Path],
118
+ { stdio: "inherit" }
119
+ );
120
+ if (result.error || result.status !== 0) {
121
+ rmSync(wavPath, { force: true });
122
+ rmSync(mp3Path, { force: true });
123
+ process.exitCode = result.status || 1;
124
+ break;
125
+ }
126
+ rmSync(wavPath, { force: true });
127
+ console.log(`wrote ${mp3Path}`);
128
+ }
Binary file
Binary file
@@ -1,83 +1,76 @@
1
1
  [
2
2
  {
3
- "file": "calm-keys.mp3",
4
- "title": "Calm Keys",
5
- "artist": "ReelMe procedural audio generator",
6
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#calm-keys",
3
+ "file": "lofi-dusk.mp3",
4
+ "title": "Lofi Dusk",
5
+ "artist": "holizna",
6
+ "source": "https://freesound.org/s/852231/",
7
7
  "license": "CC0",
8
- "tones": ["professional"],
9
- "duration": 32
8
+ "tones": [
9
+ "professional"
10
+ ],
11
+ "duration": 35,
12
+ "bpm": 82
10
13
  },
11
14
  {
12
- "file": "steady-launch.mp3",
13
- "title": "Steady Launch",
14
- "artist": "ReelMe procedural audio generator",
15
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#steady-launch",
15
+ "file": "warm-memories.mp3",
16
+ "title": "Warm Memories",
17
+ "artist": "xkeril",
18
+ "source": "https://freesound.org/s/826622/",
16
19
  "license": "CC0",
17
- "tones": ["professional", "technical"],
18
- "duration": 32
20
+ "tones": [
21
+ "professional"
22
+ ],
23
+ "duration": 20,
24
+ "bpm": 108
19
25
  },
20
26
  {
21
- "file": "clean-horizon.mp3",
22
- "title": "Clean Horizon",
23
- "artist": "ReelMe procedural audio generator",
24
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#clean-horizon",
27
+ "file": "neon-pulse.mp3",
28
+ "title": "Neon Pulse",
29
+ "artist": "newagesoup",
30
+ "source": "https://freesound.org/s/415922/",
25
31
  "license": "CC0",
26
- "tones": ["professional"],
27
- "duration": 32
32
+ "tones": [
33
+ "technical",
34
+ "playful"
35
+ ],
36
+ "duration": 17,
37
+ "bpm": 112
28
38
  },
29
39
  {
30
- "file": "bright-sparks.mp3",
31
- "title": "Bright Sparks",
32
- "artist": "ReelMe procedural audio generator",
33
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#bright-sparks",
40
+ "file": "night-drive.mp3",
41
+ "title": "Night Drive",
42
+ "artist": "waveplaySFX",
43
+ "source": "https://freesound.org/s/211555/",
34
44
  "license": "CC0",
35
- "tones": ["playful"],
36
- "duration": 32
45
+ "tones": [
46
+ "technical"
47
+ ],
48
+ "duration": 30,
49
+ "bpm": 128
37
50
  },
38
51
  {
39
- "file": "pixel-bounce.mp3",
40
- "title": "Pixel Bounce",
41
- "artist": "ReelMe procedural audio generator",
42
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#pixel-bounce",
52
+ "file": "sunny-bounce.mp3",
53
+ "title": "Sunny Bounce",
54
+ "artist": "mistermender",
55
+ "source": "https://freesound.org/s/512340/",
43
56
  "license": "CC0",
44
- "tones": ["playful", "technical"],
45
- "duration": 32
57
+ "tones": [
58
+ "playful"
59
+ ],
60
+ "duration": 58,
61
+ "bpm": 126
46
62
  },
47
63
  {
48
- "file": "sunny-loop.mp3",
49
- "title": "Sunny Loop",
50
- "artist": "ReelMe procedural audio generator",
51
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#sunny-loop",
64
+ "file": "light-steps.mp3",
65
+ "title": "Light Steps",
66
+ "artist": "Jamessi",
67
+ "source": "https://freesound.org/s/410520/",
52
68
  "license": "CC0",
53
- "tones": ["playful"],
54
- "duration": 32
55
- },
56
- {
57
- "file": "circuit-pulse.mp3",
58
- "title": "Circuit Pulse",
59
- "artist": "ReelMe procedural audio generator",
60
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#circuit-pulse",
61
- "license": "CC0",
62
- "tones": ["technical"],
63
- "duration": 32
64
- },
65
- {
66
- "file": "vector-grid.mp3",
67
- "title": "Vector Grid",
68
- "artist": "ReelMe procedural audio generator",
69
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#vector-grid",
70
- "license": "CC0",
71
- "tones": ["technical"],
72
- "duration": 32
73
- },
74
- {
75
- "file": "midnight-protocol.mp3",
76
- "title": "Midnight Protocol",
77
- "artist": "ReelMe procedural audio generator",
78
- "source": "https://github.com/RubenGlez/reelme/blob/main/cli/assets/audio/generate-tracks.mjs#midnight-protocol",
79
- "license": "CC0",
80
- "tones": ["technical", "professional"],
81
- "duration": 32
69
+ "tones": [
70
+ "playful",
71
+ "professional"
72
+ ],
73
+ "duration": 35,
74
+ "bpm": 110
82
75
  }
83
76
  ]
Binary file
Binary file
@@ -0,0 +1,16 @@
1
+ # SFX provenance
2
+
3
+ The three cue files in this directory are curated **CC0 (public domain)** cues
4
+ from [Freesound](https://freesound.org), loudness-normalized (`loudnorm
5
+ I=-23:TP=-2`) to sit under the bundled music beds. CC0 requires no attribution;
6
+ the uploader credits are kept as a courtesy.
7
+
8
+ | file | Freesound | uploader | role |
9
+ |---|---|---|---|
10
+ | `whoosh.mp3` | https://freesound.org/s/60013/ | qubodup | fast lateral cut (soft air whoosh) |
11
+ | `pop.mp3` | https://freesound.org/s/245645/ | unfa | settle cut (clean pop) |
12
+ | `rise.mp3` | https://freesound.org/s/523057/ | magnuswaker | CTA swell (played from 18 frames before the cut) |
13
+
14
+ The peaks land where `template/src/Root.tsx` schedules each cue. If these ever
15
+ need regenerating without Freesound, `node cli/assets/audio/generate-sfx.mjs
16
+ --force` produces synthetic CC0 stand-ins.
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "reelme",
3
- "version": "0.3.0",
4
- "description": "Turn your repo into launch videos for social platforms. Local Remotion rendering, zero cloud.",
3
+ "version": "0.5.0",
4
+ "description": "Turn your repo into launch videos for social platforms. Rendered on your machine with Remotion — no accounts, no uploads.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "reelme": "src/cli.mjs"
8
8
  },
9
+ "scripts": {
10
+ "test": "node test/smoke.mjs"
11
+ },
9
12
  "files": [
10
13
  "src",
11
14
  "assets",
@@ -19,6 +22,9 @@
19
22
  "engines": {
20
23
  "node": ">=18"
21
24
  },
25
+ "optionalDependencies": {
26
+ "gifsicle": "^7.0.1"
27
+ },
22
28
  "keywords": [
23
29
  "launch-video",
24
30
  "remotion",