@synthetic-ai/premiere-mcp 2.0.0 → 2.2.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 (60) hide show
  1. package/CHANGELOG.md +135 -73
  2. package/LICENSE +21 -21
  3. package/README.md +242 -242
  4. package/cep-plugin/.debug +8 -8
  5. package/cep-plugin/CSInterface.js +92 -70
  6. package/cep-plugin/CSXS/manifest.xml +49 -49
  7. package/cep-plugin/host.jsx +7 -7
  8. package/cep-plugin/index.html +472 -419
  9. package/cep-plugin/main.js +683 -327
  10. package/cep-plugin/premiere.jsx +24947 -24947
  11. package/dist/bridge/script-builder.js +378 -378
  12. package/dist/index.js +117 -18
  13. package/dist/license.d.ts +22 -0
  14. package/dist/license.js +89 -0
  15. package/dist/server.js +67 -61
  16. package/dist/tools/advanced.js +357 -357
  17. package/dist/tools/audio-advanced.js +1049 -1049
  18. package/dist/tools/audio.js +42 -42
  19. package/dist/tools/captions.js +27 -27
  20. package/dist/tools/clipboard.js +214 -214
  21. package/dist/tools/color-advanced.js +253 -253
  22. package/dist/tools/discovery.js +291 -291
  23. package/dist/tools/effects.js +285 -285
  24. package/dist/tools/export-presets.js +206 -206
  25. package/dist/tools/export.js +255 -255
  26. package/dist/tools/health.js +10 -10
  27. package/dist/tools/inspection.js +885 -885
  28. package/dist/tools/keyframes.js +369 -369
  29. package/dist/tools/markers.js +89 -89
  30. package/dist/tools/media.js +178 -178
  31. package/dist/tools/metadata.js +94 -94
  32. package/dist/tools/multicam.js +50 -50
  33. package/dist/tools/playback.js +15 -15
  34. package/dist/tools/playhead.js +95 -95
  35. package/dist/tools/project-manager.js +21 -21
  36. package/dist/tools/project.js +149 -149
  37. package/dist/tools/scripting.js +319 -319
  38. package/dist/tools/selection.js +202 -202
  39. package/dist/tools/sequence-advanced.js +379 -374
  40. package/dist/tools/sequence-settings.js +564 -556
  41. package/dist/tools/sequence.js +169 -150
  42. package/dist/tools/source-monitor.js +68 -68
  43. package/dist/tools/text.js +58 -58
  44. package/dist/tools/timeline.js +312 -312
  45. package/dist/tools/track-management.js +446 -446
  46. package/dist/tools/track-targeting.js +635 -635
  47. package/dist/tools/tracks.js +87 -87
  48. package/dist/tools/transitions-motion.js +174 -174
  49. package/dist/tools/transitions.js +111 -111
  50. package/dist/tools/utility.js +606 -606
  51. package/dist/tools/workspace.js +29 -29
  52. package/package.json +1 -1
  53. package/scripts/build-chat-dmg.sh +210 -210
  54. package/scripts/build-chat-zip.sh +291 -291
  55. package/scripts/install-cep-windows.ps1 +99 -99
  56. package/scripts/install-cep.sh +66 -66
  57. package/scripts/install-chat-plugin.bat +67 -67
  58. package/scripts/install-chat-plugin.sh +85 -85
  59. package/scripts/install-windows.bat +95 -95
  60. package/scripts/package.mjs +254 -218
@@ -2,389 +2,389 @@
2
2
  * Builds ExtendScript strings with helper functions prepended.
3
3
  * All generated code must be ES3-compatible (var, no arrow functions, no let/const).
4
4
  */
