afterpresets 0.1.0 → 0.3.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 (173) hide show
  1. package/README.md +6 -4
  2. package/native-runtime/CMakeLists.txt +82 -0
  3. package/native-runtime/README.md +40 -0
  4. package/native-runtime/THIRD_PARTY_NOTICES.md +14 -0
  5. package/native-runtime/include/ap_native.h +223 -0
  6. package/native-runtime/src/apply.cpp +199 -0
  7. package/native-runtime/src/crypto.cpp +225 -0
  8. package/native-runtime/src/crypto.hpp +51 -0
  9. package/native-runtime/src/dialog.cpp +62 -0
  10. package/native-runtime/src/dialog.hpp +35 -0
  11. package/native-runtime/src/engine.cpp +909 -0
  12. package/native-runtime/src/engine.hpp +232 -0
  13. package/native-runtime/src/fsutil.cpp +291 -0
  14. package/native-runtime/src/fsutil.hpp +57 -0
  15. package/native-runtime/src/json.cpp +326 -0
  16. package/native-runtime/src/json.hpp +70 -0
  17. package/native-runtime/src/platform.hpp +83 -0
  18. package/native-runtime/src/platform_common.cpp +60 -0
  19. package/native-runtime/src/platform_mac.mm +314 -0
  20. package/native-runtime/src/platform_win.cpp +277 -0
  21. package/native-runtime/src/runtime.cpp +473 -0
  22. package/native-runtime/src/util.cpp +169 -0
  23. package/native-runtime/src/util.hpp +32 -0
  24. package/native-runtime/src/zip.cpp +243 -0
  25. package/native-runtime/src/zip.hpp +58 -0
  26. package/native-runtime/third_party/monocypher/LICENCE.md +167 -0
  27. package/native-runtime/third_party/monocypher/monocypher-ed25519.c +500 -0
  28. package/native-runtime/third_party/monocypher/monocypher-ed25519.h +140 -0
  29. package/native-runtime/third_party/monocypher/monocypher.c +2956 -0
  30. package/native-runtime/third_party/monocypher/monocypher.h +321 -0
  31. package/native-runtime/third_party/tinf/LICENSE +22 -0
  32. package/native-runtime/third_party/tinf/crc32.c +57 -0
  33. package/native-runtime/third_party/tinf/tinf.h +140 -0
  34. package/native-runtime/third_party/tinf/tinflate.c +636 -0
  35. package/native-runtime/vendor-files.json +39 -0
  36. package/package.json +6 -3
  37. package/runtime/docs/cep/host-after-effects.md +57 -0
  38. package/runtime/docs/cep/host-premiere.md +84 -0
  39. package/runtime/docs/{host.md → cep/host.md} +42 -16
  40. package/runtime/docs/{README.md → common/README.md} +10 -8
  41. package/runtime/docs/{assets.md → common/assets.md} +16 -2
  42. package/runtime/docs/{licensing.md → common/licensing.md} +1 -1
  43. package/runtime/docs/{panels.md → common/panels.md} +5 -5
  44. package/runtime/docs/{storage.md → common/storage.md} +5 -5
  45. package/runtime/docs/{updates.md → common/updates.md} +1 -1
  46. package/runtime/docs/native/README.md +44 -0
  47. package/runtime/docs/native/debugging.md +140 -0
  48. package/runtime/docs/native/rules.md +402 -0
  49. package/runtime/docs/native/testing.md +154 -0
  50. package/runtime/package.json +1 -1
  51. package/runtime/scripts/ae-sdk-docs/config.js +102 -0
  52. package/runtime/scripts/ae-sdk-docs/convert.js +430 -0
  53. package/runtime/scripts/ae-sdk-docs/index.js +314 -0
  54. package/runtime/scripts/ae-sdk-docs/source.js +205 -0
  55. package/runtime/scripts/ae-sdk-docs/split.js +170 -0
  56. package/runtime/scripts/ae-sdk-docs.js +72 -0
  57. package/runtime/src/host.js +1 -1
  58. package/runtime/src/updates.js +1 -1
  59. package/src/api/error-messages.js +1 -1
  60. package/src/build/build.js +7 -7
  61. package/src/build/html.js +1 -1
  62. package/src/build/jsx.js +2 -2
  63. package/src/build/runtime.js +3 -3
  64. package/src/check/check.js +3 -3
  65. package/src/check/native.js +14 -0
  66. package/src/check/pricing.js +1 -1
  67. package/src/check/types.js +2 -2
  68. package/src/cli.js +5 -5
  69. package/src/commands/add.js +8 -3
  70. package/src/commands/build.js +8 -2
  71. package/src/commands/check.js +11 -3
  72. package/src/commands/dev.js +9 -2
  73. package/src/commands/docs.js +5 -1
  74. package/src/commands/init.js +15 -3
  75. package/src/commands/push.js +5 -2
  76. package/src/commands/sdk.js +14 -0
  77. package/src/commands/testers.js +1 -1
  78. package/src/commands/upgrade.js +2 -2
  79. package/src/commands/whoami.js +1 -1
  80. package/src/config.js +1 -1
  81. package/src/dev/bridge.js +8 -0
  82. package/src/dev/cep-link.js +1 -1
  83. package/src/dev/debug-file.js +5 -5
  84. package/src/dev/native.js +3 -0
  85. package/src/dev/session.js +4 -3
  86. package/src/mcp/extendscript.js +10 -7
  87. package/src/mcp/host-target.js +1 -0
  88. package/src/mcp/panels.js +1 -1
  89. package/src/mcp/png.js +1 -0
  90. package/src/mcp/server.js +2 -2
  91. package/src/mcp/tools/capture.js +3 -0
  92. package/src/mcp/tools/host.js +1 -1
  93. package/src/mcp/tools/native.js +6 -0
  94. package/src/mcp/tools/panel.js +4 -4
  95. package/src/mcp/tools/project.js +47 -31
  96. package/src/mcp/tools/releases.js +23 -20
  97. package/src/native/build.js +27 -0
  98. package/src/native/cmake/CMakeLists.txt +88 -0
  99. package/src/native/cmake/zig-windows.cmake +19 -0
  100. package/src/native/effect.js +3 -0
  101. package/src/native/install.js +8 -0
  102. package/src/native/installers.js +4 -0
  103. package/src/native/pipl.js +20 -0
  104. package/src/native/restart.js +2 -0
  105. package/src/native/sdk.js +8 -0
  106. package/src/project/add-panel.js +1 -1
  107. package/src/project/docs.js +3 -1
  108. package/src/project/manifest.js +3 -2
  109. package/src/project/project.js +2 -2
  110. package/src/project/template.js +5 -1
  111. package/src/prompt.js +1 -1
  112. package/src/release/installers.js +4 -4
  113. package/src/release/native.js +3 -0
  114. package/src/release/package.js +1 -1
  115. package/src/release/publish.js +1 -1
  116. package/src/system/run-command.js +1 -1
  117. package/src/targets.js +1 -0
  118. package/src/user-config.js +2 -2
  119. package/template/cep/AGENTS.md +104 -0
  120. package/template/{README.md → cep/README.md} +16 -11
  121. package/template/cep/docs/code_guide.md +67 -0
  122. package/template/cep/docs/initial_setup.md +115 -0
  123. package/template/cep/docs/release_checklist.md +24 -0
  124. package/template/{src → cep/src}/features/home/home.html +4 -4
  125. package/template/{src → cep/src}/features/home/home.js +10 -11
  126. package/template/{src → cep/src}/shared/lib/host-theme.js +3 -3
  127. package/template/{src → cep/src}/shared/lib/view.js +1 -1
  128. package/template/{src → cep/src}/shared/ui/tokens.css +1 -1
  129. package/template/hosts/AGENTS.two-apps.md +12 -0
  130. package/template/hosts/aeft/AGENTS.host.md +10 -0
  131. package/template/hosts/aeft/jsx/aeft/home.jsx +16 -0
  132. package/template/hosts/ppro/AGENTS.host.md +15 -0
  133. package/template/hosts/ppro/jsx/ppro/home.jsx +20 -0
  134. package/template/native/.mcp.json +8 -0
  135. package/template/native/AGENTS.md +105 -0
  136. package/template/native/README.md +71 -0
  137. package/template/native/docs/code_guide.md +72 -0
  138. package/template/native/docs/initial_setup.md +179 -0
  139. package/template/native/docs/release_checklist.md +24 -0
  140. package/template/native/effect.json +16 -0
  141. package/template/native/gitignore +8 -0
  142. package/template/native/package.json +14 -0
  143. package/template/native/src/Effect.cpp +224 -0
  144. package/template/native/src/License.cpp +95 -0
  145. package/template/native/src/License.h +29 -0
  146. package/template/native/src/Render.h +68 -0
  147. package/vendor/afterpresets-additions/AfterEffects.d.ts +291 -0
  148. package/vendor/afterpresets-additions/Premiere.d.ts +179 -0
  149. package/vendor/types-for-adobe/Premiere/24.0/index.d.ts +2567 -0
  150. package/vendor/types-for-adobe/README.md +8 -4
  151. package/template/AGENTS.md +0 -168
  152. package/template/docs/initial_setup.md +0 -69
  153. package/template/jsx/home.jsx +0 -17
  154. /package/runtime/docs/{boot.md → common/boot.md} +0 -0
  155. /package/template/{.mcp.json → cep/.mcp.json} +0 -0
  156. /package/template/{gitignore → cep/gitignore} +0 -0
  157. /package/template/{package.json → cep/package.json} +0 -0
  158. /package/template/{src → cep/src}/features/home/home.css +0 -0
  159. /package/template/{src → cep/src}/features/license-screen/license-screen.css +0 -0
  160. /package/template/{src → cep/src}/features/license-screen/license-screen.html +0 -0
  161. /package/template/{src → cep/src}/features/license-screen/license-screen.js +0 -0
  162. /package/template/{src → cep/src}/features/update-screen/update-screen.css +0 -0
  163. /package/template/{src → cep/src}/features/update-screen/update-screen.html +0 -0
  164. /package/template/{src → cep/src}/features/update-screen/update-screen.js +0 -0
  165. /package/template/{src → cep/src}/panels/main/index.html +0 -0
  166. /package/template/{src → cep/src}/panels/main/main.js +0 -0
  167. /package/template/{src → cep/src}/shared/lib/dom.js +0 -0
  168. /package/template/{src → cep/src}/shared/lib/markdown.js +0 -0
  169. /package/template/{src → cep/src}/shared/ui/base.css +0 -0
  170. /package/template/{src → cep/src}/shared/ui/button.css +0 -0
  171. /package/template/{src → cep/src}/shared/ui/card.css +0 -0
  172. /package/template/{src → cep/src}/shared/ui/field.css +0 -0
  173. /package/template/{src → cep/src}/shared/ui/index.css +0 -0
