@synthetic-ai/premiere-mcp 2.2.0 → 2.3.0-dev.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 (57) hide show
  1. package/CHANGELOG.md +135 -135
  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 -92
  6. package/cep-plugin/host.jsx +7 -7
  7. package/cep-plugin/index.html +472 -472
  8. package/cep-plugin/main.js +6 -0
  9. package/cep-plugin/premiere.jsx +24947 -24947
  10. package/dist/bridge/script-builder.js +378 -378
  11. package/dist/license.js +38 -0
  12. package/dist/server.js +60 -60
  13. package/dist/tools/advanced.js +357 -357
  14. package/dist/tools/audio-advanced.js +1049 -1049
  15. package/dist/tools/audio.js +42 -42
  16. package/dist/tools/captions.js +27 -27
  17. package/dist/tools/clipboard.js +214 -214
  18. package/dist/tools/color-advanced.js +253 -253
  19. package/dist/tools/discovery.js +291 -291
  20. package/dist/tools/effects.js +285 -285
  21. package/dist/tools/export-presets.js +206 -206
  22. package/dist/tools/export.js +255 -255
  23. package/dist/tools/health.js +10 -10
  24. package/dist/tools/inspection.js +885 -885
  25. package/dist/tools/keyframes.js +369 -369
  26. package/dist/tools/markers.js +89 -89
  27. package/dist/tools/media.js +178 -178
  28. package/dist/tools/metadata.js +94 -94
  29. package/dist/tools/multicam.js +50 -50
  30. package/dist/tools/playback.js +15 -15
  31. package/dist/tools/playhead.js +95 -95
  32. package/dist/tools/project-manager.js +21 -21
  33. package/dist/tools/project.js +149 -149
  34. package/dist/tools/scripting.js +319 -319
  35. package/dist/tools/selection.js +202 -202
  36. package/dist/tools/sequence-advanced.js +379 -379
  37. package/dist/tools/sequence-settings.js +564 -564
  38. package/dist/tools/sequence.js +167 -167
  39. package/dist/tools/source-monitor.js +68 -68
  40. package/dist/tools/text.js +58 -58
  41. package/dist/tools/timeline.js +312 -312
  42. package/dist/tools/track-management.js +446 -446
  43. package/dist/tools/track-targeting.js +635 -635
  44. package/dist/tools/tracks.js +87 -87
  45. package/dist/tools/transitions-motion.js +174 -174
  46. package/dist/tools/transitions.js +111 -111
  47. package/dist/tools/utility.js +606 -606
  48. package/dist/tools/workspace.js +29 -29
  49. package/package.json +66 -66
  50. package/scripts/build-chat-dmg.sh +210 -210
  51. package/scripts/build-chat-zip.sh +291 -291
  52. package/scripts/install-cep-windows.ps1 +99 -99
  53. package/scripts/install-cep.sh +66 -66
  54. package/scripts/install-chat-plugin.bat +67 -67
  55. package/scripts/install-chat-plugin.sh +85 -85
  56. package/scripts/install-windows.bat +95 -95
  57. package/scripts/package.mjs +254 -254
@@ -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
  /**