playrig 0.0.0-stage → 0.1.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 (118) hide show
  1. package/AGENTS.md +106 -0
  2. package/LICENSE +37 -0
  3. package/README.md +77 -2
  4. package/bin/playrig.js +4 -0
  5. package/editions/ae/README.md +144 -0
  6. package/editions/ae/client/aeb.js +9 -0
  7. package/editions/ae/examples/hello.jsx +3 -0
  8. package/editions/ae/examples/inspect.jsx +7 -0
  9. package/editions/ae/examples/snap.jsx +15 -0
  10. package/editions/ae/lib/actions.json +159 -0
  11. package/editions/ae/lib/bridge-api.jsxinc +474 -0
  12. package/editions/ae/lib/dev.jsxinc +21 -0
  13. package/editions/ae/lib/icons/README.md +4 -0
  14. package/editions/ae/lib/icons/dot-error.svg +3 -0
  15. package/editions/ae/lib/icons/dot-idle.svg +3 -0
  16. package/editions/ae/lib/icons/dot-running.svg +3 -0
  17. package/editions/ae/lib/icons/make-png.js +25 -0
  18. package/editions/ae/lib/icons/png/dot-error.png +0 -0
  19. package/editions/ae/lib/icons/png/dot-error@2x.png +0 -0
  20. package/editions/ae/lib/icons/png/dot-idle.png +0 -0
  21. package/editions/ae/lib/icons/png/dot-idle@2x.png +0 -0
  22. package/editions/ae/lib/icons/png/dot-running.png +0 -0
  23. package/editions/ae/lib/icons/png/dot-running@2x.png +0 -0
  24. package/editions/ae/lib/icons/png/power-off.png +0 -0
  25. package/editions/ae/lib/icons/png/power-off@2x.png +0 -0
  26. package/editions/ae/lib/icons/png/power-on.png +0 -0
  27. package/editions/ae/lib/icons/png/power-on@2x.png +0 -0
  28. package/editions/ae/lib/icons/png/settings.png +0 -0
  29. package/editions/ae/lib/icons/png/settings@2x.png +0 -0
  30. package/editions/ae/lib/icons/png/status-error.png +0 -0
  31. package/editions/ae/lib/icons/png/status-error@2x.png +0 -0
  32. package/editions/ae/lib/icons/png/status-ok.png +0 -0
  33. package/editions/ae/lib/icons/png/status-ok@2x.png +0 -0
  34. package/editions/ae/lib/icons/png/status-rejected.png +0 -0
  35. package/editions/ae/lib/icons/png/status-rejected@2x.png +0 -0
  36. package/editions/ae/lib/icons/png/status-timeout.png +0 -0
  37. package/editions/ae/lib/icons/png/status-timeout@2x.png +0 -0
  38. package/editions/ae/lib/icons/png/status-unknown.png +0 -0
  39. package/editions/ae/lib/icons/png/status-unknown@2x.png +0 -0
  40. package/editions/ae/lib/icons/power-off.svg +4 -0
  41. package/editions/ae/lib/icons/power-on.svg +4 -0
  42. package/editions/ae/lib/icons/settings.svg +4 -0
  43. package/editions/ae/lib/icons/status-error.svg +4 -0
  44. package/editions/ae/lib/icons/status-ok.svg +4 -0
  45. package/editions/ae/lib/icons/status-rejected.svg +4 -0
  46. package/editions/ae/lib/icons/status-timeout.svg +4 -0
  47. package/editions/ae/lib/icons/status-unknown.svg +5 -0
  48. package/editions/ae/lib/panel-core.jsxinc +249 -0
  49. package/editions/ae/lib/update-key.pem +11 -0
  50. package/editions/ae/lib/updater.jsxinc +148 -0
  51. package/editions/ae/library-starter/INDEX.md +5 -0
  52. package/editions/ae/library-starter/README.md +161 -0
  53. package/editions/ae/library-starter/_common.jsx +187 -0
  54. package/editions/ae/library-starter/categories.json +9 -0
  55. package/editions/ae/library-starter/recipes/.gitkeep +0 -0
  56. package/editions/ae/package.json +7 -0
  57. package/editions/ae/panel/Playrig.jsx +39 -0
  58. package/editions/ae/panel/core.jsx +1026 -0
  59. package/editions/ae/skills/create-ae-recipe/SKILL.md +85 -0
  60. package/editions/ae/skills/create-ae-recipe/references/ae-patterns.md +88 -0
  61. package/editions/ae/skills/create-ae-recipe/references/video-analysis.md +72 -0
  62. package/editions/ae/skills/create-ae-recipe/scripts/media +5 -0
  63. package/editions/ae/skills/create-ae-recipe/scripts/media.py +253 -0
  64. package/editions/ae/skills/create-ae-recipe/scripts/setup.sh +17 -0
  65. package/editions/ae/skills/create-ae-video/SKILL.md +88 -0
  66. package/editions/ae/skills/create-ae-video/references/templates.md +90 -0
  67. package/editions/ae/skills/create-ae-video/scripts/preflight.js +99 -0
  68. package/editions/ae/skills/use-ae-recipes/SKILL.md +99 -0
  69. package/editions/ae/skills/use-ae-recipes/scripts/contact-sheet.jsx +10 -0
  70. package/editions/ae/skills/use-ae-recipes/scripts/edit-text.jsx +12 -0
  71. package/editions/ae/skills/use-ae-recipes/scripts/remove-comp.jsx +18 -0
  72. package/editions/pr/README.md +60 -0
  73. package/editions/pr/SCORE_PROCESS.md +41 -0
  74. package/editions/pr/client/aeb.js +9 -0
  75. package/editions/pr/examples/frame.js +6 -0
  76. package/editions/pr/examples/hello.js +4 -0
  77. package/editions/pr/organize.default.json +245 -0
  78. package/editions/pr/package.json +7 -0
  79. package/editions/pr/plugin/actions.js +79 -0
  80. package/editions/pr/plugin/dev.js +19 -0
  81. package/editions/pr/plugin/icons/README.md +4 -0
  82. package/editions/pr/plugin/icons/file-code.svg +6 -0
  83. package/editions/pr/plugin/icons/folder.svg +3 -0
  84. package/editions/pr/plugin/icons/power-off.svg +4 -0
  85. package/editions/pr/plugin/icons/power-on.svg +4 -0
  86. package/editions/pr/plugin/icons/settings.svg +4 -0
  87. package/editions/pr/plugin/icons/status-error.svg +4 -0
  88. package/editions/pr/plugin/icons/status-ok.svg +4 -0
  89. package/editions/pr/plugin/icons/status-rejected.svg +4 -0
  90. package/editions/pr/plugin/icons/status-timeout.svg +4 -0
  91. package/editions/pr/plugin/icons/status-unknown.svg +5 -0
  92. package/editions/pr/plugin/incremental.js +212 -0
  93. package/editions/pr/plugin/index.html +195 -0
  94. package/editions/pr/plugin/index.js +710 -0
  95. package/editions/pr/plugin/manifest.json +45 -0
  96. package/editions/pr/plugin/organize.js +223 -0
  97. package/editions/pr/skills/score-video/SKILL.md +69 -0
  98. package/editions/pr/skills/score-video/scripts/mix.py +112 -0
  99. package/editions/pr/skills/score-video/scripts/place.js +63 -0
  100. package/lib/ae.js +754 -0
  101. package/lib/common.js +132 -0
  102. package/lib/install.js +148 -0
  103. package/lib/main.js +107 -0
  104. package/lib/payload.js +60 -0
  105. package/lib/pr.js +239 -0
  106. package/lib/update.js +118 -0
  107. package/package.json +35 -4
  108. package/skills/create-video/SKILL.md +72 -0
  109. package/skills/create-video/references/lessons.md +31 -0
  110. package/skills/create-video/references/project-conventions.md +46 -0
  111. package/skills/create-video/scripts/ae/list-project.jsx +8 -0
  112. package/skills/create-video/scripts/ae/move-chips.jsx +14 -0
  113. package/skills/create-video/scripts/ae/organize-project.jsx +25 -0
  114. package/skills/create-video/scripts/ae/quadrant-chips.jsx +143 -0
  115. package/skills/create-video/scripts/ae/render-comps.jsx +16 -0
  116. package/skills/create-video/scripts/premiere/export-sequences.js +22 -0
  117. package/skills/create-video/scripts/premiere/make-sequences.js +24 -0
  118. package/skills/create-video/scripts/premiere/swap-media.js +43 -0