5
- const HELPERS = `
6
- // === MCP Bridge Helpers (auto-prepended) ===
7
-
8
- var TICKS_PER_SECOND = 254016000000;
9
-
10
- function __ticksToSeconds(ticks) {
11
- return parseFloat(ticks) / TICKS_PER_SECOND;
12
- }
13
-
14
- function __secondsToTicks(seconds) {
15
- return Math.round(parseFloat(seconds) * TICKS_PER_SECOND);
16
- }
17
-
18
- function __ticksToTimecode(ticks, fps) {
19
- var totalSeconds = __ticksToSeconds(ticks);
20
- var hours = Math.floor(totalSeconds / 3600);
21
- var minutes = Math.floor((totalSeconds % 3600) / 60);
22
- var secs = Math.floor(totalSeconds % 60);
23
- var frames = Math.floor((totalSeconds % 1) * fps);
24
- return __pad(hours) + ":" + __pad(minutes) + ":" + __pad(secs) + ":" + __pad(frames);
25
- }
26
-
27
- function __pad(n) {
28
- return n < 10 ? "0" + n : "" + n;
29
- }
30
-
31
- function __findSequence(idOrName) {
32
- var project = app.project;
33
- for (var i = 0; i < project.sequences.numSequences; i++) {
34
- var seq = project.sequences[i];
35
- if (seq.sequenceID === idOrName || seq.name === idOrName) {
36
- return seq;
37
- }
38
- }
39
- return null;
40
- }
41
-
42
- function __findProjectItem(nodeIdOrName, rootItem) {
43
- if (!rootItem) rootItem = app.project.rootItem;
44
- for (var i = 0; i < rootItem.children.numItems; i++) {
45
- var item = rootItem.children[i];
46
- if (item.nodeId === nodeIdOrName || item.name === nodeIdOrName) {
47
- return item;
48
- }
49
- if (item.type === 2) { // Bin
50
- var found = __findProjectItem(nodeIdOrName, item);
51
- if (found) return found;
52
- }
53
- }
54
- return null;
55
- }
56
-
57
- function __findClip(nodeId) {
58
- var seq = app.project.activeSequence;
59
- if (!seq) return null;
60
-
61
- // Search video tracks
62
- for (var t = 0; t < seq.videoTracks.numTracks; t++) {
63
- var track = seq.videoTracks[t];
64
- for (var c = 0; c < track.clips.numItems; c++) {
65
- var clip = track.clips[c];
66
- if (clip.nodeId === nodeId) {
67
- return { clip: clip, trackIndex: t, clipIndex: c, trackType: "video" };
68
- }
69
- }
70
- }
71
-
72
- // Search audio tracks
73
- for (var t = 0; t < seq.audioTracks.numTracks; t++) {
74
- var track = seq.audioTracks[t];
75
- for (var c = 0; c < track.clips.numItems; c++) {
76
- var clip = track.clips[c];
77
- if (clip.nodeId === nodeId) {
78
- return { clip: clip, trackIndex: t, clipIndex: c, trackType: "audio" };
79
- }
80
- }
81
- }
82
-
83
- return null;
84
- }
85
-
86
- function __getAllClips(seq) {
87
- if (!seq) seq = app.project.activeSequence;
88
- if (!seq) return [];
89
- var clips = [];
90
-
91
- for (var t = 0; t < seq.videoTracks.numTracks; t++) {
92
- var track = seq.videoTracks[t];
93
- for (var c = 0; c < track.clips.numItems; c++) {
94
- var clip = track.clips[c];
95
- clips.push({
96
- nodeId: clip.nodeId,
97
- name: clip.name,
98
- trackIndex: t,
99
- trackType: "video",
100
- inPoint: __ticksToSeconds(clip.inPoint.ticks),
101
- outPoint: __ticksToSeconds(clip.outPoint.ticks),
102
- start: __ticksToSeconds(clip.start.ticks),
103
- end: __ticksToSeconds(clip.end.ticks),
104
- duration: __ticksToSeconds(clip.duration.ticks),
105
- mediaType: clip.mediaType
106
- });
107
- }
108
- }
109
-
110
- for (var t = 0; t < seq.audioTracks.numTracks; t++) {
111
- var track = seq.audioTracks[t];
112
- for (var c = 0; c < track.clips.numItems; c++) {
113
- var clip = track.clips[c];
114
- clips.push({
115
- nodeId: clip.nodeId,
116
- name: clip.name,
117
- trackIndex: t,
118
- trackType: "audio",
119
- inPoint: __ticksToSeconds(clip.inPoint.ticks),
120
- outPoint: __ticksToSeconds(clip.outPoint.ticks),
121
- start: __ticksToSeconds(clip.start.ticks),
122
- end: __ticksToSeconds(clip.end.ticks),
123
- duration: __ticksToSeconds(clip.duration.ticks),
124
- mediaType: clip.mediaType
125
- });
126
- }
127
- }
128
-
129
- return clips;
130
- }
131
-
132
- // Recursively coerce undefined to null in an object tree so that the result
133
- // can be passed to ExtendScript's JSON.stringify safely. ExtendScript's
134
- // JSON.stringify (a) does not honor the 'replacer' parameter and (b) emits the
135
- // bare token 'undefined' for undefined values, which is invalid JSON and
136
- // crashes the CEP-side JSON.parse. Pre-sanitizing is the only reliable fix.
137
- function __sanitizeForJSON(obj) {
138
- if (obj === undefined || obj === null) return null;
139
- // ExtendScript also emits NaN / Infinity literals which are not valid JSON.
140
- // Coerce them to null so the CEP-side JSON.parse always succeeds.
141
- if (typeof obj === "number") {
142
- if (isNaN(obj) || obj === Infinity || obj === -Infinity) return null;
143
- return obj;
144
- }
145
- if (typeof obj !== "object") return obj;
146
- if (obj instanceof Array) {
147
- var arr = [];
148
- for (var i = 0; i < obj.length; i++) arr.push(__sanitizeForJSON(obj[i]));
149
- return arr;
150
- }
151
- // Don't recurse into Premiere host objects (Time, ProjectItem, etc.) — they
152
- // serialize as [object Object] and have inaccessible internals. Detect them
153
- // by lack of plain-object constructor.
154
- try {
155
- if (obj.constructor && obj.constructor !== Object && obj.constructor !== Array) {
156
- // For host objects, fall back to their toString / accessible primitive
157
- return obj.toString ? String(obj.toString()) : "[object]";
158
- }
159
- } catch (e) { /* fall through */ }
160
- var out = {};
161
- for (var k in obj) {
162
- if (obj.hasOwnProperty(k)) out[k] = __sanitizeForJSON(obj[k]);
163
- }
164
- return out;
165
- }
166
-
167
- function __jsonStringify(obj) {
168
- // ES3-compatible JSON stringify. ExtendScript's JSON.stringify emits the
169
- // literal token 'undefined' for undefined values (vs Node/Browser which omit
170
- // them) AND ignores the replacer parameter, making the produced string
171
- // invalid JSON and crashing the CEP-side JSON.parse. Sanitize first.
172
- if (typeof JSON !== "undefined" && JSON.stringify) {
173
- return JSON.stringify(__sanitizeForJSON(obj));
174
- }
175
- // Fallback for very old ExtendScript
176
- if (obj === null) return "null";
177
- if (obj === undefined) return "undefined";
178
- if (typeof obj === "string") return '"' + obj.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"').replace(/\\n/g, "\\\\n") + '"';
179
- if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
180
- if (obj instanceof Array) {
181
- var arr = [];
182
- for (var i = 0; i < obj.length; i++) {
183
- arr.push(__jsonStringify(obj[i]));
184
- }
185
- return "[" + arr.join(",") + "]";
186
- }
187
- if (typeof obj === "object") {
188
- var parts = [];
189
- for (var k in obj) {
190
- if (obj.hasOwnProperty(k)) {
191
- parts.push(__jsonStringify(k) + ":" + __jsonStringify(obj[k]));
192
- }
193
- }
194
- return "{" + parts.join(",") + "}";
195
- }
196
- return String(obj);
197
- }
198
-
199
- function __result(data) {
200
- return __jsonStringify({ success: true, data: data });
201
- }
202
-
203
- function __error(msg) {
204
- return __jsonStringify({ success: false, error: String(msg) });
205
- }
206
-
207
- // --- Locale & version resilient lookups -------------------------------------
208
-
209
- // Candidate matchNames for Premiere's intrinsic (fixed) effects. matchName is
210
- // locale-INDEPENDENT, so trying these first makes tools work on any language
211
- // install. displayName fallbacks are language-specific and only a last resort.
212
- var __INTRINSIC = {
213
- motion: { match: ["AE.ADBE Motion", "ADBE Motion", "motionControlEffect"], display: ["Motion", "Movimiento", "Bewegung", "Mouvement"] },
214
- opacity: { match: ["AE.ADBE Opacity", "ADBE Opacity", "opacityEffect"], display: ["Opacity", "Opacidad", "Deckkraft", "Opacité"] },
215
- volume: { match: ["AE.ADBE Audio Levels", "audioVolume", "audioGain", "ADBE Audio Levels"], display: ["Volume", "Volumen", "Lautstärke"] },
216
- panner: { match: ["AE.ADBE Pan", "audioPanner", "ADBE Pan"], display: ["Panner", "Panoramización", "Balance"] },
217
- lumetri: { match: ["AE.ADBE Lumetri", "ADBE Lumetri", "PR.ADBE Lumetri"], display: ["Lumetri Color", "Color Lumetri", "Lumetri-Farbe"] },
218
- timeRemap:{ match: ["AE.ADBE Time Remapping", "ADBE Time Remapping"], display: ["Time Remapping", "Reasignación de tiempo"] }
219
- };
220
-
221
- // Find a component on a clip by trying matchName candidates then displayName candidates.
222
- // matchNames/displayNames are arrays of strings. Returns the component or null.
223
- function __findComponent(clip, matchNames, displayNames) {
224
- if (!clip || !clip.components) return null;
225
- var i, c, k;
226
- if (matchNames) {
227
- for (i = 0; i < clip.components.numItems; i++) {
228
- c = clip.components[i];
229
- for (k = 0; k < matchNames.length; k++) {
230
- if (c.matchName === matchNames[k]) return c;
231
- }
232
- }
233
- }
234
- if (displayNames) {
235
- for (i = 0; i < clip.components.numItems; i++) {
236
- c = clip.components[i];
237
- for (k = 0; k < displayNames.length; k++) {
238
- if (c.displayName === displayNames[k]) return c;
239
- }
240
- }
241
- }
242
- return null;
243
- }
244
-
245
- // Shorthand: find an intrinsic component by key from the __INTRINSIC table.
246
- function __findIntrinsic(clip, key) {
247
- var def = __INTRINSIC[key];
248
- if (!def) return null;
249
- return __findComponent(clip, def.match, def.display);
250
- }
251
-
252
- // Find a property inside a component by displayName candidates, descending one
253
- // level into nested property groups (needed for Lumetri's grouped params).
254
- function __findProp(comp, names) {
255
- if (!comp || !comp.properties) return null;
256
- var p, n, prop, sp, sub;
257
- for (p = 0; p < comp.properties.numItems; p++) {
258
- prop = comp.properties[p];
259
- for (n = 0; n < names.length; n++) {
260
- if (prop.displayName === names[n]) return prop;
261
- }
262
- }
263
- // second pass: nested groups
264
- for (p = 0; p < comp.properties.numItems; p++) {
265
- prop = comp.properties[p];
266
- if (prop.properties && prop.properties.numItems) {
267
- for (sp = 0; sp < prop.properties.numItems; sp++) {
268
- sub = prop.properties[sp];
269
- for (n = 0; n < names.length; n++) {
270
- if (sub.displayName === names[n]) return sub;
271
- }
272
- }
273
- }
274
- }
275
- return null;
276
- }
277
-
278
- // Locale-aware aliases for common Premiere Pro transition names. matchName
279
- // would be ideal but QE's transition objects don't reliably expose it, so we
280
- // map the English canonical name to localized displayNames seen in the wild.
281
- var __TRANSITION_ALIASES = {
282
- "Cross Dissolve": ["Cross Dissolve", "Disolvencia cruzada", "Fondu enchaîné", "Weiche Blende"],
283
- "Dip to Black": ["Dip to Black", "Inmersión a negro", "Fondu au noir", "Schwarzblende"],
284
- "Dip to White": ["Dip to White", "Inmersión a blanco", "Fondu au blanc", "Weißblende"],
285
- "Film Dissolve": ["Film Dissolve", "Disolvencia de película"],
286
- "Additive Dissolve": ["Additive Dissolve", "Disolvencia aditiva"],
287
- "Morph Cut": ["Morph Cut", "Corte por transformación"],
288
- "Constant Power": ["Constant Power", "Potencia constante"],
289
- "Constant Gain": ["Constant Gain", "Ganancia constante"],
290
- "Exponential Fade": ["Exponential Fade", "Fundido exponencial"]
291
- };
292
-
293
- // Resolve a transition by name from a QE transition list, locale-resilient.
294
- // First tries qe.project.getVideoTransitionByName / getAudioTransitionByName,
295
- // then iterates the list matching the requested name OR any localized alias.
296
- // kind: "video" | "audio". Returns the QE transition object or null.
297
- function __findTransition(name, kind) {
298
- try { app.enableQE(); } catch (e) {}
299
- if (typeof qe === "undefined" || !qe.project) return null;
300
- var direct = null;
301
- try {
302
- if (kind === "audio" && qe.project.getAudioTransitionByName) {
303
- direct = qe.project.getAudioTransitionByName(name);
304
- } else if (qe.project.getVideoTransitionByName) {
305
- direct = qe.project.getVideoTransitionByName(name);
306
- }
307
- } catch (e) {}
308
- if (direct) return direct;
309
- var list = null;
310
- try {
311
- list = (kind === "audio") ? qe.project.getAudioTransitionList() : qe.project.getVideoTransitionList();
312
- } catch (e) { list = null; }
313
- if (!list || !list.numItems) return null;
314
- // Candidate names: the requested name, plus aliases for it (forward map),
315
- // plus the canonical name if the request matches any alias (reverse map).
316
- var candidates = [name];
317
- var k, j, aliases;
318
- if (__TRANSITION_ALIASES[name]) {
319
- aliases = __TRANSITION_ALIASES[name];
320
- for (j = 0; j < aliases.length; j++) candidates.push(aliases[j]);
321
- }
322
- for (k in __TRANSITION_ALIASES) {
323
- if (__TRANSITION_ALIASES.hasOwnProperty(k)) {
324
- aliases = __TRANSITION_ALIASES[k];
325
- for (j = 0; j < aliases.length; j++) {
326
- if (aliases[j] === name) {
327
- candidates.push(k);
328
- for (var jj = 0; jj < aliases.length; jj++) candidates.push(aliases[jj]);
329
- }
330
- }
331
- }
332
- }
333
- for (var i = 0; i < list.numItems; i++) {
334
- var t = list[i];
335
- var tName = t.name;
336
- var tMatch = t.matchName;
337
- for (var c = 0; c < candidates.length; c++) {
338
- if (tName === candidates[c]) return t;
339
- if (tMatch && tMatch === candidates[c]) return t;
340
- }
341
- }
342
- return null;
343
- }
344
-
345
- // Resolve an ID that could be either a project item nodeId/name OR a timeline
346
- // clip nodeId, to a ProjectItem. Used by tools whose schema says "project item
347
- // node_id or name" but where callers commonly pass timeline clip nodeIds
348
- // (which come from get_active_sequence / list_sequence_tracks).
349
- // Returns the ProjectItem or null.
350
- function __resolveProjectItemOrTimelineClip(idOrName) {
351
- if (!idOrName) return null;
352
- // First try the project tree
353
- var pi = __findProjectItem(idOrName);
354
- if (pi) return pi;
355
- // Fall back: treat as timeline clip nodeId, resolve to its projectItem
356
- try {
357
- var r = __findClip(idOrName);
358
- if (r && r.clip && r.clip.projectItem) return r.clip.projectItem;
359
- } catch (e) {}
360
- return null;
361
- }
362
-
363
- // Read a member that some Premiere versions expose as a property and others as
364
- // a zero-arg method (e.g. isMuted, isSelected, isOffline, isSequence). Returns
365
- // the resolved value, or the provided default if it cannot be read.
366
- function __readMaybe(obj, name, dflt) {
367
- try {
368
- var v = obj[name];
369
- if (typeof v === "function") return v.call(obj);
370
- if (v === undefined) return dflt;
371
- return v;
372
- } catch(e) { return dflt; }
373
- }
374
-
375
- // === End MCP Bridge Helpers ===
5
+ const HELPERS = `
6
+ // === MCP Bridge Helpers (auto-prepended) ===
7
+
8
+ var TICKS_PER_SECOND = 254016000000;
9
+
10
+ function __ticksToSeconds(ticks) {
11
+ return parseFloat(ticks) / TICKS_PER_SECOND;
12
+ }
13
+
14
+ function __secondsToTicks(seconds) {
15
+ return Math.round(parseFloat(seconds) * TICKS_PER_SECOND);
16
+ }
17
+
18
+ function __ticksToTimecode(ticks, fps) {
19
+ var totalSeconds = __ticksToSeconds(ticks);
20
+ var hours = Math.floor(totalSeconds / 3600);
21
+ var minutes = Math.floor((totalSeconds % 3600) / 60);
22
+ var secs = Math.floor(totalSeconds % 60);
23
+ var frames = Math.floor((totalSeconds % 1) * fps);
24
+ return __pad(hours) + ":" + __pad(minutes) + ":" + __pad(secs) + ":" + __pad(frames);
25
+ }
26
+
27
+ function __pad(n) {
28
+ return n < 10 ? "0" + n : "" + n;
29
+ }
30
+
31
+ function __findSequence(idOrName) {
32
+ var project = app.project;
33
+ for (var i = 0; i < project.sequences.numSequences; i++) {
34
+ var seq = project.sequences[i];
35
+ if (seq.sequenceID === idOrName || seq.name === idOrName) {
36
+ return seq;
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+
42
+ function __findProjectItem(nodeIdOrName, rootItem) {
43
+ if (!rootItem) rootItem = app.project.rootItem;
44
+ for (var i = 0; i < rootItem.children.numItems; i++) {
45
+ var item = rootItem.children[i];
46
+ if (item.nodeId === nodeIdOrName || item.name === nodeIdOrName) {
47
+ return item;
48
+ }
49
+ if (item.type === 2) { // Bin
50
+ var found = __findProjectItem(nodeIdOrName, item);
51
+ if (found) return found;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ function __findClip(nodeId) {
58
+ var seq = app.project.activeSequence;
59
+ if (!seq) return null;
60
+
61
+ // Search video tracks
62
+ for (var t = 0; t < seq.videoTracks.numTracks; t++) {
63
+ var track = seq.videoTracks[t];
64
+ for (var c = 0; c < track.clips.numItems; c++) {
65
+ var clip = track.clips[c];
66
+ if (clip.nodeId === nodeId) {
67
+ return { clip: clip, trackIndex: t, clipIndex: c, trackType: "video" };
68
+ }
69
+ }
70
+ }
71
+
72
+ // Search audio tracks
73
+ for (var t = 0; t < seq.audioTracks.numTracks; t++) {
74
+ var track = seq.audioTracks[t];
75
+ for (var c = 0; c < track.clips.numItems; c++) {
76
+ var clip = track.clips[c];
77
+ if (clip.nodeId === nodeId) {
78
+ return { clip: clip, trackIndex: t, clipIndex: c, trackType: "audio" };
79
+ }
80
+ }
81
+ }
82
+
83
+ return null;
84
+ }
85
+
86
+ function __getAllClips(seq) {
87
+ if (!seq) seq = app.project.activeSequence;
88
+ if (!seq) return [];
89
+ var clips = [];
90
+
91
+ for (var t = 0; t < seq.videoTracks.numTracks; t++) {
92
+ var track = seq.videoTracks[t];
93
+ for (var c = 0; c < track.clips.numItems; c++) {
94
+ var clip = track.clips[c];
95
+ clips.push({
96
+ nodeId: clip.nodeId,
97
+ name: clip.name,
98
+ trackIndex: t,
99
+ trackType: "video",
100
+ inPoint: __ticksToSeconds(clip.inPoint.ticks),
101
+ outPoint: __ticksToSeconds(clip.outPoint.ticks),
102
+ start: __ticksToSeconds(clip.start.ticks),
103
+ end: __ticksToSeconds(clip.end.ticks),
104
+ duration: __ticksToSeconds(clip.duration.ticks),
105
+ mediaType: clip.mediaType
106
+ });
107
+ }
108
+ }
109
+
110
+ for (var t = 0; t < seq.audioTracks.numTracks; t++) {
111
+ var track = seq.audioTracks[t];
112
+ for (var c = 0; c < track.clips.numItems; c++) {
113
+ var clip = track.clips[c];
114
+ clips.push({
115
+ nodeId: clip.nodeId,
116
+ name: clip.name,
117
+ trackIndex: t,
118
+ trackType: "audio",
119
+ inPoint: __ticksToSeconds(clip.inPoint.ticks),
120
+ outPoint: __ticksToSeconds(clip.outPoint.ticks),
121
+ start: __ticksToSeconds(clip.start.ticks),
122
+ end: __ticksToSeconds(clip.end.ticks),
123
+ duration: __ticksToSeconds(clip.duration.ticks),
124
+ mediaType: clip.mediaType
125
+ });
126
+ }
127
+ }
128
+
129
+ return clips;
130
+ }
131
+
132
+ // Recursively coerce undefined to null in an object tree so that the result
133
+ // can be passed to ExtendScript's JSON.stringify safely. ExtendScript's
134
+ // JSON.stringify (a) does not honor the 'replacer' parameter and (b) emits the
135
+ // bare token 'undefined' for undefined values, which is invalid JSON and
136
+ // crashes the CEP-side JSON.parse. Pre-sanitizing is the only reliable fix.
137
+ function __sanitizeForJSON(obj) {
138
+ if (obj === undefined || obj === null) return null;
139
+ // ExtendScript also emits NaN / Infinity literals which are not valid JSON.
140
+ // Coerce them to null so the CEP-side JSON.parse always succeeds.
141
+ if (typeof obj === "number") {
142
+ if (isNaN(obj) || obj === Infinity || obj === -Infinity) return null;
143
+ return obj;
144
+ }
145
+ if (typeof obj !== "object") return obj;
146
+ if (obj instanceof Array) {
147
+ var arr = [];
148
+ for (var i = 0; i < obj.length; i++) arr.push(__sanitizeForJSON(obj[i]));
149
+ return arr;
150
+ }
151
+ // Don't recurse into Premiere host objects (Time, ProjectItem, etc.) — they
152
+ // serialize as [object Object] and have inaccessible internals. Detect them
153
+ // by lack of plain-object constructor.
154
+ try {
155
+ if (obj.constructor && obj.constructor !== Object && obj.constructor !== Array) {
156
+ // For host objects, fall back to their toString / accessible primitive
157
+ return obj.toString ? String(obj.toString()) : "[object]";
158
+ }
159
+ } catch (e) { /* fall through */ }
160
+ var out = {};
161
+ for (var k in obj) {
162
+ if (obj.hasOwnProperty(k)) out[k] = __sanitizeForJSON(obj[k]);
163
+ }
164
+ return out;
165
+ }
166
+
167
+ function __jsonStringify(obj) {
168
+ // ES3-compatible JSON stringify. ExtendScript's JSON.stringify emits the
169
+ // literal token 'undefined' for undefined values (vs Node/Browser which omit
170
+ // them) AND ignores the replacer parameter, making the produced string
171
+ // invalid JSON and crashing the CEP-side JSON.parse. Sanitize first.
172
+ if (typeof JSON !== "undefined" && JSON.stringify) {
173
+ return JSON.stringify(__sanitizeForJSON(obj));
174
+ }
175
+ // Fallback for very old ExtendScript
176
+ if (obj === null) return "null";
177
+ if (obj === undefined) return "undefined";
178
+ if (typeof obj === "string") return '"' + obj.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"').replace(/\\n/g, "\\\\n") + '"';
179
+ if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
180
+ if (obj instanceof Array) {
181
+ var arr = [];
182
+ for (var i = 0; i < obj.length; i++) {
183
+ arr.push(__jsonStringify(obj[i]));
184
+ }
185
+ return "[" + arr.join(",") + "]";
186
+ }
187
+ if (typeof obj === "object") {
188
+ var parts = [];
189
+ for (var k in obj) {
190
+ if (obj.hasOwnProperty(k)) {
191
+ parts.push(__jsonStringify(k) + ":" + __jsonStringify(obj[k]));
192
+ }
193
+ }
194
+ return "{" + parts.join(",") + "}";
195
+ }
196
+ return String(obj);
197
+ }
198
+
199
+ function __result(data) {
200
+ return __jsonStringify({ success: true, data: data });
201
+ }
202
+
203
+ function __error(msg) {
204
+ return __jsonStringify({ success: false, error: String(msg) });
205
+ }
206
+
207
+ // --- Locale & version resilient lookups -------------------------------------
208
+
209
+ // Candidate matchNames for Premiere's intrinsic (fixed) effects. matchName is
210
+ // locale-INDEPENDENT, so trying these first makes tools work on any language
211
+ // install. displayName fallbacks are language-specific and only a last resort.
212
+ var __INTRINSIC = {
213
+ motion: { match: ["AE.ADBE Motion", "ADBE Motion", "motionControlEffect"], display: ["Motion", "Movimiento", "Bewegung", "Mouvement"] },
214
+ opacity: { match: ["AE.ADBE Opacity", "ADBE Opacity", "opacityEffect"], display: ["Opacity", "Opacidad", "Deckkraft", "Opacité"] },
215
+ volume: { match: ["AE.ADBE Audio Levels", "audioVolume", "audioGain", "ADBE Audio Levels"], display: ["Volume", "Volumen", "Lautstärke"] },
216
+ panner: { match: ["AE.ADBE Pan", "audioPanner", "ADBE Pan"], display: ["Panner", "Panoramización", "Balance"] },
217
+ lumetri: { match: ["AE.ADBE Lumetri", "ADBE Lumetri", "PR.ADBE Lumetri"], display: ["Lumetri Color", "Color Lumetri", "Lumetri-Farbe"] },
218
+ timeRemap:{ match: ["AE.ADBE Time Remapping", "ADBE Time Remapping"], display: ["Time Remapping", "Reasignación de tiempo"] }
219
+ };
220
+
221
+ // Find a component on a clip by trying matchName candidates then displayName candidates.
222
+ // matchNames/displayNames are arrays of strings. Returns the component or null.
223
+ function __findComponent(clip, matchNames, displayNames) {
224
+ if (!clip || !clip.components) return null;
225
+ var i, c, k;
226
+ if (matchNames) {
227
+ for (i = 0; i < clip.components.numItems; i++) {
228
+ c = clip.components[i];
229
+ for (k = 0; k < matchNames.length; k++) {
230
+ if (c.matchName === matchNames[k]) return c;
231
+ }
232
+ }
233
+ }
234
+ if (displayNames) {
235
+ for (i = 0; i < clip.components.numItems; i++) {
236
+ c = clip.components[i];
237
+ for (k = 0; k < displayNames.length; k++) {
238
+ if (c.displayName === displayNames[k]) return c;
239
+ }
240
+ }
241
+ }
242
+ return null;
243
+ }
244
+
245
+ // Shorthand: find an intrinsic component by key from the __INTRINSIC table.
246
+ function __findIntrinsic(clip, key) {
247
+ var def = __INTRINSIC[key];
248
+ if (!def) return null;
249
+ return __findComponent(clip, def.match, def.display);
250
+ }
251
+
252
+ // Find a property inside a component by displayName candidates, descending one
253
+ // level into nested property groups (needed for Lumetri's grouped params).
254
+ function __findProp(comp, names) {
255
+ if (!comp || !comp.properties) return null;
256
+ var p, n, prop, sp, sub;
257
+ for (p = 0; p < comp.properties.numItems; p++) {
258
+ prop = comp.properties[p];
259
+ for (n = 0; n < names.length; n++) {
260
+ if (prop.displayName === names[n]) return prop;
261
+ }
262
+ }
263
+ // second pass: nested groups
264
+ for (p = 0; p < comp.properties.numItems; p++) {
265
+ prop = comp.properties[p];
266
+ if (prop.properties && prop.properties.numItems) {
267
+ for (sp = 0; sp < prop.properties.numItems; sp++) {
268
+ sub = prop.properties[sp];
269
+ for (n = 0; n < names.length; n++) {
270
+ if (sub.displayName === names[n]) return sub;
271
+ }
272
+ }
273
+ }
274
+ }
275
+ return null;
276
+ }
277
+
278
+ // Locale-aware aliases for common Premiere Pro transition names. matchName
279
+ // would be ideal but QE's transition objects don't reliably expose it, so we
280
+ // map the English canonical name to localized displayNames seen in the wild.
281
+ var __TRANSITION_ALIASES = {
282
+ "Cross Dissolve": ["Cross Dissolve", "Disolvencia cruzada", "Fondu enchaîné", "Weiche Blende"],
283
+ "Dip to Black": ["Dip to Black", "Inmersión a negro", "Fondu au noir", "Schwarzblende"],
284
+ "Dip to White": ["Dip to White", "Inmersión a blanco", "Fondu au blanc", "Weißblende"],
285
+ "Film Dissolve": ["Film Dissolve", "Disolvencia de película"],
286
+ "Additive Dissolve": ["Additive Dissolve", "Disolvencia aditiva"],
287
+ "Morph Cut": ["Morph Cut", "Corte por transformación"],
288
+ "Constant Power": ["Constant Power", "Potencia constante"],
289
+ "Constant Gain": ["Constant Gain", "Ganancia constante"],
290
+ "Exponential Fade": ["Exponential Fade", "Fundido exponencial"]
291
+ };
292
+
293
+ // Resolve a transition by name from a QE transition list, locale-resilient.
294
+ // First tries qe.project.getVideoTransitionByName / getAudioTransitionByName,
295
+ // then iterates the list matching the requested name OR any localized alias.
296
+ // kind: "video" | "audio". Returns the QE transition object or null.
297
+ function __findTransition(name, kind) {
298
+ try { app.enableQE(); } catch (e) {}
299
+ if (typeof qe === "undefined" || !qe.project) return null;
300
+ var direct = null;
301
+ try {
302
+ if (kind === "audio" && qe.project.getAudioTransitionByName) {
303
+ direct = qe.project.getAudioTransitionByName(name);
304
+ } else if (qe.project.getVideoTransitionByName) {
305
+ direct = qe.project.getVideoTransitionByName(name);
306
+ }
307
+ } catch (e) {}
308
+ if (direct) return direct;
309
+ var list = null;
310
+ try {
311
+ list = (kind === "audio") ? qe.project.getAudioTransitionList() : qe.project.getVideoTransitionList();
312
+ } catch (e) { list = null; }
313
+ if (!list || !list.numItems) return null;
314
+ // Candidate names: the requested name, plus aliases for it (forward map),
315
+ // plus the canonical name if the request matches any alias (reverse map).
316
+ var candidates = [name];
317
+ var k, j, aliases;
318
+ if (__TRANSITION_ALIASES[name]) {
319
+ aliases = __TRANSITION_ALIASES[name];
320
+ for (j = 0; j < aliases.length; j++) candidates.push(aliases[j]);
321
+ }
322
+ for (k in __TRANSITION_ALIASES) {
323
+ if (__TRANSITION_ALIASES.hasOwnProperty(k)) {
324
+ aliases = __TRANSITION_ALIASES[k];
325
+ for (j = 0; j < aliases.length; j++) {
326
+ if (aliases[j] === name) {
327
+ candidates.push(k);
328
+ for (var jj = 0; jj < aliases.length; jj++) candidates.push(aliases[jj]);
329
+ }
330
+ }
331
+ }
332
+ }
333
+ for (var i = 0; i < list.numItems; i++) {
334
+ var t = list[i];
335
+ var tName = t.name;
336
+ var tMatch = t.matchName;
337
+ for (var c = 0; c < candidates.length; c++) {
338
+ if (tName === candidates[c]) return t;
339
+ if (tMatch && tMatch === candidates[c]) return t;
340
+ }
341
+ }
342
+ return null;
343
+ }
344
+
345
+ // Resolve an ID that could be either a project item nodeId/name OR a timeline
346
+ // clip nodeId, to a ProjectItem. Used by tools whose schema says "project item
347
+ // node_id or name" but where callers commonly pass timeline clip nodeIds
348
+ // (which come from get_active_sequence / list_sequence_tracks).
349
+ // Returns the ProjectItem or null.
350
+ function __resolveProjectItemOrTimelineClip(idOrName) {
351
+ if (!idOrName) return null;
352
+ // First try the project tree
353
+ var pi = __findProjectItem(idOrName);
354
+ if (pi) return pi;
355
+ // Fall back: treat as timeline clip nodeId, resolve to its projectItem
356
+ try {
357
+ var r = __findClip(idOrName);
358
+ if (r && r.clip && r.clip.projectItem) return r.clip.projectItem;
359
+ } catch (e) {}
360
+ return null;
361
+ }
362
+
363
+ // Read a member that some Premiere versions expose as a property and others as
364
+ // a zero-arg method (e.g. isMuted, isSelected, isOffline, isSequence). Returns
365
+ // the resolved value, or the provided default if it cannot be read.
366
+ function __readMaybe(obj, name, dflt) {
367
+ try {
368
+ var v = obj[name];
369
+ if (typeof v === "function") return v.call(obj);
370
+ if (v === undefined) return dflt;
371
+ return v;
372
+ } catch(e) { return dflt; }
373
+ }
374
+
375
+ // === End MCP Bridge Helpers ===
376
376
  `;
377
377
  /**
378
378
  * Build a complete ExtendScript by wrapping user code in an IIFE with helpers.
379
379
  */
380
380
  export function buildScript(code) {
381
- return `${HELPERS}
382
- (function() {
383
- try {
384
- ${code}
385
- } catch(e) {
386
- return __error(e.toString());
387
- }
381
+ return `${HELPERS}
382
+ (function() {
383
+ try {
384
+ ${code}
385
+ } catch(e) {
386
+ return __error(e.toString());
387
+ }
388
388
  })();`;
389
389
  }
390
390
  /**