@@ -0,0 +1,179 @@
1
+ # Initial setup
2
+
3
+ Do this once per computer. The user is usually not a programmer and may not
4
+ have a terminal open (for example, they use the Code tab of the Claude
5
+ desktop app). **Run every command yourself**; ask the user only for the
6
+ steps marked **User**, one at a time, in plain words, and wait for "Done".
7
+ Never ask them to type a command, and never use `sudo` yourself. If a
8
+ command fails because your sandbox blocks the internet or writing to
9
+ `~/.afterpresets`, ask the user to allow it to run outside the sandbox; do
10
+ not work around it.
11
+
12
+ Follow the steps for this computer's system (macOS or Windows). A Mac builds
13
+ the macOS version and cross-compiles the Windows version; a Windows PC builds
14
+ the Windows version only.
15
+
16
+ ## 1. Tools
17
+
18
+ - [ ] `node --version` prints 20 or newer. If Node is missing: on Windows run
19
+ `winget install -e --id OpenJS.NodeJS.LTS`; on a Mac, **User** installs
20
+ the "LTS" version from https://nodejs.org (a normal installer). Then check
21
+ again (a new chat may be needed for the agent to see it).
22
+ - [ ] `npm install` has run; `node_modules/afterpresets` exists.
23
+
24
+ **macOS:**
25
+
26
+ - [ ] Homebrew: `brew --version` works (also try `/opt/homebrew/bin/brew`
27
+ and `/usr/local/bin/brew`). If not, **User**: "Open https://brew.sh, copy
28
+ the install line into the Terminal app, press Return, and type your Mac
29
+ password when asked (nothing shows while you type). Press Return again
30
+ when it asks. If Apple's Command Line Tools are missing it downloads them
31
+ too (about 1 GB from Apple, 5-15 minutes); wait until it says
32
+ 'Installation successful'." Then check again (Homebrew prints two lines to
33
+ add it to the PATH; follow them).
34
+ - [ ] Apple's Command Line Tools (Apple's compiler and signing tools; Xcode
35
+ itself is not needed): `xcode-select -p` prints a folder. Homebrew's
36
+ installer normally installed them. If not, run `xcode-select --install`
37
+ and tell the **User**: "A macOS window asks to install the Command Line
38
+ Tools: click **Install** and wait until it finishes (about 5-15 minutes,
39
+ it downloads about 1 GB from Apple)."
40
+ - [ ] Build tools: `brew install cmake ninja zig` (you run it). `cmake
41
+ --version`, `ninja --version` and `zig version` all work afterwards.
42
+
43
+ **Windows** (Visual Studio is not needed):
44
+
45
+ - [ ] Build tools, with winget (you run them; no administrator needed):
46
+ `winget install -e --id Kitware.CMake`, `winget install -e --id
47
+ Ninja-build.Ninja`, `winget install -e --id zig.zig`. `cmake --version`,
48
+ `ninja --version` and `zig version` all work afterwards (a new terminal,
49
+ or a new chat, may be needed to see them).
50
+
51
+ **Both:**
52
+
53
+ - [ ] The account: `whoami` (MCP) or `npx ap whoami`. If it says "not logged
54
+ in", call `login` (MCP) or run `npx ap login` in the background, then tell
55
+ the **User**: "Your browser opened an AfterPresets page; click **Authorize
56
+ CLI**." Wait with `login_status`, or until the command finishes.
57
+
58
+ ## 2. Adobe's After Effects SDK (one time per computer)
59
+
60
+ Adobe does not allow AfterPresets to ship its SDK, and downloading it needs
61
+ the user's Adobe ID, so this step is theirs.
62
+
63
+ - [ ] Run `npx ap sdk`. If it says the SDK is installed, go on.
64
+ - [ ] Otherwise run `npx ap sdk install`. It opens Adobe's download page. Tell
65
+ the **User**: "Sign in with your Adobe ID on the page that opened, and
66
+ download the **After Effects** SDK for **macOS** (or **Windows** on a PC; a
67
+ zip). Tell me when it
68
+ is in your Downloads folder." Never ask for their Adobe password.
69
+ - [ ] Run `npx ap sdk install` again: it finds the zip in Downloads, checks
70
+ it and installs it into `~/.afterpresets/sdk/` (Windows:
71
+ `%USERPROFILE%\.afterpresets\sdk\`). If the zip is elsewhere,
72
+ pass its path: `npx ap sdk install <path to zip>`.
73
+
74
+ ## 3. AfterPresets MCP
75
+
76
+ The `afterpresets` MCP server (`ap mcp`) gives you the CLI (login, check,
77
+ dev, build, push), the docs, and After Effects itself (`host_eval`,
78
+ `host_scratch_project`, `comp_capture`).
79
+
80
+ - **Claude (desktop app Code tab, or Claude Code):** `.mcp.json` declares it.
81
+ If its tools are missing because this project was just created in this
82
+ chat (or in another folder), tell the **User** exactly how to start a new
83
+ chat in this folder, with its full path, and to approve the AfterPresets
84
+ tools when Claude asks:
85
+ - Claude app: "In the **Code** tab, start a new session, choose the
86
+ folder `<full path>`, and approve the AfterPresets tools when asked."
87
+ - Claude Code in a terminal: "Type `/exit`, then run
88
+ `cd "<full path>" && claude`, and approve the AfterPresets tools."
89
+ - Cursor or VS Code: "**File → Open Folder…**, choose `<full path>`,
90
+ open a new chat, and enable the afterpresets MCP server if asked."
91
+ Give them the message to paste into the new chat: "Continue the
92
+ AfterPresets setup: follow docs/initial_setup.md step by step until you
93
+ have shown me the starter effect rendering in After Effects, then ask me
94
+ what to build." Stop there; the
95
+ new chat continues from this file.
96
+ - **Codex CLI** (run in this folder):
97
+
98
+ ```sh
99
+ codex mcp add afterpresets -- node node_modules/afterpresets/bin/ap.js mcp
100
+ ```
101
+
102
+ Then tell the **User**: "Quit Codex and start it again in this folder:
103
+ `cd "<full path>" && codex`."
104
+
105
+ - **Other agents:** add a stdio server that runs
106
+ `node node_modules/afterpresets/bin/ap.js mcp` in this folder.
107
+
108
+ - [ ] The `afterpresets` tools are visible to you.
109
+
110
+ ## 4. First build
111
+
112
+ - [ ] Call `dev_start` (or `npx ap dev` if the user runs it). It builds the
113
+ effect, installs it into After Effects' dev plugin folder
114
+ (macOS: `/Library/Application Support/Adobe/Common/Plug-ins/7.0/MediaCore/AfterPresets Dev/`;
115
+ Windows: `C:\Program Files\Adobe\Common\Plug-ins\7.0\MediaCore\AfterPresets Dev\`),
116
+ installs the AfterPresets dev bridge (how the tools reach After Effects),
117
+ and converts Adobe's SDK guide into `docs/afterpresets/sdk/`. Then
118
+ `dev_status` until the build is done.
119
+ - [ ] If it says the dev plugin folder needs an administrator, that is a
120
+ **User** step, needed once. **macOS:** it gives one Terminal line (`sudo mkdir -p …
121
+ && sudo chown …`). Tell the **User**: "Open the Terminal app, paste this
122
+ line, press Return and type your Mac password (nothing shows while you
123
+ type)." and give them the exact line it printed. (When `ap dev` runs in an
124
+ interactive terminal it may show the macOS administrator window instead:
125
+ then the **User** just enters their password there.) Never run `sudo`
126
+ yourself. **Windows:** it gives one PowerShell line (`New-Item … ;
127
+ icacls …`). Tell the **User**: "Right-click **Start**, choose **Terminal
128
+ (Admin)** or **Windows PowerShell (Admin)**, click **Yes**, paste this
129
+ line, press Enter, and tell me Done." and give them the exact line it
130
+ printed. Never elevate yourself. Then run `dev_start` / `dev_status`
131
+ again; later builds install without an administrator.
132
+ - [ ] If it reports compiler errors on the untouched starter, the SDK or a
133
+ build tool is wrong: re-check steps 1 and 2 and read the hint it prints.
134
+ - [ ] `docs/afterpresets/sdk/README.md` exists. If it says the guide could
135
+ not be downloaded, check the internet connection and run `npx ap docs`.
136
+
137
+ ## 5. Restart After Effects once
138
+
139
+ - [ ] **User:** "Save your work in After Effects, then quit and reopen it."
140
+ (Needed now because After Effects loads plugins and the dev bridge only
141
+ when it starts. Later, on a Mac, `ap dev` restarts it for you when that
142
+ is safe, or tells you to ask; on Windows always ask the user.)
143
+ - [ ] `host_bridge` (no action) says the bridge is loaded in After Effects.
144
+
145
+ ## 6. Prove the loop
146
+
147
+ - [ ] A harmless host call works: `host_eval` with `app.version`.
148
+ - [ ] The starter effect is loaded: in `host_eval`, look for the
149
+ `matchName` from `effect.json` in `app.effects` (see
150
+ `docs/afterpresets/debugging.md`).
151
+ - [ ] `host_scratch_project`, then apply the starter effect to a solid in a
152
+ test comp with `host_eval` and capture two frames with `comp_capture`
153
+ (`docs/afterpresets/testing.md`). Look at them: the effect is visible.
154
+ - [ ] Show the **User** the frames: "This is the starter effect, running in
155
+ After Effects. Next, tell me what yours should do."
156
+
157
+ ## 7. Ask what to build
158
+
159
+ Only now, with the starter effect rendering, ask what the effect should do.
160
+ Do not start building from a vague idea.
161
+
162
+ - [ ] Ask the **User** to describe the look in detail, in their own words.
163
+ Invite references: images or video frames of the look they want, another
164
+ effect they like, or a frame from their own project. They can drag images
165
+ into the chat.
166
+ - [ ] Ask short questions about anything unclear, a few at a time (for
167
+ example: which controls they want, what each one changes, how it should
168
+ animate, whether it works on the layer's edges or its whole image).
169
+ - [ ] Repeat back a short plan in plain words (the look, the controls and
170
+ their ranges) and wait for their OK.
171
+ - [ ] Write the Overview in `AGENTS.md` from the plan, following the
172
+ instructions there. Keep it current from now on.
173
+ - [ ] Turn the starter into the product: its name, category, controls and
174
+ render code (`docs/code_guide.md` lists what to keep). Build it in small
175
+ steps, rendering and showing frames after each one.
176
+
177
+ Repeat after moving the project, installing a new After Effects version, or
178
+ switching agents (skip step 7 when the Overview in `AGENTS.md` is already
179
+ written).
@@ -0,0 +1,24 @@
1
+ # Before the first production release
2
+
3
+ Do this once, before the first `push` to `production`. Tell the user what you
4
+ are doing; show them the frames and wait for their feedback.
5
+
6
+ - [ ] **Licensing matches the price.** A paid product keeps
7
+ `"license": true` in `effect.json` (`ap check` and `ap push` refuse
8
+ otherwise). A free one may remove it. See `docs/code_guide.md`.
9
+ - [ ] **The locked state works.** Turn enforcement on as described in
10
+ "Testing the locked (signed-out) state" in `docs/afterpresets/testing.md`,
11
+ restart After Effects, and render frames signed out: the effect must still
12
+ render (with the watermark), never crash or hang. Render frames signed in
13
+ too. Then turn enforcement off again.
14
+ - [ ] **Show the user** the locked and unlocked frames and ask for feedback.
15
+ - [ ] **The match name and control ids are final.** After this release they
16
+ can never change (see `docs/code_guide.md`). Confirm the effect's name and
17
+ controls with the user.
18
+ - [ ] **A test release works.** `push` to `test` and let the user try it.
19
+ Built on a Mac, the release also has the Windows version: tell the user to
20
+ test it on a Windows PC before selling it there.
21
+ - [ ] `check` passes with no errors.
22
+
23
+ Then `push` to `production`: show the user the summary it returns, and call
24
+ it again with its `confirmToken` only after they approve.
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "__EFFECT_NAME__",
3
+ "matchName": "__MATCH_NAME__",
4
+ "category": "AfterPresets",
5
+ "description": "Tints the layer toward a colour.",
6
+ "license": true,
7
+ "updates": "background",
8
+ "params": [
9
+ { "key": "amount", "id": 1, "type": "slider", "name": "Amount", "min": 0, "max": 100, "default": 50, "precision": 1, "display": "percent" },
10
+ { "key": "color", "id": 2, "type": "color", "name": "Color", "default": "#FF4000" },
11
+ { "key": "mode", "id": 3, "type": "popup", "name": "Mode", "choices": ["Tint", "Screen", "Multiply"], "default": "Tint" },
12
+ { "key": "gradient", "id": 4, "type": "checkbox", "name": "Gradient", "label": "Fade across layer", "default": false },
13
+ { "key": "center", "id": 5, "type": "point", "name": "Gradient Center", "default": [50, 50] },
14
+ { "key": "angle", "id": 6, "type": "angle", "name": "Gradient Angle", "default": 90 }
15
+ ]
16
+ }
@@ -0,0 +1,8 @@
1
+ # Build output (ap build / ap dev)
2
+ /build/
3
+ /dist/
4
+
5
+ # Never commit Adobe's SDK (it lives in ~/.afterpresets/sdk/)
6
+ AfterEffectsSDK*/
7
+
8
+ .DS_Store
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "__SLUG__",
3
+ "private": true,
4
+ "scripts": {
5
+ "dev": "ap dev",
6
+ "check": "ap check",
7
+ "build": "ap build",
8
+ "push": "ap push",
9
+ "docs": "ap docs"
10
+ },
11
+ "devDependencies": {
12
+ "afterpresets": "^__CLI_VERSION__"
13
+ }
14
+ }
@@ -0,0 +1,224 @@
1
+ // The starter effect: tints the layer toward a colour (Tint, Screen or
2
+ // Multiply), optionally fading across the layer along a gradient.
3
+ //
4
+ // Plain After Effects SDK code. Names, flags, version and parameters come from
5
+ // effect.json through the generated ap_effect_config.h / ap_effect_params.h:
6
+ // change effect.json, not the numbers here.
7
+ //
8
+ // 8/16/32 bpc DEEP_COLOR_AWARE + FLOAT_COLOR_AWARE, one template (Render.h)
9
+ // Smart Render PF_Cmd_SMART_PRE_RENDER / PF_Cmd_SMART_RENDER
10
+ // Multi-frame render no mutable globals; everything a render needs is on the stack
11
+ // AfterPresets status button + Options dialog (License.cpp, ap_native)
12
+ #include "AEConfig.h"
13
+ #include "entry.h"
14
+ #include "AE_Effect.h"
15
+ #include "AE_EffectVers.h"
16
+ #include "AE_EffectCB.h"
17
+ #include "AE_EffectCBSuites.h"
18
+ #include "AE_EffectSuites.h"
19
+ #include "AE_Macros.h"
20
+ #include "Param_Utils.h"
21
+
22
+ #include <cmath>
23
+ #include <cstdio>
24
+ #include <cstring>
25
+ #include <new>
26
+
27
+ #include "ap_effect_params.h" // generated from effect.json
28
+ #include "License.h"
29
+ #include "Render.h"
30
+
31
+ namespace {
32
+
33
+ // Decided once per frame in PreRender and handed to SmartRender.
34
+ struct PreRenderData {
35
+ A_long left, top; // where the output buffer sits in layer space (downsampled pixels)
36
+ A_long unlocked; // license state for this frame (also mixed into the cache key)
37
+ };
38
+
39
+ void delete_pre_render_data(void *data) { delete static_cast<PreRenderData *>(data); }
40
+
41
+ PF_Err about(PF_OutData *out_data) {
42
+ std::snprintf(out_data->return_msg, sizeof out_data->return_msg, "%s %s\r%s", AP_EFFECT_NAME, AP_VERSION,
43
+ AP_DESCRIPTION);
44
+ return PF_Err_NONE;
45
+ }
46
+
47
+ PF_Err global_setup(PF_OutData *out_data) {
48
+ out_data->my_version = AP_EFFECT_VERSION; // equals the PiPL's eVER (both from afterpresets.json)
49
+ out_data->out_flags = AP_OUT_FLAGS; // equals the PiPL's eGLO (both from effect.json)
50
+ out_data->out_flags2 = AP_OUT_FLAGS2; // equals the PiPL's eGL2
51
+ license::start();
52
+ return PF_Err_NONE;
53
+ }
54
+
55
+ PF_Err params_setup(PF_InData *in_data, PF_OutData *out_data) {
56
+ char status[32];
57
+ license::status_name(status, sizeof status);
58
+ return ap_add_params(in_data, out_data, status, license::button_label());
59
+ }
60
+
61
+ // The status button's name shows the license / update state. Names are the
62
+ // only text an effect can change in the Effect Controls without custom UI.
63
+ PF_Err update_params_ui(PF_InData *in_data, PF_ParamDef *params[]) {
64
+ PF_ParamDef def = *params[AP_PARAM_LICENSE];
65
+ char status[32];
66
+ license::status_name(status, sizeof status);
67
+ if (std::strncmp(def.PF_DEF_NAME, status, sizeof def.PF_DEF_NAME) == 0) return PF_Err_NONE;
68
+ std::snprintf(def.PF_DEF_NAME, sizeof def.PF_DEF_NAME, "%s", status);
69
+ const PF_ParamUtilsSuite3 *pu = nullptr;
70
+ PF_Err err = PF_Err_NONE;
71
+ if (in_data->pica_basicP->AcquireSuite(kPFParamUtilsSuite, kPFParamUtilsSuiteVersion3, (const void **)&pu) == 0 && pu) {
72
+ err = pu->PF_UpdateParamUI(in_data->effect_ref, AP_PARAM_LICENSE, &def);
73
+ in_data->pica_basicP->ReleaseSuite(kPFParamUtilsSuite, kPFParamUtilsSuiteVersion3);
74
+ }
75
+ return err;
76
+ }
77
+
78
+ // Effect > Options or the status button: the AfterPresets dialog (sign in,
79
+ // buy, sign out, update notes). Re-render: signing in or out changes the frame.
80
+ PF_Err show_options(PF_OutData *out_data) {
81
+ license::options_dialog();
82
+ out_data->out_flags |= PF_OutFlag_FORCE_RERENDER;
83
+ return PF_Err_NONE;
84
+ }
85
+
86
+ PF_Err user_changed_param(PF_OutData *out_data, const PF_UserChangedParamExtra *extra) {
87
+ if (extra->param_index == AP_PARAM_LICENSE) return show_options(out_data);
88
+ return PF_Err_NONE;
89
+ }
90
+
91
+ PF_Err pre_render(PF_InData *in_data, PF_PreRenderExtra *extra) {
92
+ PF_RenderRequest req = extra->input->output_request;
93
+ PF_CheckoutResult in_result;
94
+ PF_Err err = extra->cb->checkout_layer(in_data->effect_ref, AP_PARAM_INPUT, AP_PARAM_INPUT, &req,
95
+ in_data->current_time, in_data->time_step, in_data->time_scale, &in_result);
96
+ if (err) return err;
97
+ // Pixel-independent: the output covers exactly what the input provides.
98
+ extra->output->result_rect = in_result.result_rect;
99
+ extra->output->max_result_rect = in_result.max_result_rect;
100
+ // The license state is part of the frame: mix it into After Effects' cache key
101
+ // (with PF_OutFlag2_I_MIX_GUID_DEPENDENCIES) so a frame rendered locked is
102
+ // never shown after sign-in, and the reverse.
103
+ A_long unlocked = license::unlocked() ? 1 : 0;
104
+ err = extra->cb->GuidMixInPtr(in_data->effect_ref, sizeof unlocked, &unlocked);
105
+ if (err) return err;
106
+ PreRenderData *data = new (std::nothrow) PreRenderData{in_result.result_rect.left, in_result.result_rect.top, unlocked};
107
+ if (!data) return PF_Err_OUT_OF_MEMORY;
108
+ extra->output->pre_render_data = data;
109
+ extra->output->delete_pre_render_data_func = delete_pre_render_data;
110
+ return PF_Err_NONE;
111
+ }
112
+
113
+ PF_Err smart_render(PF_InData *in_data, PF_SmartRenderExtra *extra) {
114
+ PF_Err err = PF_Err_NONE;
115
+ PF_EffectWorld *input = nullptr, *output = nullptr;
116
+ err = extra->cb->checkout_layer_pixels(in_data->effect_ref, AP_PARAM_INPUT, &input);
117
+ if (!err) err = extra->cb->checkout_output(in_data->effect_ref, &output);
118
+
119
+ // Check out every parameter at the current time (checked back in below).
120
+ const int ids[] = {AP_PARAM_AMOUNT, AP_PARAM_COLOR, AP_PARAM_MODE, AP_PARAM_GRADIENT, AP_PARAM_CENTER, AP_PARAM_ANGLE};
121
+ const int n = (int)(sizeof ids / sizeof ids[0]);
122
+ PF_ParamDef p[sizeof ids / sizeof ids[0]];
123
+ bool checked[sizeof ids / sizeof ids[0]] = {};
124
+ for (int i = 0; i < n && !err; ++i) {
125
+ AEFX_CLR_STRUCT(p[i]);
126
+ err = PF_CHECKOUT_PARAM(in_data, ids[i], in_data->current_time, in_data->time_step, in_data->time_scale, &p[i]);
127
+ checked[i] = !err;
128
+ }
129
+
130
+ if (!err && input && output) {
131
+ const PreRenderData *pre = static_cast<const PreRenderData *>(extra->input->pre_render_data);
132
+ TintParams tp;
133
+ tp.amount = (float)(p[0].u.fs_d.value / 100.0);
134
+ tp.r = p[1].u.cd.value.red / 255.f;
135
+ tp.g = p[1].u.cd.value.green / 255.f;
136
+ tp.b = p[1].u.cd.value.blue / 255.f;
137
+ tp.mode = p[2].u.pd.value;
138
+ tp.gradient = p[3].u.bd.value != 0;
139
+ // Point values arrive in layer pixels at the current resolution (16.16
140
+ // fixed; After Effects already applied the downsample factor; verified at
141
+ // half resolution). The buffers start at the pre-render rectangle's corner.
142
+ tp.cx = (float)p[4].u.td.x_value / 65536.f - (pre ? pre->left : 0);
143
+ tp.cy = (float)p[4].u.td.y_value / 65536.f - (pre ? pre->top : 0);
144
+ const float rad = (float)p[5].u.ad.value / 65536.f * 3.14159265f / 180.f; // 0 degrees points up
145
+ tp.dirx = std::sin(rad);
146
+ tp.diry = -std::cos(rad);
147
+ tp.span = (float)(input->width > input->height ? input->width : input->height);
148
+ if (tp.span < 1.f) tp.span = 1.f;
149
+ tp.watermark = pre ? !pre->unlocked : !license::unlocked();
150
+
151
+ PF_PixelFormat fmt = PF_PixelFormat_ARGB32;
152
+ const PF_WorldSuite2 *ws = nullptr;
153
+ if (in_data->pica_basicP->AcquireSuite(kPFWorldSuite, kPFWorldSuiteVersion2, (const void **)&ws) == 0 && ws) {
154
+ err = ws->PF_GetPixelFormat(output, &fmt);
155
+ in_data->pica_basicP->ReleaseSuite(kPFWorldSuite, kPFWorldSuiteVersion2);
156
+ }
157
+ const int w = output->width < input->width ? output->width : input->width;
158
+ const int h = output->height < input->height ? output->height : input->height;
159
+ const char *src = (const char *)input->data;
160
+ char *dst = (char *)output->data;
161
+ if (!err) {
162
+ switch (fmt) {
163
+ case PF_PixelFormat_ARGB128:
164
+ tint_rows<PF_PixelFloat, 1, true>(src, input->rowbytes, dst, output->rowbytes, w, h, tp);
165
+ break;
166
+ case PF_PixelFormat_ARGB64:
167
+ tint_rows<PF_Pixel16, PF_MAX_CHAN16, false>(src, input->rowbytes, dst, output->rowbytes, w, h, tp);
168
+ break;
169
+ case PF_PixelFormat_ARGB32:
170
+ tint_rows<PF_Pixel8, PF_MAX_CHAN8, false>(src, input->rowbytes, dst, output->rowbytes, w, h, tp);
171
+ break;
172
+ default:
173
+ err = PF_Err_BAD_CALLBACK_PARAM;
174
+ }
175
+ }
176
+ }
177
+
178
+ for (int i = 0; i < n; ++i) {
179
+ if (checked[i]) {
180
+ PF_Err e2 = PF_CHECKIN_PARAM(in_data, &p[i]);
181
+ if (!err) err = e2;
182
+ }
183
+ }
184
+ if (input) {
185
+ PF_Err e2 = extra->cb->checkin_layer_pixels(in_data->effect_ref, AP_PARAM_INPUT);
186
+ if (!err) err = e2;
187
+ }
188
+ return err;
189
+ }
190
+
191
+ } // namespace
192
+
193
+ extern "C" DllExport PF_Err PluginDataEntryFunction2(PF_PluginDataPtr inPtr, PF_PluginDataCB2 inPluginDataCallBackPtr,
194
+ SPBasicSuite *, const char *, const char *) {
195
+ PF_Err result = PF_Err_INVALID_CALLBACK;
196
+ result = PF_REGISTER_EFFECT_EXT2(inPtr, inPluginDataCallBackPtr, AP_EFFECT_NAME, AP_MATCH_NAME, AP_CATEGORY,
197
+ AE_RESERVED_INFO, "EffectMain", AP_SUPPORT_URL);
198
+ return result;
199
+ }
200
+
201
+ extern "C" DllExport PF_Err EffectMain(PF_Cmd cmd, PF_InData *in_data, PF_OutData *out_data, PF_ParamDef *params[],
202
+ PF_LayerDef *output, void *extra) {
203
+ (void)output;
204
+ PF_Err err = PF_Err_NONE;
205
+ try {
206
+ switch (cmd) {
207
+ case PF_Cmd_ABOUT: err = about(out_data); break;
208
+ case PF_Cmd_GLOBAL_SETUP: err = global_setup(out_data); break;
209
+ case PF_Cmd_GLOBAL_SETDOWN: license::stop(); break;
210
+ case PF_Cmd_PARAMS_SETUP: err = params_setup(in_data, out_data); break;
211
+ case PF_Cmd_UPDATE_PARAMS_UI: err = update_params_ui(in_data, params); break;
212
+ case PF_Cmd_USER_CHANGED_PARAM:
213
+ err = user_changed_param(out_data, static_cast<const PF_UserChangedParamExtra *>(extra));
214
+ break;
215
+ case PF_Cmd_DO_DIALOG: err = show_options(out_data); break;
216
+ case PF_Cmd_SMART_PRE_RENDER: err = pre_render(in_data, static_cast<PF_PreRenderExtra *>(extra)); break;
217
+ case PF_Cmd_SMART_RENDER: err = smart_render(in_data, static_cast<PF_SmartRenderExtra *>(extra)); break;
218
+ default: break;
219
+ }
220
+ } catch (...) {
221
+ err = PF_Err_INTERNAL_STRUCT_DAMAGED; // never let a C++ exception reach After Effects
222
+ }
223
+ return err;
224
+ }
@@ -0,0 +1,95 @@
1
+ #include "License.h"
2
+
3
+ #include <cstdio>
4
+ #include <cstdlib>
5
+ #include <string>
6
+
7
+ #include "ap_effect_config.h" // generated: AP_BUNDLE_ID, AP_VERSION, AP_API_BASE, AP_PUBLIC_KEY, ...
8
+ #include "ap_native.h"
9
+
10
+ namespace license {
11
+ namespace {
12
+
13
+ // Process-wide; set and cleared on the main thread only (GLOBAL_SETUP /
14
+ // GLOBAL_SETDOWN), read-only while frames render.
15
+ ap_runtime *g_ap = nullptr; // ap-check: allow mfr_global (written only in GLOBAL_SETUP/SETDOWN)
16
+ bool g_enforce = true; // ap-check: allow mfr_global (written only in GLOBAL_SETUP)
17
+
18
+ void copy(char *out, std::size_t size, const char *text) {
19
+ if (size) std::snprintf(out, size, "%s", text);
20
+ }
21
+
22
+ // Dev builds render without the watermark so you can test the look. To see
23
+ // the locked state in a dev build, create the file
24
+ // ~/.afterpresets/native-dev-enforce-license (or start After Effects with
25
+ // AP_DEV_ENFORCE_LICENSE=1) and restart After Effects. Release builds always enforce.
26
+ bool dev_enforce_requested() {
27
+ if (std::getenv("AP_DEV_ENFORCE_LICENSE")) return true;
28
+ #ifdef _WIN32
29
+ const char *home = std::getenv("USERPROFILE");
30
+ #else
31
+ const char *home = std::getenv("HOME");
32
+ #endif
33
+ if (!home) return false;
34
+ std::string flag = std::string(home) + "/.afterpresets/native-dev-enforce-license";
35
+ FILE *f = std::fopen(flag.c_str(), "rb");
36
+ if (f) std::fclose(f);
37
+ return f != nullptr;
38
+ }
39
+
40
+ } // namespace
41
+
42
+ void start() {
43
+ if (g_ap) return;
44
+ ap_config c;
45
+ ap_config_defaults(&c);
46
+ c.bundle_id = AP_BUNDLE_ID;
47
+ c.version = AP_VERSION;
48
+ c.api_base = AP_API_BASE;
49
+ c.public_key = AP_PUBLIC_KEY; // Ed25519 public key: verifies, cannot sign
50
+ c.product_url = AP_PRODUCT_URL[0] ? AP_PRODUCT_URL : nullptr;
51
+ c.manage_devices_url = AP_MANAGE_DEVICES_URL[0] ? AP_MANAGE_DEVICES_URL : nullptr;
52
+ c.license_enabled = AP_LICENSE_ENABLED;
53
+ c.offline_grace_days = AP_OFFLINE_GRACE_DAYS;
54
+ c.check_in_background = AP_CHECK_IN_BACKGROUND;
55
+ c.update_mode = AP_UPDATE_MODE;
56
+ c.dev = AP_DEV_BUILD; // dev builds never install updates
57
+ c.build_id = AP_BUILD_ID; // logged at start; `ap dev` reads it to know which build is loaded
58
+ g_ap = ap_init(&c); // NULL (config error): stays locked, never crashes
59
+ g_enforce = AP_LICENSE_ENABLED && (!AP_DEV_BUILD || dev_enforce_requested());
60
+ ap_apply_staged_update(g_ap); // an update staged in an earlier session
61
+ }
62
+
63
+ void stop() {
64
+ ap_log(g_ap, 1, "global setdown"); // lets tools confirm After Effects shut the effect down cleanly
65
+ ap_apply_staged_update(g_ap); // usually a no-op: updates apply right after download
66
+ ap_shutdown(g_ap);
67
+ g_ap = nullptr;
68
+ }
69
+
70
+ bool unlocked() {
71
+ if (!g_enforce) return true;
72
+ return ap_is_unlocked(g_ap) != 0; // NULL runtime: locked
73
+ }
74
+
75
+ void options_dialog() { ap_options_dialog(g_ap, AP_EFFECT_NAME); }
76
+
77
+ void status_name(char *out, std::size_t size) {
78
+ char notice[256] = "";
79
+ if (g_ap && ap_update_notice(g_ap, notice, sizeof notice) && notice[0]) return copy(out, size, "Update: restart AE");
80
+ if (!AP_LICENSE_ENABLED) return copy(out, size, "AfterPresets");
81
+ if (!g_ap) return copy(out, size, "License: unavailable");
82
+ switch (ap_status_get(g_ap)) {
83
+ case AP_STATUS_DISABLED: return copy(out, size, "AfterPresets");
84
+ case AP_STATUS_ACTIVE: return copy(out, size, "License: active");
85
+ case AP_STATUS_OFFLINE_GRACE: return copy(out, size, "License: offline");
86
+ case AP_STATUS_SIGNING_IN: return copy(out, size, "License: signing in");
87
+ case AP_STATUS_CHECKING: return copy(out, size, "License: checking");
88
+ case AP_STATUS_SIGNED_OUT: return copy(out, size, "License: sign in");
89
+ default: return copy(out, size, "License: see Options");
90
+ }
91
+ }
92
+
93
+ const char *button_label() { return AP_LICENSE_ENABLED ? "License..." : "About..."; }
94
+
95
+ } // namespace license
@@ -0,0 +1,29 @@
1
+ // Glue between the effect and the AfterPresets runtime (ap_native): license
2
+ // state, the Options dialog, the update notice. Keep this file: it is how
3
+ // buyers sign in and how updates reach them. `ap docs` has the details.
4
+ #pragma once
5
+
6
+ #include <cstddef>
7
+
8
+ namespace license {
9
+
10
+ // PF_Cmd_GLOBAL_SETUP / PF_Cmd_GLOBAL_SETDOWN (main thread).
11
+ void start();
12
+ void stop();
13
+
14
+ // Any thread, including render threads (lock-free): true when the frame
15
+ // renders without the watermark. Read it once per frame in PreRender.
16
+ bool unlocked();
17
+
18
+ // Effect > Options, or the status button (main thread). Shows the
19
+ // AfterPresets dialog: license state, Sign in / Buy / Sign out, update notice.
20
+ void options_dialog();
21
+
22
+ // Short text for the status button's name (at most 31 bytes), e.g.
23
+ // "License: active" or "Update: restart AE". UI thread.
24
+ void status_name(char *out, std::size_t size);
25
+
26
+ // Caption on the status button.
27
+ const char *button_label();
28
+
29
+ } // namespace license
@@ -0,0 +1,68 @@
1
+ // Pixel work for the starter effect: tint toward a colour, blended by mode,
2
+ // optionally along a gradient (centre + angle). One template serves 8, 16 and
3
+ // 32 bits per channel. Pure functions of their inputs, so they are safe for
4
+ // multi-frame rendering (After Effects renders several frames at once).
5
+ #pragma once
6
+
7
+ #include <cmath>
8
+ #include <cstddef>
9
+
10
+ struct TintParams {
11
+ float amount; // 0..1
12
+ float r, g, b; // 0..1
13
+ int mode; // AP_MODE_* (1-based popup value)
14
+ bool gradient; // fade the tint across the layer
15
+ float cx, cy; // gradient centre, in the pixels of the buffers below
16
+ float dirx, diry; // unit vector the tint grows along
17
+ float span; // pixels over which the gradient goes from 0 to full
18
+ bool watermark; // not licensed: draw stripes
19
+ };
20
+
21
+ template <typename T> inline T clampv(T v, T lo, T hi) { return v < lo ? lo : (v > hi ? hi : v); }
22
+
23
+ // Premultiplied colour channel blend. s, c in 0..1 (s premultiplied by a).
24
+ inline float blend(int mode, float s, float c, float a) {
25
+ switch (mode) {
26
+ case 2: return s + c * a - s * c; // Screen
27
+ case 3: return s * c; // Multiply
28
+ default: return c * a; // Tint
29
+ }
30
+ }
31
+
32
+ // PixelT: PF_Pixel / PF_Pixel16 / PF_PixelFloat. MAXV: 255, 32768 or 1 (float, unclamped).
33
+ template <typename PixelT, int MAXV, bool FLOAT>
34
+ void tint_rows(const char *src, long src_rowbytes, char *dst, long dst_rowbytes, int width, int height,
35
+ const TintParams &p) {
36
+ const float scale = FLOAT ? 1.f : (float)MAXV;
37
+ for (int y = 0; y < height; ++y) {
38
+ const PixelT *s = (const PixelT *)(src + (std::ptrdiff_t)y * src_rowbytes);
39
+ PixelT *d = (PixelT *)(dst + (std::ptrdiff_t)y * dst_rowbytes);
40
+ for (int x = 0; x < width; ++x) {
41
+ const float a = s[x].alpha / scale;
42
+ const float sr = s[x].red / scale, sg = s[x].green / scale, sb = s[x].blue / scale;
43
+ float t = p.amount;
44
+ if (p.gradient) {
45
+ const float along = ((float)x - p.cx) * p.dirx + ((float)y - p.cy) * p.diry;
46
+ t *= clampv(0.5f + along / p.span, 0.f, 1.f);
47
+ }
48
+ float r = sr + (blend(p.mode, sr, p.r, a) - sr) * t;
49
+ float g = sg + (blend(p.mode, sg, p.g, a) - sg) * t;
50
+ float b = sb + (blend(p.mode, sb, p.b, a) - sb) * t;
51
+ if (p.watermark && (((x + y) / 24) & 1)) {
52
+ r = r * 0.5f + 0.25f * a;
53
+ g = g * 0.5f + 0.25f * a;
54
+ b = b * 0.5f + 0.25f * a;
55
+ }
56
+ d[x].alpha = s[x].alpha;
57
+ if (FLOAT) {
58
+ d[x].red = (decltype(d[x].red))r;
59
+ d[x].green = (decltype(d[x].green))g;
60
+ d[x].blue = (decltype(d[x].blue))b;
61
+ } else {
62
+ d[x].red = (decltype(d[x].red))clampv(r * scale + 0.5f, 0.f, scale);
63
+ d[x].green = (decltype(d[x].green))clampv(g * scale + 0.5f, 0.f, scale);
64
+ d[x].blue = (decltype(d[x].blue))clampv(b * scale + 0.5f, 0.f, scale);
65
+ }
66
+ }
67
+ }
68
+ }