@@ -0,0 +1,90 @@
1
+ # Templates for Create AE Video
2
+
3
+ Copy, fill, and reprint compactly. Keep each to what fits on a screen.
4
+
5
+ ## Question bank (pick ≤ 5; defaults in brackets)
6
+
7
+ | Topic | Question | Default if not answered |
8
+ |---|---|---|
9
+ | Purpose | What is this for and who watches it? (launch promo, explainer, social ad, title/opener, internal) | promo / explainer |
10
+ | Length & format | How long, what shape? (15 s, 30 s; 16:9, 9:16, 1:1) | 15 s, 1920×1080, 24 fps |
11
+ | Content | What should it say? (script, key lines, labels, a CTA) | ask for the 1-3 key lines; draft the rest and confirm |
12
+ | Look | Light & clean, or dark & technical? Brand colours? Font? | light for friendly, dark for technical; Inter |
13
+ | References | Any video/images to match? | none |
14
+ | Must-haves | Anything that must appear / must not appear? | none |
15
+ | Music/VO | Does motion need to hit beats? | no audio sync |
16
+
17
+ Choice-style questions work best with 2-4 options and a recommended default.
18
+
19
+ ## Brief block
20
+
21
+ ```
22
+ BRIEF
23
+ Goal: <one sentence>
24
+ Audience: <who>
25
+ Format: <WxH, fps, length>
26
+ Look: <light|dark>, <palette>, <font>
27
+ Content: <key lines / labels / CTA>
28
+ Reference: <none | path>
29
+ Assumed: <what you decided without asking>
30
+ ```
31
+
32
+ ## Coverage map
33
+
34
+ | Need | Recipe(s) | Status | Note / fallback |
35
+ |---|---|---|---|
36
+ | Light background with depth | `<background recipe id>` | ready | |
37
+ | Headline word-by-word | `<text recipe id>` | ready | |
38
+ | Logo build | `<logo recipe id>` | missing | substitute a fade-in, or create one |
39
+ | ... | | | |
40
+
41
+ Status: `ready` (runs now), `planned` (documented, no script), `missing` (nothing in the library).
42
+
43
+ ## Storyboard
44
+
45
+ | # | Scene | Time | Background | Content | Recipe chain | Out |
46
+ |---|---|---|---|---|---|---|
47
+ | 1 | Hook | 0-3.5 s | radial light | "…" bold "…" + 6 chips | <background> → <headline> → <chips> → <exit> | hard cut |
48
+ | 2 | … | | | | | |
49
+
50
+ Total: <sum> s (target <n> s). Comps will be named `S1 Hook`, `S2 …`, master `MAIN`.
51
+
52
+ ## Status board (reprint at each milestone)
53
+
54
+ ```
55
+ VIDEO: <name> <WxH> @ <fps> target <n>s
56
+ Preflight: ready Library gaps: <n> to create
57
+
58
+ S1 Hook built approved
59
+ S2 Proof building -
60
+ S3 CTA planned -
61
+ MAIN planned -
62
+
63
+ Recipes to create: <id> (in progress), <id> (queued)
64
+ Next: <one line>
65
+ ```
66
+
67
+ ## Polish checklist
68
+
69
+ - Pacing: is every line readable for its full hold (≈ 3 words/s)? Any dead air?
70
+ - Legibility: contrast, size, nothing clipped or off-screen, fonts are the intended ones.
71
+ - Consistency: one typeface, one palette, same motion language (stagger/ease) across scenes.
72
+ - Transitions: each cut is hidden behind motion or softened; no accidental flashes.
73
+ - First and last frame: the opener starts clean; the end card holds long enough.
74
+
75
+ ## Handover
76
+
77
+ ```
78
+ DONE: <name>
79
+ Comps: S1 Hook (3.5 s), S2 … , MAIN (15.0 s) in <project name | unsaved project>
80
+ Rebuild: per-scene command chains (below), re-runnable with different --param values
81
+ Tweak first: <3-5 parameters that change the feel most, with what they do>
82
+ New recipes added to the library: <ids | none>
83
+ Not covered / approximated: <list, incl. planned recipes>
84
+ Reminders: the project is UNSAVED and NOT RENDERED; render via the Render Queue / Media Encoder.
85
+ Next: <2-3 offers: variations, vertical cut, new recipes for planned items>
86
+
87
+ S1 Hook
88
+ playrig ae lib run … --param …
89
+ …
90
+ ```
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // Readiness report for making an After Effects video: toolchain, library, After Effects + Playrig panel, fonts.
4
+ // Usage: node .claude/skills/create-ae-video/scripts/preflight.js [--root <Playrig inbox>] [--wait 15]
5
+ const fs = require('fs');
6
+ const os = require('os');
7
+ const path = require('path');
8
+ const cp = require('child_process');
9
+
10
+ // The Playrig CLI: `playrig` on the PATH (npm install -g playrig), else the one next to this edition in the monorepo.
11
+ const projectDir = path.resolve(__dirname, '..', '..', '..', '..'); // where the skills were installed (.claude/skills/<skill>/scripts/..)
12
+ const repo = projectDir;
13
+ const monoCli = path.resolve(__dirname, '..', '..', '..', '..', 'cli', 'bin', 'playrig.js');
14
+ const onPath = cp.spawnSync(process.platform === 'win32' ? 'where' : 'which', ['playrig'], { encoding: 'utf8' }).status === 0;
15
+ const cli = onPath ? ['playrig'] : fs.existsSync(monoCli) ? [process.execPath, monoCli] : null;
16
+ const runCli = (args, o) => cp.spawnSync(cli[0], [...cli.slice(1), 'ae', ...args], o);
17
+ const args = process.argv.slice(2);
18
+ const opt = (k, d) => { const i = args.indexOf(`--${k}`); return i >= 0 ? args[i + 1] : d; };
19
+ const rootOpt = opt('root', null) ? path.resolve(opt('root')) : null; // otherwise the CLI picks the inbox (PLAYRIG_ROOT, ~/Documents/Playrig/ae)
20
+ const waitSec = Number(opt('wait', 15));
21
+ const root = rootOpt || (cli ? cp.spawnSync(cli[0], [...cli.slice(1), 'where', 'inbox:ae'], { encoding: 'utf8' }).stdout.trim() : null);
22
+
23
+ const rows = [];
24
+ const add = (status, name, detail, fix) => rows.push({ status, name, detail, fix });
25
+ const which = (t) => cp.spawnSync(process.platform === 'win32' ? 'where' : 'which', [t], { encoding: 'utf8' }).status === 0;
26
+
27
+ // 1. Node + Playrig CLI
28
+ const major = Number(process.versions.node.split('.')[0]);
29
+ add(major >= 18 ? 'ok' : 'fail', 'Node.js', `v${process.versions.node}`, 'Install Node 18 or newer');
30
+ add(cli ? 'ok' : 'fail', 'Playrig CLI', cli ? cli.join(' ') : 'playrig not found', 'Install it: npm install -g playrig');
31
+ if (!cli) { rows.forEach((r) => console.log(`${r.status.toUpperCase().padEnd(4)} ${r.name}: ${r.detail}${r.fix ? ` -> ${r.fix}` : ''}`)); process.exit(1); }
32
+
33
+ // 2. Library
34
+ let lib = null;
35
+ const chk = runCli(['lib', 'check'], { encoding: 'utf8' });
36
+ const m = /(\d+) recipes checked: (\d+) errors, (\d+) warnings/.exec(chk.stdout || '');
37
+ const libDir = cp.spawnSync(cli[0], [...cli.slice(1), 'where', 'library'], { encoding: 'utf8' }).stdout.trim();
38
+ const idx = path.join(libDir, 'index.json');
39
+ if (m && fs.existsSync(idx)) {
40
+ const j = JSON.parse(fs.readFileSync(idx, 'utf8'));
41
+ lib = { ready: j.filter((x) => x.status !== 'planned').length, planned: j.filter((x) => x.status === 'planned').length };
42
+ add(Number(m[2]) !== 0 ? 'fail' : lib.ready === 0 ? 'warn' : 'ok', 'Motion library', `${lib.ready} usable recipes, ${lib.planned} planned; lint: ${m[2]} errors, ${m[3]} warnings`, lib.ready === 0 ? 'The library is empty: add recipes with the create-ae-recipe skill, or build scenes with plain job scripts' : 'Run: playrig ae lib check, and fix the errors');
43
+ } else add('warn', 'Motion library', 'no recipe library yet', 'Run: playrig ae lib init (creates a starter you can add recipes to)');
44
+
45
+ // 3. Media tools (needed by create-ae-recipe and for contact sheets)
46
+ const venvPy = path.join(repo, '.claude', 'skills', 'create-ae-recipe', '.venv', 'bin', 'python');
47
+ const haveFf = which('ffmpeg') && which('ffprobe');
48
+ const havePil = fs.existsSync(venvPy);
49
+ add(haveFf && havePil ? 'ok' : 'warn', 'Media tools (ffmpeg + Pillow)', `ffmpeg ${haveFf ? 'yes' : 'NO'}, Pillow venv ${havePil ? 'yes' : 'NO'}`, 'Run: .claude/skills/create-ae-recipe/scripts/setup.sh (needed for contact sheets and video analysis)');
50
+
51
+ // 4. Sibling skills
52
+ const skills = ['create-ae-recipe', 'use-ae-recipes'].filter((s) => !fs.existsSync(path.join(repo, '.claude', 'skills', s, 'SKILL.md')));
53
+ add(skills.length ? 'warn' : 'ok', 'Sibling skills', skills.length ? `missing: ${skills.join(', ')}` : 'create-ae-recipe, use-ae-recipes present', 'Restore .claude/skills/<name>/');
54
+
55
+ // 5. After Effects + bridge round trip
56
+ const id = `preflight-${Date.now().toString(36)}`;
57
+ const jobFile = path.join(os.tmpdir(), `${id}.jsx`);
58
+ fs.writeFileSync(jobFile, `
59
+ var want = ["Inter-Regular", "Inter-Medium", "Inter-Bold", "Inter-ExtraBold"], fonts = {}, i, names = [], comps = 0, it;
60
+ for (i = 0; i < want.length; i++) { try { fonts[want[i]] = app.fonts.getFontsByPostScriptName(want[i]).length > 0; } catch (e) { fonts[want[i]] = null; } }
61
+ for (i = 1; i <= app.project.numItems; i++) { it = app.project.item(i); if (it instanceof CompItem) { comps++; if (names.length < 12) { names.push(it.name); } } }
62
+ var active = app.project.activeItem;
63
+ PLAYRIG.ret({ ae: String(app.version), project: { path: app.project.file ? app.project.file.fsName : null, dirty: app.project.dirty, items: app.project.numItems }, comps: comps, compNames: names, active: active instanceof CompItem ? active.name : null, fonts: fonts });
64
+ `);
65
+ const run = runCli(['run', jobFile, '--id', id, '--json', '--no-undo', '--timeout', '30', '--root', root], { encoding: 'utf8', timeout: waitSec * 1000 });
66
+ let ae = null;
67
+ if (run.status === 0 || (run.stdout && run.stdout.trim().startsWith('{'))) {
68
+ try { ae = JSON.parse(run.stdout); } catch (e) { /* fall through */ }
69
+ }
70
+ if (!ae || ae.status !== 'ok') {
71
+ // Never leave a stale job behind: it would run later when the panel starts.
72
+ for (const f of [`${id}.jsx`, `${id}.json`, `${id}.jsx.tmp`, `${id}.json.tmp`]) fs.rmSync(path.join(root, 'inbox', f), { force: true });
73
+ const why = ae ? `job ${ae.status}: ${ae.error && ae.error.message}` : `no answer within ${waitSec}s`;
74
+ add('fail', 'After Effects + Playrig for After Effects panel', why, 'Open After Effects, then Window > Playrig and press Start (status "Idle"). First time? Install the panel: playrig install ae, and enable Settings > Scripting & Expressions > "Allow Scripts to Write Files and Access Network"');
75
+ } else {
76
+ const v = ae.returnValue;
77
+ add('ok', 'After Effects + Playrig for After Effects panel', `AE ${v.ae}, panel answering`);
78
+ add('ok', 'Open project', `${v.project.path || '(unsaved project)'}${v.project.dirty ? ', unsaved changes' : ''}; ${v.project.items} items, ${v.comps} comps${v.active ? `; active comp: ${v.active}` : ''}`);
79
+ const missing = Object.entries(v.fonts).filter(([, ok]) => ok === false).map(([n]) => n);
80
+ const unknown = Object.entries(v.fonts).filter(([, ok]) => ok === null).map(([n]) => n);
81
+ add(missing.length ? 'warn' : unknown.length ? 'warn' : 'ok', 'Fonts (Inter, the library default)',
82
+ missing.length ? `not installed: ${missing.join(', ')}` : unknown.length ? 'could not check this AE version' : 'Regular, Medium, Bold, ExtraBold installed',
83
+ 'Install Inter (https://rsms.me/inter/) or choose another font per recipe with --param font=<PostScript name>');
84
+ rows.ae = v;
85
+ }
86
+ fs.rmSync(jobFile, { force: true });
87
+
88
+ // Report
89
+ const icon = { ok: 'OK ', warn: 'WARN', fail: 'FAIL' };
90
+ console.log('Create AE Video: preflight\n');
91
+ for (const r of rows) {
92
+ console.log(`${icon[r.status]} ${r.name}: ${r.detail}`);
93
+ if (r.status !== 'ok' && r.fix) console.log(` fix: ${r.fix}`);
94
+ }
95
+ const fails = rows.filter((r) => r.status === 'fail').length;
96
+ const warns = rows.filter((r) => r.status === 'warn').length;
97
+ console.log(`\n${fails ? 'NOT READY' : warns ? 'READY WITH NOTES' : 'READY'}: ${fails} blocking, ${warns} notes`);
98
+ if (rows.ae && rows.ae.compNames.length) console.log(`Comps already in the project: ${rows.ae.compNames.join(', ')}${rows.ae.comps > rows.ae.compNames.length ? ', ...' : ''}`);
99
+ process.exit(fails ? 1 : 0);
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: use-ae-recipes
3
+ description: Build After Effects scenes and videos from the user's motion library (<library>/). Picks recipes from the index, chains them with parameters, previews and tunes them with the user, and assembles scenes into a master comp. Use when the user wants to make, animate, mock up or recreate a video, scene, title, intro, transition or effect in After Effects, says "use the recipes", or asks for a look the library may already cover (kinetic typography, chips, push-in, tilt, grade, fade...).
4
+ ---
5
+
6
+ # use-ae-recipes
7
+
8
+ You turn a brief into finished comps inside the user's **open** After Effects project by running tested recipes from `<library>/` through the Playrig for After Effects: pick, chain, tune, assemble, show.
9
+
10
+ Missing or unsuitable recipe? Switch to the **`create-ae-recipe`** skill rather than improvising a one-off. Don't render final output: that stays with the user (render queue / Media Encoder).
11
+
12
+ ## 0. Preflight
13
+
14
+ - After Effects open, Playrig for After Effects panel started (`Idle`). If a job times out, ask the user to start it.
15
+ - You are working in their real project. **Create new comps only**; don't touch existing comps/layers unless asked. Never save the project. Every run is one Undo step.
16
+ - Commands are `playrig ae ...`. Helper-script paths below (`.claude/skills/...`) are relative to the project folder where the skills were installed (`playrig install skills`). `<library>` is the folder printed by `playrig where library`.
17
+ - For frame review you'll use `.claude/skills/create-ae-recipe/scripts/media` (run its `setup.sh` once if `media check` fails).
18
+
19
+ ## 1. Understand the brief (≤ 3 questions)
20
+
21
+ Need: **goal**, **length/format** (default 1920×1080, 24 fps), **content** (headline text, labels, brand colours, font), **tone** (light/clean vs dark/technical). Ask only what changes the build; assume the rest and say what you assumed. If the user gave a reference video, match what they show against the recipe index.
22
+
23
+ ## 2. Route: find recipes (progressive disclosure)
24
+
25
+ 1. Read **only** `<library>/INDEX.md`. Match by summary and *aliases* (users say "typewriter", "RGB split", "dolly in", "fade from black").
26
+ 2. Open the **one** recipe file you will use next (parameters, "Tweaks that matter", "Gotchas", "Combine with"). Don't read scripts. Don't open recipes you won't use.
27
+ 3. `planned` recipes can't run: tell the user it exists as an idea; offer to build it via `create-ae-recipe`, or skip/substitute.
28
+ 4. Nothing fits → say so, propose the nearest recipe + a tweak, or hand off to `create-ae-recipe`.
29
+
30
+ ## 3. Plan the scenes (show a short table, then go)
31
+
32
+ | Scene | Time | Background | Content | Recipe chain (in order) | Out |
33
+ |---|---|---|---|---|---|
34
+
35
+ Keep it small: **one idea per scene, 1.5-4 s each**. For a single effect request, skip the table and just run the recipe.
36
+
37
+ ## 4. Build each scene
38
+
39
+ One comp per scene, named for the user (`S1 Hook`, `S2 Proof`). The first recipe creates it (pass `width`, `height`, `duration`, `fps` there); later recipes reuse it via the same `comp` value.
40
+
41
+ ```bash
42
+ playrig ae lib show <id> # parameters at a glance (optional)
43
+ playrig ae lib run <id> --param comp="S1 Hook" --param text="Ship faster" ... # prints preview frame paths
44
+ ```
45
+
46
+ - **Order inside a scene:** background → content (type, chips, elements) → camera/rigs → grade → exits/overlays. Rigs and exits act on layers that must already exist; each recipe's Gotchas say so.
47
+ - **Look at every preview** before the next step (read the PNGs). Fix with parameters, not scripts: use the recipe's "Tweaks that matter" table to translate feedback ("snappier" → fewer `moveFrames`, "calmer" → lower `rotY`).
48
+ - Check fonts: if a headline looks like a different typeface, the font isn't installed: ask for another or pass `--param font=<PostScript name>`.
49
+ - Record every command you run (you'll hand them back as the scene's recipe chain). If a recipe runs more than once in a scene (two headlines), give each run its own `--instance` name.
50
+
51
+ ## 5. Assemble
52
+
53
+ Join scenes into a master comp with a structure recipe of your own (hard cuts, or an overlap with a crossfade), or a job script. Example, if your library has one called `comp-sequence-scenes`:
54
+
55
+ ```bash
56
+ playrig ae lib run comp-sequence-scenes --param comp="MAIN" --param scenes="S1 Hook|S2 Proof|S3 CTA" --param overlap=0.4 --no-snap
57
+ ```
58
+
59
+ Then review the whole timeline:
60
+
61
+ ```bash
62
+ playrig ae run .claude/skills/use-ae-recipes/scripts/contact-sheet.jsx --param comp=MAIN --param step=0.25 --no-undo
63
+ .claude/skills/create-ae-recipe/scripts/media sheet "<out folder printed above>" --cols 3 --out /tmp/main.png
64
+ ```
65
+ Read the sheet(s): check pacing, transitions, text legibility, anything cut off.
66
+
67
+ ## 6. Iterate with the user (edit in place: don't rebuild scenes)
68
+
69
+ Show the sheet (path + what to look at), ask for concrete changes, then change **only what was asked**, from lightest to heaviest:
70
+
71
+ 1. **Tiny edit, no rebuild.** New words: `playrig ae run .claude/skills/use-ae-recipes/scripts/edit-text.jsx --param comp="S1 Hook" --param layer=Headline --param text="…" --no-undo` (keeps animators and position). Other small changes (a colour, a position, a timing): a short bridge job on the existing layer. Use `PLAYRIG.inspect` first to see what's there.
72
+ 2. **Change a recipe's settings: replace just its layers, in place.**
73
+ ```bash
74
+ playrig ae lib run <id> --param comp="S1 Hook" --param <changed params…> --replace # --instance <name> if you ran it more than once
75
+ ```
76
+ Every recipe tags the layers it creates (`aeb:<recipe>:<instance>` in the layer comment). `--replace` removes only that recipe's layers and puts the new ones in the **same stacking position**; the rest of the scene is untouched. Pass **all** the params you want (it rebuilds from the defaults plus what you give).
77
+ - Replaced **content** (type, chips, background)? Anything that was parented to a rig recipe won't follow it until you re-run that rig with `--replace` too.
78
+ - Running a recipe a second time **without** `--replace` adds a separate numbered instance on purpose (two headlines, two chip groups). Name them with `--instance`.
79
+ - Recipes marked not replaceable (`playrig ae lib show <id>` says "edit in place: NO", e.g. a rig or an exit effect) change existing layers: follow the note it prints (usually re-run the upstream recipe with `--replace`, then this one; or Edit > Undo).
80
+ 3. **Last resort only:** rebuild a whole scene (`scripts/remove-comp.jsx`, then re-run its chain), e.g. the structure changed so much that patching makes no sense. Say so before doing it; it discards manual changes the user made in that comp.
81
+
82
+ Never rebuild a scene to change a parameter. After an in-place change, re-render only the affected times to check it.
83
+
84
+ ## 7. Hand over
85
+
86
+ Report: comps created (names, lengths), the final command chain per scene (so it can be re-run or tweaked later), the parameters most worth tweaking, assumptions made, fonts used, recipes that were missing, and that the project is **unsaved** and **not rendered**. Offer next steps (tune, add a scene, build a missing recipe with `create-ae-recipe`).
87
+
88
+ ## Rules of thumb
89
+
90
+ - Don't invent parameters: use the names in the recipe's frontmatter (`playrig ae lib show` lists them; unknown ones are rejected).
91
+ - Respect each recipe's notes: a recipe written for light backgrounds must not be used on dark scenes.
92
+ - Don't claim a scene works from "status ok": look at frames at start, mid and end of each move.
93
+ - Keep it incremental: build and show one scene, get a reaction, then continue.
94
+ - If something fails, read the printed error (line + source), fix the parameters or the order; don't retry blindly.
95
+
96
+ ## References
97
+
98
+ - `scripts/contact-sheet.jsx` (review), `scripts/edit-text.jsx` (change words in place), `scripts/remove-comp.jsx` (last-resort rebuild).
99
+ - `<library>/INDEX.md`, `<library>/README.md`: the library and its conventions.
@@ -0,0 +1,10 @@
1
+ // Renders frames of a comp at regular steps (use with: playrig ae run contact-sheet.jsx --param comp=NAME --param start=0 --param end=3 --param step=0.25)
2
+ // Then tile them: .claude/skills/create-ae-recipe/scripts/media sheet <out folder printed by aeb>
3
+ if (typeof P === "undefined" || !P.comp) { throw new Error("Pass --param comp=<comp name> (optional: start, end, step in seconds)"); }
4
+ var c = PLAYRIG.comp(P.comp);
5
+ var s = P.start === undefined ? 0 : Number(P.start);
6
+ var e = P.end === undefined ? c.duration - 1 / c.frameRate : Number(P.end);
7
+ var st = P.step === undefined ? 0.25 : Number(P.step);
8
+ if (!(st > 0)) { throw new Error("step must be > 0"); }
9
+ PLAYRIG.contactSheet(P.comp, s, e, st);
10
+ PLAYRIG.log("queued frames of " + P.comp + " from " + s + "s to " + e + "s every " + st + "s");
@@ -0,0 +1,12 @@
1
+ // Changes the words of an existing text layer IN PLACE (keeps its animators, keys, position, stacking):
2
+ // playrig ae run edit-text.jsx --param comp="S1 Hook" --param layer="Headline" --param text="New words" --no-undo
3
+ // Per-word styling (e.g. a bold keyword) may need re-applying; for that, re-run the recipe with --replace instead.
4
+ if (typeof P === "undefined" || !P.comp || !P.layer || P.text === undefined) { throw new Error("Pass --param comp=… --param layer=… --param text=…"); }
5
+ var c = PLAYRIG.comp(P.comp), i, l = null;
6
+ for (i = 1; i <= c.numLayers; i++) { if (c.layer(i).name === P.layer) { l = c.layer(i); break; } }
7
+ if (!l) { throw new Error("No layer \"" + P.layer + "\" in " + P.comp); }
8
+ if (!(l instanceof TextLayer)) { throw new Error("\"" + P.layer + "\" is not a text layer"); }
9
+ var prop = l.property("ADBE Text Properties").property("ADBE Text Document"), doc = prop.value, old = doc.text;
10
+ doc.text = P.text;
11
+ prop.setValue(doc);
12
+ PLAYRIG.log("\"" + old + "\" -> \"" + P.text + "\" on " + P.layer + " (animators kept: " + l.property("ADBE Text Properties").property("ADBE Text Animators").numProperties + ")");
@@ -0,0 +1,18 @@
1
+ // Removes a comp and the solids it used if nothing else uses them (use with: playrig ae run remove-comp.jsx --param name=NAME).
2
+ // For rebuilding a scene with different settings. Refuses if the comp is used inside another comp.
3
+ if (typeof P === "undefined" || !P.name) { throw new Error("Pass --param name=<comp name>"); }
4
+ var c = PLAYRIG.comp(P.name), i, l, solids = [], n = 0, it;
5
+ if (c.usedIn.length > 0) { throw new Error("\"" + P.name + "\" is used in " + c.usedIn.length + " other comp(s); remove those layers first"); }
6
+ for (i = 1; i <= c.numLayers; i++) {
7
+ l = c.layer(i);
8
+ if (l.source && l.source instanceof FootageItem && l.source.mainSource instanceof SolidSource) { solids.push(l.source); }
9
+ }
10
+ c.remove();
11
+ var folders = [];
12
+ for (i = 0; i < solids.length; i++) {
13
+ try { var pf = solids[i].parentFolder; if (solids[i].usedIn.length === 0) { solids[i].remove(); n++; folders.push(pf); } } catch (e) { }
14
+ }
15
+ for (i = 0; i < folders.length; i++) { // drop folders this left empty (never the project root)
16
+ try { if (folders[i] !== app.project.rootFolder && folders[i].numItems === 0) { folders[i].remove(); } } catch (e2) { }
17
+ }
18
+ PLAYRIG.log("removed comp \"" + P.name + "\" and " + n + " unused solid(s)");
@@ -0,0 +1,60 @@
1
+ # Playrig for Premiere Pro (UXP) — v0.1 spike
2
+
3
+ Same folder protocol as the After Effects edition (`inbox/ running/ done/ out/<id>/result.json`), driven by a UXP panel in Premiere Pro 25.6+.
4
+ Jobs are **modern JavaScript** (async function bodies; `await` works) receiving `PLAYRIG` (`BRIDGE` is an alias), `ppro` (the `premierepro` module), `alert`, `confirm`.
5
+
6
+ ## Install
7
+ `playrig install pr` builds `Playrig (PR).ccx` (a zip of `plugin/` without the dev-only `dev.js`) and opens it with the Creative Cloud installer. For development: Adobe UXP Developer Tool > Add Plugin > select `plugin/manifest.json` > Load.
8
+ Then in Premiere: Window > Extensions > Playrig. Pick a folder (default `~/Documents/Playrig/pr`; an existing `~/Documents/Premiere Bridge` is used until the new one exists), press Start, and run `playrig pr run examples/hello.js`.
9
+
10
+ ## Panel
11
+ Main screen: project name under the title; status dot, a **power icon** (grey = stopped, green = running; click to start/stop) and a **settings gear** at the top right; an **Organize Project** button; and the **Recent jobs** table.
12
+
13
+ **Recent jobs** is per project: it lists the jobs of the project that is open, read from `<project>/EDIT_LOGS/results/`, and reloads when you switch projects (so another project's history never shows up, and a project's history travels with it). Columns: status (icon + word: ok, error, rejected, timeout), **action** (a human-readable label from the defined list in `plugin/actions.js`, e.g. "Duplicate sequence", "Adjust audio levels", "Organize project"), time (DD-MM-YY HH:MM). A job declares its action with a `// @action <id>` header or `aeb --action`; otherwise it is inferred from the script (shown as "inferred"). Hover a row for a tooltip of what the job did (its description line, or the action's summary, plus result, duration, error, frames filed); click it for the full detail window: description, id, start/duration, project, Premiere version, error with line and source, log, exported frames, filed items, return value, the script itself and the file paths. Up to the latest 50 are shown, newest first. Jobs run in an unsaved project appear for this session only.
14
+
15
+ Browse closes the Settings window while the native folder picker is open (a picker cannot be used on top of a modal window) and reopens it afterwards. The Settings window has three sections: **General** (bridge folder as an editable field: type or paste a full path and press Enter, or Browse; Start automatically; Back up), **Organization** (Keep organized, Edit structure) and **About** (version, developer, copyright, Twitter/Instagram links, icon credit). Version comes from `manifest.json` (`0.1.0`, shown as "preview").
16
+
17
+ Edit structure uses `uxp.shell.openPath`, which needs the `launchProcess` permission in `manifest.json` (`schemes: ["https"]` for the About links, `extensions: [".json", ""]` for the structure file and folders). Premiere asks for consent the first time. If opening fails the panel shows the path.
18
+
19
+ ## Project-folder log
20
+ Every job is mirrored into `<project folder>/EDIT_LOGS/` (`JOURNAL.md`, `jobs/`, `results/`, `assets.md/.json`), same idea as the AE bridge. `<project folder>` is the folder containing the open `.prproj` (e.g. `0_PROJECT`). Media paths are listed absolute and relative to that folder, flagged OFFLINE if missing.
21
+
22
+ ## Incremental editing (same idea as the AE bridge's `--replace`)
23
+ Jobs should change what they own, not rebuild. The bridge tags clips it creates with a name suffix `[pb:<family>:<key>]` and `PLAYRIG.sync(seq, family, desired)` moves, swaps, adds or removes **only those clips** to match a desired list; re-running it is a no-op. `PLAYRIG.adopt` brings existing untagged clips under management once. Level helpers: `setLevelDb`, `levelKeys` (timeline seconds in, source time handled for you). `sync` computes the final layout first and refuses (changing nothing) if a clip would overwrite another. See `CLAUDE.md` for the full API and the Premiere rules.
24
+
25
+ `playrig pr scratch start | status | clean --yes` records the project's sequences and items, and later deletes exactly what was added (clones, test imports). Files on disk are never touched.
26
+
27
+ Code: `plugin/incremental.js` (loaded before `index.js`); client commands in `client/aeb.js`.
28
+
29
+ ## Project organization
30
+ Keeps the project panel in a fixed bin scheme (1_MASTER, 2_FOOTAGE/{IMAGES,RAW,RENDERS,GRADED}, 3_SELECTS, 5_SOUND, 6_MUSIC, 7_LAYERS, 99_ARCHIVE). The scheme is **data**: `organize.default.json` is the template; a copy in `<project>/EDIT_LOGS/organize.json` (per project) or `<bridge folder>/organize.json` (personal default) overrides it. Edit bin names, what they mean, and the ordered rules (by item kind, name, file path, extension). The panel has:
31
+ - **Keep the project organized** (checkbox): after every job, files new loose items. Never moves items already in a bin.
32
+ - **Organize Project** (button): the full pass for a project that has drifted. Creates missing bins, files loose items, corrects items in the wrong scheme bin; one undo step; leaves your own bins and the archive alone.
33
+ - **Edit structure** (button): writes the effective structure to the project's `EDIT_LOGS/organize.json` if there is none, and opens it.
34
+
35
+ Code: `plugin/organize.js`.
36
+
37
+ ## Dev-only parts
38
+ `plugin/dev.js` holds developer-only panel features (currently the job script in the job details window). Packaged builds leave that file out; see `DEV_ONLY.md`.
39
+
40
+ ## Updates
41
+ Settings > About > **Check for Updates** reads the public feed (`updates/versions.json` on GitHub; the manifest declares the `network` permission for `raw.githubusercontent.com`), compares versions and links to the release. It also checks once a day (switch off with "Check for updates automatically") and shows a banner when a newer version exists. A UXP panel can't replace its own files: if you installed it from Adobe's marketplace, Creative Cloud updates it; otherwise download the new `.ccx` (or run `playrig update pr`, which verifies and opens it).
42
+
43
+ ## Security
44
+ Anything that can write to `<root>/inbox` can run code in Premiere with full file access (`localFileSystem: fullAccess`). Use a private folder. Off until you press Start.
45
+
46
+ ## Verified on Premiere Pro 26.5.2 (macOS, 2026-10-02)
47
+ Plugin loads via UDT; jobs run (`new AsyncFunction` works); `alert`/log capture; error path reports message, line and source line; `getSequences()`, `sequence.name`, `project.path/name`, `getVideoTrackCount()`, `getEndTime().seconds`, `getVideoTrack(i).getTrackItems(1,false)`, `clip.getName()/getStartTime()`; `Exporter.exportSequenceFrame` writes correct, time-dependent PNGs at the sequence's own frame size; `EDIT_LOGS/` (journal, jobs, results incl. PNGs) is written next to the `.prproj`.
48
+ Note: an empty sequence exports black frames; that is correct, not a failure.
49
+
50
+ ## Still UNVERIFIED
51
+ `PLAYRIG.transact` (one undo step per job), the unsaved-project backfill, rejection of badly named files, timeout behaviour. `PLAYRIG.inspect` is minimal (sequence names only). No asset manifest yet.
52
+
53
+ ## Differences from the AE bridge
54
+ Timeouts are real (Promise.race) but the job is not cancelled. Undo grouping is explicit via `PLAYRIG.transact`, not automatic. Snapshots/inspect use `--snap "<sequence>@t"` / sequence names.
55
+
56
+ ## Skills (ship with the bridge)
57
+ `skills/` holds the Claude Code skills that drive this bridge. Install into a project with `playrig install skills`.
58
+ - **create-video**: end-to-end video workflow across After Effects, Premiere and sound, with the user's project layout, naming, interaction rules and gotchas (`SKILL.md`, `references/project-conventions.md`, `references/lessons.md`, job templates in `scripts/`).
59
+ - **score-video**: music bed + SFX from Epidemic Sound, ducked and placed in Premiere (`scripts/mix.py`, `scripts/place.js`).
60
+ The canonical copies are in `skills/`; edit there and re-run the installer.
@@ -0,0 +1,41 @@
1
+ # Adding a music score to Premiere sequences (draft process, for a future skill)
2
+
3
+ Worked once on "Quadrant Chips Wide/Portrait" (7 s motion pieces). Epidemic Sound MCP + Playrig for Premiere Pro.
4
+
5
+ ## Steps
6
+ 1. **Read the timeline, not just the brief.** Get each sequence's length, frame size and the moments that carry rhythm (here: chip pops every 0.6 s starting 2.6 s). Derive a music brief from them:
7
+ - mood from the visual style (dark, technical, minimal) -> mood/genre tags
8
+ - **BPM from the motion cadence**: beat = 60/BPM, so 0.6 s between events = 100 BPM. Search a +-2 BPM window so hits can land on beats.
9
+ - instrumental only (`vocals: false`) for motion/explainer pieces.
10
+ 2. **Search** `SearchRecordings` (term + `bpm`, `vocals:false`, `first` ~8). Results skew pop; judge by tags (mood, genre) and pick one. Say why in one line. Playing the `lqmp3Url` preview is the only way to judge by ear; ask the user if the taste call is risky.
11
+ 3. **Fit the length with `EditRecording`**: `targetDurationMs` = sequence length, `forceDuration: true`, `downloadAudioFormat: WAV`, `maxResults: 1`, `skipStems: true`. It returns a version with a real ending (no hard cut). Poll `PollEditRecordingJob` until COMPLETED (was instant for 7 s), then `DownloadRecordingEdit` (needs `jobId` + `editId`).
12
+ - Tracks longer than ~5 min can't be edited; use a fade out instead.
13
+ 4. **Download into the project's music folder** (`3_MUSIC` here) with `curl -L`. The signed URL expires, so download immediately. Verify with `ffprobe` (duration) and `ffmpeg -af ebur128` (loudness ~ -14 to -16 LUFS is fine for music-only).
14
+ 5. **Place in Premiere** (job, modern JS):
15
+ - `p.importFiles([path], true, rootItem, false)`, then find the item by name in `root.getItems()`.
16
+ - `ppro.SequenceEditor.getEditor(seq).createOverwriteItemAction(item, TickTime.createWithSeconds(0), 0, 0)` (video track 0, audio track 0 = A1), added inside `p.lockedAccess(() => p.executeTransaction(ca => ca.addAction(...), "label"))` so it is one undo step.
17
+ - Verify: `seq.getAudioTrack(0).getTrackItems(1,false)` -> name, start, end.
18
+ 6. **Report**: track, artist, BPM, why chosen, file path, and that the project is unsaved.
19
+
20
+ ## Gotchas
21
+ - Never save the Premiere project for the user. The bridge backs up the saved `.prproj` before each job.
22
+ - Sequences made from an AE render already have 3 audio tracks; A1 was empty (the mp4 has no audio).
23
+ - Premiere imports go to the project root unless a target bin is passed.
24
+
25
+ ## Open questions for the skill
26
+ - How to audition tracks (we only have preview URLs).
27
+ - Ducking/levels once SFX or voiceover exist.
28
+ - Whether to put the music in a bin and label the track.
29
+
30
+ ## Sound effects + making the music work with them (done on the same piece)
31
+ 7. **Map events to sounds** from the motion plan: grid/axes growing -> one tech whoosh at 0 s; typewriter labels -> a tick bed; each chip pop -> a UI pop at its pop time (2.6, 3.2, 3.8, 4.4, 5.0 s). Skip sounds for minor moves; restraint beats wall-to-wall SFX.
32
+ 8. **Search** `SearchSoundEffects` (`duration` filter keeps results short). Download WAV into the project's sound folder (`2_SOUND/SFX`). Signed URLs expire; copy them exactly, a mistyped signature gives 401.
33
+ 9. **Inspect each SFX before using it.** The "typewriter letter button" file had its loud strike at 0.40 s, not at the start; trimming 0..0.11 s gave an almost silent tick. Look at a 20 ms RMS profile and cut around the real transient.
34
+ 10. **Build the typing bed in ffmpeg** (one tick per 2 characters at the label start times, alternating -8/-10 dB, 2.2 s long) instead of dozens of clips.
35
+ 11. **Level the SFX**: pops about -7 dBFS peak, whoosh about -11, ticks about -22. Bake the gain into `_lv` copies.
36
+ 12. **Music works with SFX = lower and duck, not beat-match.** The edit's beat phase came out different per frequency band (0.26 s vs 0.42 s), so a syncopated groove cannot be reliably locked to the pops. Instead: music -3 dB, then `sidechaincompress` keyed by a mix of all SFX at their timeline times (`threshold=0.02 ratio=3 attack=8 release=300 level_sc=0.25`) -> about -6.5 dB under pops, -1.5 dB under the whoosh/ticks, 0 dB between. Measure the dip (100 ms RMS, ducked vs original) and iterate; first settings dipped -17 dB.
37
+ 13. `sidechaincompress` drops the tail (7.0 -> 6.91 s): add `apad=whole_dur=N,atrim=0:N`.
38
+ 14. **Place in Premiere**: music A1, whoosh A2 @0, typing bed A3 @0, pops alternate A2/A3 (they are 0.66 s long on a 0.6 s cadence, so same-track overwrite would clip the previous tail). Premiere snaps to the frame (2.6 s -> 2.58 s at 24 fps), about 17 ms early, inaudible.
39
+ 15. Overwriting a longer clip with a shorter one leaves a remnant of the old clip; always check `getTrackItems` afterwards. Remove unused intermediates with `root.createRemoveItemAction(item)` inside `executeTransaction`.
40
+
41
+ Still unverified: how it sounds (no audition path), clip-level gain/ducking via the Premiere API (we baked it into the files instead, so levels are not adjustable per clip in Premiere).
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // Deprecated: `aeb` is now `playrig pr <command>`. Kept so old commands and scripts keep working; it prints a notice and forwards.
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const candidates = [path.join(__dirname, '..', '..', 'cli', 'lib', 'main.js'), path.join(__dirname, '..', '..', '..', 'lib', 'main.js')];
7
+ const main = candidates.find((f) => fs.existsSync(f));
8
+ if (!main) { console.error('aeb is deprecated and could not find the playrig CLI. Install it: npm install -g playrig'); process.exit(2); }
9
+ require(main).legacy('pr', process.argv.slice(2));
@@ -0,0 +1,6 @@
1
+ // @action project.inspect
2
+ // Lists the sequences; frames requested with --snap are exported after the job.
3
+ // Usage: playrig pr run examples/frame.js --snap "My Sequence@0,1"
4
+ // Frames requested with --snap are exported after the job ends.
5
+ const seqs = await PLAYRIG.project.getSequences();
6
+ PLAYRIG.log("sequences: " + seqs.map((s) => s.name).join(", "));
@@ -0,0 +1,4 @@
1
+ // @action bridge.test
2
+ // Checks that Playrig can run a job and lists the project's sequences.
3
+ PLAYRIG.log("hi from Premiere");
4
+ PLAYRIG.log(await PLAYRIG.inspect());