@synthetic-ai/premiere-mcp 2.0.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 (104) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/cep-plugin/.debug +8 -0
  5. package/cep-plugin/CSInterface.js +70 -0
  6. package/cep-plugin/CSXS/manifest.xml +49 -0
  7. package/cep-plugin/host.jsx +7 -0
  8. package/cep-plugin/index.html +419 -0
  9. package/cep-plugin/main.js +327 -0
  10. package/cep-plugin/premiere.jsx +24947 -0
  11. package/cep-plugin/synthetic-logo.png +0 -0
  12. package/dist/bridge/file-bridge.d.ts +29 -0
  13. package/dist/bridge/file-bridge.js +145 -0
  14. package/dist/bridge/script-builder.d.ts +19 -0
  15. package/dist/bridge/script-builder.js +408 -0
  16. package/dist/http-server.d.ts +21 -0
  17. package/dist/http-server.js +140 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.js +70 -0
  20. package/dist/resources/extendscript-reference.d.ts +2 -0
  21. package/dist/resources/extendscript-reference.js +219 -0
  22. package/dist/server.d.ts +4 -0
  23. package/dist/server.js +276 -0
  24. package/dist/tools/advanced.d.ts +530 -0
  25. package/dist/tools/advanced.js +923 -0
  26. package/dist/tools/audio-advanced.d.ts +847 -0
  27. package/dist/tools/audio-advanced.js +1653 -0
  28. package/dist/tools/audio.d.ts +84 -0
  29. package/dist/tools/audio.js +123 -0
  30. package/dist/tools/captions.d.ts +30 -0
  31. package/dist/tools/captions.js +73 -0
  32. package/dist/tools/clipboard.d.ts +153 -0
  33. package/dist/tools/clipboard.js +381 -0
  34. package/dist/tools/color-advanced.d.ts +821 -0
  35. package/dist/tools/color-advanced.js +772 -0
  36. package/dist/tools/discovery.d.ts +128 -0
  37. package/dist/tools/discovery.js +445 -0
  38. package/dist/tools/effects.d.ts +211 -0
  39. package/dist/tools/effects.js +554 -0
  40. package/dist/tools/export-presets.d.ts +333 -0
  41. package/dist/tools/export-presets.js +490 -0
  42. package/dist/tools/export.d.ts +320 -0
  43. package/dist/tools/export.js +618 -0
  44. package/dist/tools/health.d.ts +9 -0
  45. package/dist/tools/health.js +25 -0
  46. package/dist/tools/inspection.d.ts +179 -0
  47. package/dist/tools/inspection.js +1089 -0
  48. package/dist/tools/keyframes.d.ts +253 -0
  49. package/dist/tools/keyframes.js +611 -0
  50. package/dist/tools/markers.d.ts +117 -0
  51. package/dist/tools/markers.js +206 -0
  52. package/dist/tools/media.d.ts +297 -0
  53. package/dist/tools/media.js +493 -0
  54. package/dist/tools/metadata.d.ts +178 -0
  55. package/dist/tools/metadata.js +278 -0
  56. package/dist/tools/multicam.d.ts +199 -0
  57. package/dist/tools/multicam.js +212 -0
  58. package/dist/tools/playback.d.ts +34 -0
  59. package/dist/tools/playback.js +63 -0
  60. package/dist/tools/playhead.d.ts +77 -0
  61. package/dist/tools/playhead.js +185 -0
  62. package/dist/tools/project-manager.d.ts +65 -0
  63. package/dist/tools/project-manager.js +81 -0
  64. package/dist/tools/project.d.ts +390 -0
  65. package/dist/tools/project.js +596 -0
  66. package/dist/tools/scripting.d.ts +98 -0
  67. package/dist/tools/scripting.js +434 -0
  68. package/dist/tools/selection.d.ts +120 -0
  69. package/dist/tools/selection.js +334 -0
  70. package/dist/tools/sequence-advanced.d.ts +84 -0
  71. package/dist/tools/sequence-advanced.js +471 -0
  72. package/dist/tools/sequence-settings.d.ts +397 -0
  73. package/dist/tools/sequence-settings.js +967 -0
  74. package/dist/tools/sequence.d.ts +211 -0
  75. package/dist/tools/sequence.js +378 -0
  76. package/dist/tools/source-monitor.d.ts +95 -0
  77. package/dist/tools/source-monitor.js +180 -0
  78. package/dist/tools/text.d.ts +97 -0
  79. package/dist/tools/text.js +159 -0
  80. package/dist/tools/timeline.d.ts +265 -0
  81. package/dist/tools/timeline.js +575 -0
  82. package/dist/tools/track-management.d.ts +482 -0
  83. package/dist/tools/track-management.js +787 -0
  84. package/dist/tools/track-targeting.d.ts +666 -0
  85. package/dist/tools/track-targeting.js +1318 -0
  86. package/dist/tools/tracks.d.ts +90 -0
  87. package/dist/tools/tracks.js +183 -0
  88. package/dist/tools/transitions-motion.d.ts +788 -0
  89. package/dist/tools/transitions-motion.js +680 -0
  90. package/dist/tools/transitions.d.ts +124 -0
  91. package/dist/tools/transitions.js +267 -0
  92. package/dist/tools/utility.d.ts +469 -0
  93. package/dist/tools/utility.js +1118 -0
  94. package/dist/tools/workspace.d.ts +25 -0
  95. package/dist/tools/workspace.js +61 -0
  96. package/package.json +66 -0
  97. package/scripts/build-chat-dmg.sh +210 -0
  98. package/scripts/build-chat-zip.sh +291 -0
  99. package/scripts/install-cep-windows.ps1 +99 -0
  100. package/scripts/install-cep.sh +66 -0
  101. package/scripts/install-chat-plugin.bat +67 -0
  102. package/scripts/install-chat-plugin.sh +85 -0
  103. package/scripts/install-windows.bat +95 -0
  104. package/scripts/package.mjs +218 -0
@@ -0,0 +1,1318 @@
1
+ import { buildToolScript, escapeForExtendScript } from "../bridge/script-builder.js";
2
+ import { sendCommand } from "../bridge/file-bridge.js";
3
+ export function getTrackTargetingTools(bridgeOptions) {
4
+ return {
5
+ set_target_track: {
6
+ description: "Set a track as targeted (active for insert/overwrite edits). Only one video and one audio track can be targeted at a time.",
7
+ parameters: {
8
+ type: "object",
9
+ properties: {
10
+ track_type: {
11
+ type: "string",
12
+ enum: ["video", "audio"],
13
+ description: "Track type",
14
+ },
15
+ track_index: {
16
+ type: "number",
17
+ description: "Track index (0-based)",
18
+ },
19
+ targeted: {
20
+ type: "boolean",
21
+ description: "Whether to target (true) or untarget (false) the track",
22
+ },
23
+ },
24
+ required: ["track_type", "track_index", "targeted"],
25
+ },
26
+ handler: async (args) => {
27
+ const script = buildToolScript(`
28
+ var seq = app.project.activeSequence;
29
+ if (!seq) return __error("No active sequence");
30
+
31
+ var tracks = ${args.track_type === "video" ? "seq.videoTracks" : "seq.audioTracks"};
32
+ if (${args.track_index} >= tracks.numTracks) return __error("Track index out of range");
33
+
34
+ var track = tracks[${args.track_index}];
35
+ track.setTargeted(${args.targeted}, ${args.track_type === "video"});
36
+
37
+ return __result({
38
+ trackType: "${args.track_type}",
39
+ trackIndex: ${args.track_index},
40
+ trackName: track.name,
41
+ targeted: ${args.targeted}
42
+ });
43
+ `);
44
+ return sendCommand(script, bridgeOptions);
45
+ },
46
+ },
47
+ get_target_tracks: {
48
+ description: "Get which tracks are currently targeted for editing.",
49
+ parameters: {},
50
+ handler: async () => {
51
+ const script = buildToolScript(`
52
+ var seq = app.project.activeSequence;
53
+ if (!seq) return __error("No active sequence");
54
+
55
+ var targets = { video: [], audio: [] };
56
+ for (var t = 0; t < seq.videoTracks.numTracks; t++) {
57
+ var track = seq.videoTracks[t];
58
+ try {
59
+ if (track.isTargeted()) {
60
+ targets.video.push({ index: t, name: track.name });
61
+ }
62
+ } catch(e) {}
63
+ }
64
+ for (var t = 0; t < seq.audioTracks.numTracks; t++) {
65
+ var track = seq.audioTracks[t];
66
+ try {
67
+ if (track.isTargeted()) {
68
+ targets.audio.push({ index: t, name: track.name });
69
+ }
70
+ } catch(e) {}
71
+ }
72
+
73
+ return __result(targets);
74
+ `);
75
+ return sendCommand(script, bridgeOptions);
76
+ },
77
+ },
78
+ set_all_tracks_targeted: {
79
+ description: "Set all tracks targeted or untargeted. Useful before insert/overwrite edits.",
80
+ parameters: {
81
+ type: "object",
82
+ properties: {
83
+ targeted: {
84
+ type: "boolean",
85
+ description: "Whether to target (true) or untarget (false) all tracks",
86
+ },
87
+ track_type: {
88
+ type: "string",
89
+ enum: ["video", "audio", "both"],
90
+ description: "Which track type(s) to affect (default: both)",
91
+ },
92
+ },
93
+ required: ["targeted"],
94
+ },
95
+ handler: async (args) => {
96
+ const trackType = args.track_type || "both";
97
+ const script = buildToolScript(`
98
+ var seq = app.project.activeSequence;
99
+ if (!seq) return __error("No active sequence");
100
+
101
+ var count = 0;
102
+ if ("${trackType}" !== "audio") {
103
+ for (var t = 0; t < seq.videoTracks.numTracks; t++) {
104
+ try { seq.videoTracks[t].setTargeted(${args.targeted}, true); count++; } catch(e) {}
105
+ }
106
+ }
107
+ if ("${trackType}" !== "video") {
108
+ for (var t = 0; t < seq.audioTracks.numTracks; t++) {
109
+ try { seq.audioTracks[t].setTargeted(${args.targeted}, false); count++; } catch(e) {}
110
+ }
111
+ }
112
+
113
+ return __result({ tracksAffected: count, targeted: ${args.targeted} });
114
+ `);
115
+ return sendCommand(script, bridgeOptions);
116
+ },
117
+ },
118
+ rename_track: {
119
+ description: "Rename a video or audio track.",
120
+ parameters: {
121
+ type: "object",
122
+ properties: {
123
+ track_type: {
124
+ type: "string",
125
+ enum: ["video", "audio"],
126
+ description: "Track type",
127
+ },
128
+ track_index: {
129
+ type: "number",
130
+ description: "Track index (0-based)",
131
+ },
132
+ name: {
133
+ type: "string",
134
+ description: "New track name",
135
+ },
136
+ },
137
+ required: ["track_type", "track_index", "name"],
138
+ },
139
+ handler: async (args) => {
140
+ const script = buildToolScript(`
141
+ var seq = app.project.activeSequence;
142
+ if (!seq) return __error("No active sequence");
143
+
144
+ var tracks = ${args.track_type === "video" ? "seq.videoTracks" : "seq.audioTracks"};
145
+ if (${args.track_index} >= tracks.numTracks) return __error("Track index out of range");
146
+
147
+ var track = tracks[${args.track_index}];
148
+ var oldName = track.name;
149
+ track.name = "${escapeForExtendScript(args.name)}";
150
+
151
+ return __result({ oldName: oldName, newName: track.name, trackType: "${args.track_type}", trackIndex: ${args.track_index} });
152
+ `);
153
+ return sendCommand(script, bridgeOptions);
154
+ },
155
+ },
156
+ get_track_info: {
157
+ description: "Get detailed information about a specific track: name, clip count, muted, locked, targeted, and list of all clips.",
158
+ parameters: {
159
+ type: "object",
160
+ properties: {
161
+ track_type: {
162
+ type: "string",
163
+ enum: ["video", "audio"],
164
+ description: "Track type",
165
+ },
166
+ track_index: {
167
+ type: "number",
168
+ description: "Track index (0-based)",
169
+ },
170
+ },
171
+ required: ["track_type", "track_index"],
172
+ },
173
+ handler: async (args) => {
174
+ const script = buildToolScript(`
175
+ var seq = app.project.activeSequence;
176
+ if (!seq) return __error("No active sequence");
177
+
178
+ var tracks = ${args.track_type === "video" ? "seq.videoTracks" : "seq.audioTracks"};
179
+ if (${args.track_index} >= tracks.numTracks) return __error("Track index out of range");
180
+
181
+ var track = tracks[${args.track_index}];
182
+ var info = {
183
+ name: track.name,
184
+ trackType: "${args.track_type}",
185
+ trackIndex: ${args.track_index},
186
+ clipCount: track.clips.numItems,
187
+ isMuted: track.isMuted(),
188
+ isLocked: track.isLocked()
189
+ };
190
+ try { info.isTargeted = track.isTargeted(); } catch(e) {}
191
+
192
+ info.clips = [];
193
+ for (var c = 0; c < track.clips.numItems; c++) {
194
+ var clip = track.clips[c];
195
+ var ci = {
196
+ index: c,
197
+ nodeId: clip.nodeId,
198
+ name: clip.name,
199
+ startSeconds: __ticksToSeconds(clip.start.ticks),
200
+ endSeconds: __ticksToSeconds(clip.end.ticks),
201
+ durationSeconds: __ticksToSeconds(clip.duration.ticks)
202
+ };
203
+ var clipDisabled = __readMaybe(clip, "disabled", null);
204
+ if (clipDisabled === null) clipDisabled = __readMaybe(clip, "isDisabled", false);
205
+ ci.enabled = !clipDisabled;
206
+ var clipSpeed = __readMaybe(clip, "getSpeed", null);
207
+ if (clipSpeed !== null) ci.speed = clipSpeed;
208
+ info.clips.push(ci);
209
+ }
210
+
211
+ info.transitions = [];
212
+ try {
213
+ for (var t = 0; t < track.transitions.numItems; t++) {
214
+ info.transitions.push({
215
+ index: t,
216
+ startSeconds: __ticksToSeconds(track.transitions[t].start.ticks),
217
+ endSeconds: __ticksToSeconds(track.transitions[t].end.ticks)
218
+ });
219
+ }
220
+ } catch(e) {}
221
+
222
+ return __result(info);
223
+ `);
224
+ return sendCommand(script, bridgeOptions);
225
+ },
226
+ },
227
+ razor_all_tracks: {
228
+ description: "Razor (split) all clips at the playhead position across all tracks, or at a specific time.",
229
+ parameters: {
230
+ type: "object",
231
+ properties: {
232
+ time_seconds: {
233
+ type: "number",
234
+ description: "Time to razor at in seconds (uses playhead position if omitted)",
235
+ },
236
+ track_type: {
237
+ type: "string",
238
+ enum: ["video", "audio", "both"],
239
+ description: "Which track types to razor (default: both)",
240
+ },
241
+ },
242
+ },
243
+ handler: async (args) => {
244
+ const trackType = args.track_type || "both";
245
+ const script = buildToolScript(`
246
+ app.enableQE();
247
+ var seq = app.project.activeSequence;
248
+ if (!seq) return __error("No active sequence");
249
+
250
+ var qeSeq = qe.project.getActiveSequence();
251
+ ${args.time_seconds !== undefined
252
+ ? `var ticks = __secondsToTicks(${args.time_seconds}).toString();`
253
+ : `var ticks = seq.getPlayerPosition().ticks;`}
254
+
255
+ var razored = 0;
256
+ if ("${trackType}" !== "audio") {
257
+ for (var t = 0; t < seq.videoTracks.numTracks; t++) {
258
+ try {
259
+ qeSeq.getVideoTrackAt(t).razor(ticks);
260
+ razored++;
261
+ } catch(e) {}
262
+ }
263
+ }
264
+ if ("${trackType}" !== "video") {
265
+ for (var t = 0; t < seq.audioTracks.numTracks; t++) {
266
+ try {
267
+ qeSeq.getAudioTrackAt(t).razor(ticks);
268
+ razored++;
269
+ } catch(e) {}
270
+ }
271
+ }
272
+
273
+ return __result({ razored: razored, atSeconds: __ticksToSeconds(ticks) });
274
+ `);
275
+ return sendCommand(script, bridgeOptions);
276
+ },
277
+ },
278
+ set_clip_start_time: {
279
+ description: "Set the start time (timecode offset) of a project item. This shifts where timecode begins for the source media.",
280
+ parameters: {
281
+ type: "object",
282
+ properties: {
283
+ item_id: {
284
+ type: "string",
285
+ description: "Node ID or name of the project item",
286
+ },
287
+ start_seconds: {
288
+ type: "number",
289
+ description: "New start time in seconds",
290
+ },
291
+ },
292
+ required: ["item_id", "start_seconds"],
293
+ },
294
+ handler: async (args) => {
295
+ const script = buildToolScript(`
296
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
297
+ if (!item) return __error("Item not found");
298
+
299
+ var t = new Time();
300
+ t.seconds = ${args.start_seconds};
301
+ item.setStartTime(t.ticks);
302
+
303
+ return __result({ item: item.name, startSeconds: ${args.start_seconds} });
304
+ `);
305
+ return sendCommand(script, bridgeOptions);
306
+ },
307
+ },
308
+ clear_item_in_out: {
309
+ description: "Clear in and/or out points on a project item (reset to full duration).",
310
+ parameters: {
311
+ type: "object",
312
+ properties: {
313
+ item_id: {
314
+ type: "string",
315
+ description: "Node ID or name of the project item",
316
+ },
317
+ clear_in: {
318
+ type: "boolean",
319
+ description: "Clear the in point (default: true)",
320
+ },
321
+ clear_out: {
322
+ type: "boolean",
323
+ description: "Clear the out point (default: true)",
324
+ },
325
+ },
326
+ required: ["item_id"],
327
+ },
328
+ handler: async (args) => {
329
+ const clearIn = args.clear_in !== false;
330
+ const clearOut = args.clear_out !== false;
331
+ const script = buildToolScript(`
332
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
333
+ if (!item) return __error("Item not found");
334
+
335
+ ${clearIn ? `item.clearInPoint();` : ""}
336
+ ${clearOut ? `item.clearOutPoint();` : ""}
337
+
338
+ return __result({ item: item.name, clearedIn: ${clearIn}, clearedOut: ${clearOut} });
339
+ `);
340
+ return sendCommand(script, bridgeOptions);
341
+ },
342
+ },
343
+ set_item_in_out: {
344
+ description: "Set in and/or out points on a project item in the project panel (marks source range for editing).",
345
+ parameters: {
346
+ type: "object",
347
+ properties: {
348
+ item_id: {
349
+ type: "string",
350
+ description: "Node ID or name of the project item",
351
+ },
352
+ in_seconds: {
353
+ type: "number",
354
+ description: "In point in seconds",
355
+ },
356
+ out_seconds: {
357
+ type: "number",
358
+ description: "Out point in seconds",
359
+ },
360
+ media_type: {
361
+ type: "number",
362
+ description: "Media type: 1=video, 2=audio, 4=all (default: 4)",
363
+ },
364
+ },
365
+ required: ["item_id"],
366
+ },
367
+ handler: async (args) => {
368
+ const mediaType = args.media_type ?? 4;
369
+ const script = buildToolScript(`
370
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
371
+ if (!item) return __error("Item not found");
372
+
373
+ ${args.in_seconds !== undefined ? `
374
+ var inTime = new Time();
375
+ inTime.seconds = ${args.in_seconds};
376
+ item.setInPoint(inTime.ticks, ${mediaType});
377
+ ` : ""}
378
+
379
+ ${args.out_seconds !== undefined ? `
380
+ var outTime = new Time();
381
+ outTime.seconds = ${args.out_seconds};
382
+ item.setOutPoint(outTime.ticks, ${mediaType});
383
+ ` : ""}
384
+
385
+ return __result({ item: item.name, inSet: ${args.in_seconds !== undefined}, outSet: ${args.out_seconds !== undefined} });
386
+ `);
387
+ return sendCommand(script, bridgeOptions);
388
+ },
389
+ },
390
+ import_image_sequence: {
391
+ description: "Import a numbered image sequence as a single video clip.",
392
+ parameters: {
393
+ type: "object",
394
+ properties: {
395
+ first_file_path: {
396
+ type: "string",
397
+ description: "Full path to the first image in the sequence (e.g., /path/to/frame_001.png)",
398
+ },
399
+ target_bin: {
400
+ type: "string",
401
+ description: "Target bin name to import into (optional, imports to root if omitted)",
402
+ },
403
+ },
404
+ required: ["first_file_path"],
405
+ },
406
+ handler: async (args) => {
407
+ const script = buildToolScript(`
408
+ var targetBin = app.project.rootItem;
409
+ ${args.target_bin ? `
410
+ var found = __findProjectItem("${escapeForExtendScript(args.target_bin)}");
411
+ if (found && found.type === 2) targetBin = found;
412
+ ` : ""}
413
+
414
+ app.project.importFiles(["${escapeForExtendScript(args.first_file_path)}"], true, targetBin, true);
415
+
416
+ return __result({ imported: true, file: "${escapeForExtendScript(args.first_file_path)}", asImageSequence: true });
417
+ `);
418
+ return sendCommand(script, bridgeOptions);
419
+ },
420
+ },
421
+ set_clip_position: {
422
+ description: "Set the Position property on a video clip's Motion effect. Values are in pixels.",
423
+ parameters: {
424
+ type: "object",
425
+ properties: {
426
+ node_id: {
427
+ type: "string",
428
+ description: "Node ID of the clip",
429
+ },
430
+ x: {
431
+ type: "number",
432
+ description: "X position in pixels",
433
+ },
434
+ y: {
435
+ type: "number",
436
+ description: "Y position in pixels",
437
+ },
438
+ },
439
+ required: ["node_id", "x", "y"],
440
+ },
441
+ handler: async (args) => {
442
+ const script = buildToolScript(`
443
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
444
+ if (!result) return __error("Clip not found");
445
+
446
+ var clip = result.clip;
447
+ var motion = __findIntrinsic(clip, "motion");
448
+ if (!motion) return __error("Motion component not found on clip");
449
+ var posProp = __findProp(motion, ["Position", "Posición"]);
450
+ if (!posProp) return __error("Position property not found in Motion");
451
+ posProp.setValue([${args.x}, ${args.y}], true);
452
+ return __result({ x: ${args.x}, y: ${args.y}, clip: clip.name });
453
+ `);
454
+ return sendCommand(script, bridgeOptions);
455
+ },
456
+ },
457
+ set_clip_scale: {
458
+ description: "Set the Scale property on a video clip's Motion effect.",
459
+ parameters: {
460
+ type: "object",
461
+ properties: {
462
+ node_id: {
463
+ type: "string",
464
+ description: "Node ID of the clip",
465
+ },
466
+ scale: {
467
+ type: "number",
468
+ description: "Scale value (100 = original size, 200 = 2x, 50 = half)",
469
+ },
470
+ },
471
+ required: ["node_id", "scale"],
472
+ },
473
+ handler: async (args) => {
474
+ const script = buildToolScript(`
475
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
476
+ if (!result) return __error("Clip not found");
477
+
478
+ var clip = result.clip;
479
+ var motion = __findIntrinsic(clip, "motion");
480
+ if (!motion) return __error("Motion component not found on clip");
481
+ var scaleProp = __findProp(motion, ["Scale", "Escala", "Skalierung", "Échelle"]);
482
+ if (!scaleProp) return __error("Scale property not found in Motion");
483
+ scaleProp.setValue(${args.scale}, true);
484
+ return __result({ scale: ${args.scale}, clip: clip.name });
485
+ `);
486
+ return sendCommand(script, bridgeOptions);
487
+ },
488
+ },
489
+ set_clip_rotation: {
490
+ description: "Set the Rotation property on a video clip's Motion effect.",
491
+ parameters: {
492
+ type: "object",
493
+ properties: {
494
+ node_id: {
495
+ type: "string",
496
+ description: "Node ID of the clip",
497
+ },
498
+ degrees: {
499
+ type: "number",
500
+ description: "Rotation in degrees (0-360, can exceed for multiple rotations)",
501
+ },
502
+ },
503
+ required: ["node_id", "degrees"],
504
+ },
505
+ handler: async (args) => {
506
+ const script = buildToolScript(`
507
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
508
+ if (!result) return __error("Clip not found");
509
+
510
+ var clip = result.clip;
511
+ var motion = __findIntrinsic(clip, "motion");
512
+ if (!motion) return __error("Motion component not found on clip");
513
+ var rotProp = __findProp(motion, ["Rotation", "Rotación", "Drehung"]);
514
+ if (!rotProp) return __error("Rotation property not found in Motion");
515
+ rotProp.setValue(${args.degrees}, true);
516
+ return __result({ degrees: ${args.degrees}, clip: clip.name });
517
+ `);
518
+ return sendCommand(script, bridgeOptions);
519
+ },
520
+ },
521
+ set_clip_anchor_point: {
522
+ description: "Set the Anchor Point property on a video clip's Motion effect.",
523
+ parameters: {
524
+ type: "object",
525
+ properties: {
526
+ node_id: {
527
+ type: "string",
528
+ description: "Node ID of the clip",
529
+ },
530
+ x: {
531
+ type: "number",
532
+ description: "Anchor point X in pixels",
533
+ },
534
+ y: {
535
+ type: "number",
536
+ description: "Anchor point Y in pixels",
537
+ },
538
+ },
539
+ required: ["node_id", "x", "y"],
540
+ },
541
+ handler: async (args) => {
542
+ const script = buildToolScript(`
543
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
544
+ if (!result) return __error("Clip not found");
545
+
546
+ var clip = result.clip;
547
+ var set = false;
548
+ for (var i = 0; i < clip.components.numItems; i++) {
549
+ if (clip.components[i].displayName === "Motion") {
550
+ for (var p = 0; p < clip.components[i].properties.numItems; p++) {
551
+ if (clip.components[i].properties[p].displayName === "Anchor Point") {
552
+ clip.components[i].properties[p].setValue([${args.x}, ${args.y}], true);
553
+ set = true;
554
+ break;
555
+ }
556
+ }
557
+ break;
558
+ }
559
+ }
560
+ if (!set) return __error("Could not set anchor point");
561
+ return __result({ x: ${args.x}, y: ${args.y}, clip: clip.name });
562
+ `);
563
+ return sendCommand(script, bridgeOptions);
564
+ },
565
+ },
566
+ set_clip_opacity: {
567
+ description: "Set the opacity of a video clip (0-100).",
568
+ parameters: {
569
+ type: "object",
570
+ properties: {
571
+ node_id: {
572
+ type: "string",
573
+ description: "Node ID of the clip",
574
+ },
575
+ opacity: {
576
+ type: "number",
577
+ description: "Opacity value (0 = transparent, 100 = fully opaque)",
578
+ },
579
+ },
580
+ required: ["node_id", "opacity"],
581
+ },
582
+ handler: async (args) => {
583
+ const script = buildToolScript(`
584
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
585
+ if (!result) return __error("Clip not found");
586
+
587
+ var clip = result.clip;
588
+ // Locale-resilient lookup via __findIntrinsic (matchName first, then
589
+ // localized displayName fallbacks). The Opacity property itself is
590
+ // also localized (Opacidad / Opazität / Opacité).
591
+ var opComp = __findIntrinsic(clip, "opacity");
592
+ if (!opComp) return __error("Opacity component not found on clip");
593
+ var opProp = __findProp(opComp, ["Opacity", "Opacidad", "Deckkraft", "Opacité"]);
594
+ if (!opProp) return __error("Opacity property not found in Opacity component");
595
+ opProp.setValue(${args.opacity}, true);
596
+ return __result({ opacity: ${args.opacity}, clip: clip.name });
597
+ `);
598
+ return sendCommand(script, bridgeOptions);
599
+ },
600
+ },
601
+ set_clip_volume: {
602
+ description: "Set the volume level on an audio clip (in dB).",
603
+ parameters: {
604
+ type: "object",
605
+ properties: {
606
+ node_id: {
607
+ type: "string",
608
+ description: "Node ID of the audio clip",
609
+ },
610
+ volume_db: {
611
+ type: "number",
612
+ description: "Volume in dB (0 = unity, negative = quieter, positive = louder)",
613
+ },
614
+ },
615
+ required: ["node_id", "volume_db"],
616
+ },
617
+ handler: async (args) => {
618
+ const script = buildToolScript(`
619
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
620
+ if (!result) return __error("Clip not found");
621
+
622
+ var clip = result.clip;
623
+ var set = false;
624
+ for (var i = 0; i < clip.components.numItems; i++) {
625
+ if (clip.components[i].displayName === "Volume") {
626
+ for (var p = 0; p < clip.components[i].properties.numItems; p++) {
627
+ if (clip.components[i].properties[p].displayName === "Level") {
628
+ clip.components[i].properties[p].setValue(${args.volume_db}, true);
629
+ set = true;
630
+ break;
631
+ }
632
+ }
633
+ break;
634
+ }
635
+ }
636
+ if (!set) return __error("Could not set volume - is this an audio clip?");
637
+ return __result({ volumeDb: ${args.volume_db}, clip: clip.name });
638
+ `);
639
+ return sendCommand(script, bridgeOptions);
640
+ },
641
+ },
642
+ set_clip_pan: {
643
+ description: "Set the pan (left/right balance) on an audio clip.",
644
+ parameters: {
645
+ type: "object",
646
+ properties: {
647
+ node_id: {
648
+ type: "string",
649
+ description: "Node ID of the audio clip",
650
+ },
651
+ pan: {
652
+ type: "number",
653
+ description: "Pan value (-100 = full left, 0 = center, 100 = full right)",
654
+ },
655
+ },
656
+ required: ["node_id", "pan"],
657
+ },
658
+ handler: async (args) => {
659
+ const script = buildToolScript(`
660
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
661
+ if (!result) return __error("Clip not found");
662
+
663
+ var clip = result.clip;
664
+ var set = false;
665
+ for (var i = 0; i < clip.components.numItems; i++) {
666
+ if (clip.components[i].displayName === "Panner") {
667
+ for (var p = 0; p < clip.components[i].properties.numItems; p++) {
668
+ if (clip.components[i].properties[p].displayName === "Balance" || clip.components[i].properties[p].displayName === "Pan") {
669
+ clip.components[i].properties[p].setValue(${args.pan}, true);
670
+ set = true;
671
+ break;
672
+ }
673
+ }
674
+ break;
675
+ }
676
+ }
677
+ if (!set) return __error("Could not set pan - is this an audio clip?");
678
+ return __result({ pan: ${args.pan}, clip: clip.name });
679
+ `);
680
+ return sendCommand(script, bridgeOptions);
681
+ },
682
+ },
683
+ batch_rename_clips: {
684
+ description: "Rename multiple clips on the timeline using a pattern. Supports sequential numbering.",
685
+ parameters: {
686
+ type: "object",
687
+ properties: {
688
+ pattern: {
689
+ type: "string",
690
+ description: "Name pattern. Use {n} for sequential number, {name} for original name (e.g., 'Scene_{n}', '{name}_v2')",
691
+ },
692
+ track_type: {
693
+ type: "string",
694
+ enum: ["video", "audio"],
695
+ description: "Track type to rename clips on",
696
+ },
697
+ track_index: {
698
+ type: "number",
699
+ description: "Track index (0-based)",
700
+ },
701
+ selected_only: {
702
+ type: "boolean",
703
+ description: "Only rename selected clips (default: false, renames all on track)",
704
+ },
705
+ start_number: {
706
+ type: "number",
707
+ description: "Starting number for {n} placeholder (default: 1)",
708
+ },
709
+ },
710
+ required: ["pattern", "track_type", "track_index"],
711
+ },
712
+ handler: async (args) => {
713
+ const startNum = args.start_number ?? 1;
714
+ const script = buildToolScript(`
715
+ app.enableQE();
716
+ var seq = app.project.activeSequence;
717
+ if (!seq) return __error("No active sequence");
718
+
719
+ var tracks = ${args.track_type === "video" ? "seq.videoTracks" : "seq.audioTracks"};
720
+ if (${args.track_index} >= tracks.numTracks) return __error("Track index out of range");
721
+
722
+ var qeSeq = qe.project.getActiveSequence();
723
+ var qeTrack = ${args.track_type === "video"
724
+ ? `qeSeq.getVideoTrackAt(${args.track_index})`
725
+ : `qeSeq.getAudioTrackAt(${args.track_index})`};
726
+
727
+ var track = tracks[${args.track_index}];
728
+ var renamed = 0;
729
+ var num = ${startNum};
730
+ var pattern = "${escapeForExtendScript(args.pattern)}";
731
+
732
+ for (var c = 0; c < track.clips.numItems; c++) {
733
+ var clip = track.clips[c];
734
+ ${args.selected_only ? `if (!clip.isSelected()) continue;` : ""}
735
+
736
+ var newName = pattern.split("{n}").join("" + num).split("{name}").join(clip.name);
737
+ try {
738
+ var qeClip = qeTrack.getItemAt(c);
739
+ qeClip.setName(newName);
740
+ renamed++;
741
+ } catch(e) {}
742
+ num++;
743
+ }
744
+
745
+ return __result({ renamed: renamed, pattern: pattern });
746
+ `);
747
+ return sendCommand(script, bridgeOptions);
748
+ },
749
+ },
750
+ batch_enable_disable: {
751
+ description: "Enable or disable multiple clips at once (selected, track, or all).",
752
+ parameters: {
753
+ type: "object",
754
+ properties: {
755
+ enabled: {
756
+ type: "boolean",
757
+ description: "true to enable, false to disable",
758
+ },
759
+ target: {
760
+ type: "string",
761
+ enum: ["selected", "track", "all"],
762
+ description: "Which clips to affect",
763
+ },
764
+ track_type: {
765
+ type: "string",
766
+ enum: ["video", "audio"],
767
+ description: "Track type (required when target is 'track')",
768
+ },
769
+ track_index: {
770
+ type: "number",
771
+ description: "Track index (required when target is 'track')",
772
+ },
773
+ },
774
+ required: ["enabled", "target"],
775
+ },
776
+ handler: async (args) => {
777
+ const script = buildToolScript(`
778
+ var seq = app.project.activeSequence;
779
+ if (!seq) return __error("No active sequence");
780
+
781
+ var count = 0;
782
+ var disabled = ${!args.enabled};
783
+
784
+ function setOnTrack(track) {
785
+ for (var c = 0; c < track.clips.numItems; c++) {
786
+ try {
787
+ if ("${args.target}" === "selected" && !track.clips[c].isSelected()) continue;
788
+ track.clips[c].setDisabled(disabled);
789
+ count++;
790
+ } catch(e) {}
791
+ }
792
+ }
793
+
794
+ if ("${args.target}" === "track") {
795
+ var tracks = ${(args.track_type || "video") === "video" ? "seq.videoTracks" : "seq.audioTracks"};
796
+ if (${args.track_index ?? 0} >= tracks.numTracks) return __error("Track index out of range");
797
+ setOnTrack(tracks[${args.track_index ?? 0}]);
798
+ } else {
799
+ for (var t = 0; t < seq.videoTracks.numTracks; t++) setOnTrack(seq.videoTracks[t]);
800
+ for (var t = 0; t < seq.audioTracks.numTracks; t++) setOnTrack(seq.audioTracks[t]);
801
+ }
802
+
803
+ return __result({ affected: count, enabled: ${args.enabled} });
804
+ `);
805
+ return sendCommand(script, bridgeOptions);
806
+ },
807
+ },
808
+ remove_selected_clips: {
809
+ description: "Remove all currently selected clips from the timeline.",
810
+ parameters: {
811
+ type: "object",
812
+ properties: {
813
+ ripple: {
814
+ type: "boolean",
815
+ description: "If true, close the gap after removing (ripple delete). Default: false",
816
+ },
817
+ },
818
+ },
819
+ handler: async (args) => {
820
+ const script = buildToolScript(`
821
+ var seq = app.project.activeSequence;
822
+ if (!seq) return __error("No active sequence");
823
+
824
+ var removed = 0;
825
+ var toRemove = [];
826
+
827
+ function collect(tracks) {
828
+ for (var t = 0; t < tracks.numTracks; t++) {
829
+ for (var c = tracks[t].clips.numItems - 1; c >= 0; c--) {
830
+ if (tracks[t].clips[c].isSelected()) {
831
+ toRemove.push(tracks[t].clips[c]);
832
+ }
833
+ }
834
+ }
835
+ }
836
+ collect(seq.videoTracks);
837
+ collect(seq.audioTracks);
838
+
839
+ for (var i = 0; i < toRemove.length; i++) {
840
+ try {
841
+ toRemove[i].remove(${args.ripple ? "true" : "false"}, true);
842
+ removed++;
843
+ } catch(e) {}
844
+ }
845
+
846
+ return __result({ removed: removed, ripple: ${args.ripple ? "true" : "false"} });
847
+ `);
848
+ return sendCommand(script, bridgeOptions);
849
+ },
850
+ },
851
+ clear_sequence_in_out: {
852
+ description: "Clear the in and/or out points on the active sequence.",
853
+ parameters: {
854
+ type: "object",
855
+ properties: {
856
+ clear_in: {
857
+ type: "boolean",
858
+ description: "Clear in point (default: true)",
859
+ },
860
+ clear_out: {
861
+ type: "boolean",
862
+ description: "Clear out point (default: true)",
863
+ },
864
+ },
865
+ },
866
+ handler: async (args) => {
867
+ const clearIn = args.clear_in !== false;
868
+ const clearOut = args.clear_out !== false;
869
+ const script = buildToolScript(`
870
+ var seq = app.project.activeSequence;
871
+ if (!seq) return __error("No active sequence");
872
+
873
+ var clearIn = ${clearIn};
874
+ var clearOut = ${clearOut};
875
+ var inDone = false;
876
+ var outDone = false;
877
+ var usedDedicated = false;
878
+ var errMsg = null;
879
+
880
+ // Prefer a dedicated clear method if it exists (clears both at once).
881
+ if (clearIn && clearOut) {
882
+ try {
883
+ if (typeof seq.clearInOutPoints === "function") {
884
+ seq.clearInOutPoints();
885
+ usedDedicated = true;
886
+ inDone = true;
887
+ outDone = true;
888
+ }
889
+ } catch(eClear) { errMsg = "clearInOutPoints: " + eClear.toString(); usedDedicated = false; }
890
+ }
891
+
892
+ if (!usedDedicated) {
893
+ // setInPoint/setOutPoint expect ticks as a STRING. Resolve both as
894
+ // ticks strings so the two calls are consistent.
895
+ var zeroTicks = "0";
896
+ try {
897
+ if (seq.zeroPoint && seq.zeroPoint.ticks) zeroTicks = seq.zeroPoint.ticks.toString();
898
+ } catch(eZero) {}
899
+
900
+ var endTicks = null;
901
+ try {
902
+ if (seq.end && seq.end.ticks) endTicks = seq.end.ticks.toString();
903
+ } catch(eEnd) {}
904
+
905
+ if (clearIn) {
906
+ try {
907
+ seq.setInPoint(zeroTicks);
908
+ inDone = true;
909
+ } catch(eIn) { errMsg = (errMsg ? errMsg + "; " : "") + "in: " + eIn.toString(); }
910
+ }
911
+
912
+ if (clearOut && endTicks !== null) {
913
+ try {
914
+ seq.setOutPoint(endTicks);
915
+ outDone = true;
916
+ } catch(eOut) { errMsg = (errMsg ? errMsg + "; " : "") + "out: " + eOut.toString(); }
917
+ }
918
+ }
919
+
920
+ return __result({
921
+ success: ((!clearIn || inDone) && (!clearOut || outDone)),
922
+ clearedIn: inDone,
923
+ clearedOut: outDone,
924
+ usedDedicatedMethod: usedDedicated,
925
+ error: errMsg
926
+ });
927
+ `);
928
+ return sendCommand(script, bridgeOptions);
929
+ },
930
+ },
931
+ get_encoder_presets: {
932
+ description: "List available Adobe Media Encoder export presets for a given format.",
933
+ parameters: {
934
+ type: "object",
935
+ properties: {
936
+ format: {
937
+ type: "string",
938
+ description: "Format name to list presets for (e.g., 'H.264', 'QuickTime', 'MPEG2'). Leave empty to list formats.",
939
+ },
940
+ },
941
+ },
942
+ handler: async (args) => {
943
+ const script = buildToolScript(`
944
+ var encoder = app.encoder;
945
+ if (!encoder) return __error("Encoder not available");
946
+
947
+ try {
948
+ var formats = encoder.getFormatList();
949
+ if (!formats) return __error("Could not get format list");
950
+
951
+ var result = [];
952
+ for (var f = 0; f < formats.length; f++) {
953
+ var formatName = formats[f];
954
+ ${args.format ? `if (formatName.toLowerCase().indexOf("${escapeForExtendScript(args.format)}".toLowerCase()) === -1) continue;` : ""}
955
+ var presets = encoder.getPresetList(formatName);
956
+ var presetNames = [];
957
+ if (presets) {
958
+ for (var p = 0; p < presets.length; p++) {
959
+ presetNames.push(presets[p]);
960
+ }
961
+ }
962
+ result.push({ format: formatName, presets: presetNames });
963
+ }
964
+ return __result(result);
965
+ } catch(e) {
966
+ return __error("Encoder preset listing failed: " + e.message);
967
+ }
968
+ `);
969
+ return sendCommand(script, bridgeOptions);
970
+ },
971
+ },
972
+ get_qe_clip_info: {
973
+ description: "Get QE DOM information about a clip, including properties not available through the standard API.",
974
+ parameters: {
975
+ type: "object",
976
+ properties: {
977
+ track_type: {
978
+ type: "string",
979
+ enum: ["video", "audio"],
980
+ description: "Track type",
981
+ },
982
+ track_index: {
983
+ type: "number",
984
+ description: "Track index (0-based)",
985
+ },
986
+ clip_index: {
987
+ type: "number",
988
+ description: "Clip index on the track (0-based)",
989
+ },
990
+ },
991
+ required: ["track_type", "track_index", "clip_index"],
992
+ },
993
+ handler: async (args) => {
994
+ const script = buildToolScript(`
995
+ app.enableQE();
996
+ var qeSeq = qe.project.getActiveSequence();
997
+ if (!qeSeq) return __error("No active sequence");
998
+
999
+ var qeTrack = ${args.track_type === "video"
1000
+ ? `qeSeq.getVideoTrackAt(${args.track_index})`
1001
+ : `qeSeq.getAudioTrackAt(${args.track_index})`};
1002
+ if (!qeTrack) return __error("Track not found");
1003
+
1004
+ var qeClip = qeTrack.getItemAt(${args.clip_index});
1005
+ if (!qeClip) return __error("Clip not found at index ${args.clip_index}");
1006
+
1007
+ var info = { trackType: "${args.track_type}", trackIndex: ${args.track_index}, clipIndex: ${args.clip_index} };
1008
+
1009
+ // Try to read all QE clip properties
1010
+ var props = ["name", "type", "mediaType", "duration", "start", "end", "inPoint", "outPoint",
1011
+ "speed", "audioChannelType", "numAudioChannels"];
1012
+ for (var i = 0; i < props.length; i++) {
1013
+ try { info[props[i]] = qeClip[props[i]]; } catch(e) {}
1014
+ }
1015
+
1016
+ // Enumerate any additional properties
1017
+ var extra = [];
1018
+ try {
1019
+ for (var key in qeClip) {
1020
+ if (typeof qeClip[key] !== "function") {
1021
+ extra.push(key);
1022
+ }
1023
+ }
1024
+ } catch(e) {}
1025
+ info.availableProperties = extra;
1026
+
1027
+ // Enumerate methods
1028
+ var methods = [];
1029
+ try {
1030
+ for (var key in qeClip) {
1031
+ if (typeof qeClip[key] === "function") {
1032
+ methods.push(key);
1033
+ }
1034
+ }
1035
+ } catch(e) {}
1036
+ info.availableMethods = methods;
1037
+
1038
+ return __result(info);
1039
+ `);
1040
+ return sendCommand(script, bridgeOptions);
1041
+ },
1042
+ },
1043
+ redo: {
1044
+ description: "Redo the last undone action in Premiere Pro.",
1045
+ parameters: {},
1046
+ handler: async () => {
1047
+ const script = buildToolScript(`
1048
+ app.enableQE();
1049
+ try {
1050
+ qe.project.redo();
1051
+ return __result({ redone: true });
1052
+ } catch(e) {
1053
+ return __error("Redo failed: " + e.message);
1054
+ }
1055
+ `);
1056
+ return sendCommand(script, bridgeOptions);
1057
+ },
1058
+ },
1059
+ multiple_undo: {
1060
+ description: "Undo multiple steps at once.",
1061
+ parameters: {
1062
+ type: "object",
1063
+ properties: {
1064
+ count: {
1065
+ type: "number",
1066
+ description: "Number of undo steps (default: 1)",
1067
+ },
1068
+ },
1069
+ },
1070
+ handler: async (args) => {
1071
+ const count = args.count ?? 1;
1072
+ const script = buildToolScript(`
1073
+ var undone = 0;
1074
+ for (var i = 0; i < ${count}; i++) {
1075
+ try {
1076
+ app.project.undo();
1077
+ undone++;
1078
+ } catch(e) { break; }
1079
+ }
1080
+ return __result({ undone: undone });
1081
+ `);
1082
+ return sendCommand(script, bridgeOptions);
1083
+ },
1084
+ },
1085
+ set_poster_frame: {
1086
+ description: "Set the poster frame (thumbnail) for a project item at a specific time.",
1087
+ parameters: {
1088
+ type: "object",
1089
+ properties: {
1090
+ item_id: {
1091
+ type: "string",
1092
+ description: "Node ID or name of the project item",
1093
+ },
1094
+ time_seconds: {
1095
+ type: "number",
1096
+ description: "Time in seconds for the poster frame",
1097
+ },
1098
+ },
1099
+ required: ["item_id", "time_seconds"],
1100
+ },
1101
+ handler: async (args) => {
1102
+ const script = buildToolScript(`
1103
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
1104
+ if (!item) {
1105
+ // Fall back to treating item_id as a timeline clip node id
1106
+ var res = __findClip("${escapeForExtendScript(args.item_id)}");
1107
+ if (res && res.clip && res.clip.projectItem) item = res.clip.projectItem;
1108
+ }
1109
+ if (!item) return __error("Item not found");
1110
+
1111
+ if (typeof item.setPosterFrame !== "function") {
1112
+ return __error("setPosterFrame is not available on this project item in this Premiere Pro version");
1113
+ }
1114
+
1115
+ try {
1116
+ var posterTicks = __secondsToTicks(${args.time_seconds}).toString();
1117
+ item.setPosterFrame(posterTicks);
1118
+ return __result({ item: item.name, timeSeconds: ${args.time_seconds}, posterTicks: posterTicks });
1119
+ } catch(e) {
1120
+ return __error("Failed to set poster frame: " + e.message);
1121
+ }
1122
+ `);
1123
+ return sendCommand(script, bridgeOptions);
1124
+ },
1125
+ },
1126
+ get_version_info: {
1127
+ description: "Get Premiere Pro version and build information.",
1128
+ parameters: {},
1129
+ handler: async () => {
1130
+ const script = buildToolScript(`
1131
+ var info = {
1132
+ version: app.version,
1133
+ buildNumber: app.build
1134
+ };
1135
+ try { info.isDocumentOpen = app.isDocumentOpen(); } catch(e) {}
1136
+ try { info.path = app.path; } catch(e) {}
1137
+ return __result(info);
1138
+ `);
1139
+ return sendCommand(script, bridgeOptions);
1140
+ },
1141
+ },
1142
+ move_items_to_bin: {
1143
+ description: "Move multiple project items to a target bin at once.",
1144
+ parameters: {
1145
+ type: "object",
1146
+ properties: {
1147
+ item_ids: {
1148
+ type: "array",
1149
+ description: "Array of node IDs or names of items to move",
1150
+ },
1151
+ target_bin: {
1152
+ type: "string",
1153
+ description: "Name or node ID of the target bin",
1154
+ },
1155
+ },
1156
+ required: ["item_ids", "target_bin"],
1157
+ },
1158
+ handler: async (args) => {
1159
+ const idsJson = JSON.stringify(args.item_ids);
1160
+ const script = buildToolScript(`
1161
+ var targetBin = __findProjectItem("${escapeForExtendScript(args.target_bin)}");
1162
+ if (!targetBin || targetBin.type !== 2) return __error("Target bin not found: ${escapeForExtendScript(args.target_bin)}");
1163
+
1164
+ var ids = ${idsJson};
1165
+ var moved = 0;
1166
+ for (var i = 0; i < ids.length; i++) {
1167
+ var item = __findProjectItem(ids[i]);
1168
+ if (item) {
1169
+ try {
1170
+ item.moveBin(targetBin);
1171
+ moved++;
1172
+ } catch(e) {}
1173
+ }
1174
+ }
1175
+
1176
+ return __result({ moved: moved, total: ids.length, targetBin: targetBin.name });
1177
+ `);
1178
+ return sendCommand(script, bridgeOptions);
1179
+ },
1180
+ },
1181
+ set_anti_alias_quality: {
1182
+ description: "Set the anti-alias quality on a clip's Motion effect (useful for scaled/rotated clips).",
1183
+ parameters: {
1184
+ type: "object",
1185
+ properties: {
1186
+ node_id: {
1187
+ type: "string",
1188
+ description: "Node ID of the clip",
1189
+ },
1190
+ enabled: {
1191
+ type: "boolean",
1192
+ description: "Enable (true) or disable (false) anti-aliasing",
1193
+ },
1194
+ },
1195
+ required: ["node_id", "enabled"],
1196
+ },
1197
+ handler: async (args) => {
1198
+ const script = buildToolScript(`
1199
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
1200
+ if (!result) return __error("Clip not found");
1201
+
1202
+ var clip = result.clip;
1203
+ var set = false;
1204
+ for (var i = 0; i < clip.components.numItems; i++) {
1205
+ if (clip.components[i].displayName === "Motion") {
1206
+ for (var p = 0; p < clip.components[i].properties.numItems; p++) {
1207
+ var pName = clip.components[i].properties[p].displayName;
1208
+ if (pName === "Anti-flicker Filter" || pName === "Use Composition's Shutter Angle") {
1209
+ clip.components[i].properties[p].setValue(${args.enabled}, true);
1210
+ set = true;
1211
+ break;
1212
+ }
1213
+ }
1214
+ break;
1215
+ }
1216
+ }
1217
+
1218
+ return __result({ clip: clip.name, antiAlias: ${args.enabled}, propertyFound: set });
1219
+ `);
1220
+ return sendCommand(script, bridgeOptions);
1221
+ },
1222
+ },
1223
+ set_uniform_scale: {
1224
+ description: "Toggle uniform scale on a clip's Motion effect. When enabled, Scale Width and Scale Height are linked.",
1225
+ parameters: {
1226
+ type: "object",
1227
+ properties: {
1228
+ node_id: {
1229
+ type: "string",
1230
+ description: "Node ID of the clip",
1231
+ },
1232
+ uniform: {
1233
+ type: "boolean",
1234
+ description: "true for uniform (linked), false for non-uniform (independent width/height)",
1235
+ },
1236
+ },
1237
+ required: ["node_id", "uniform"],
1238
+ },
1239
+ handler: async (args) => {
1240
+ const script = buildToolScript(`
1241
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
1242
+ if (!result) return __error("Clip not found");
1243
+
1244
+ var clip = result.clip;
1245
+ var set = false;
1246
+ for (var i = 0; i < clip.components.numItems; i++) {
1247
+ if (clip.components[i].displayName === "Motion") {
1248
+ for (var p = 0; p < clip.components[i].properties.numItems; p++) {
1249
+ if (clip.components[i].properties[p].displayName === "Uniform Scale") {
1250
+ clip.components[i].properties[p].setValue(${args.uniform}, true);
1251
+ set = true;
1252
+ break;
1253
+ }
1254
+ }
1255
+ break;
1256
+ }
1257
+ }
1258
+ if (!set) return __error("Uniform Scale property not found");
1259
+ return __result({ clip: clip.name, uniformScale: ${args.uniform} });
1260
+ `);
1261
+ return sendCommand(script, bridgeOptions);
1262
+ },
1263
+ },
1264
+ set_scale_width_height: {
1265
+ description: "Set independent Scale Width and Scale Height on a clip (requires Uniform Scale to be OFF).",
1266
+ parameters: {
1267
+ type: "object",
1268
+ properties: {
1269
+ node_id: {
1270
+ type: "string",
1271
+ description: "Node ID of the clip",
1272
+ },
1273
+ scale_width: {
1274
+ type: "number",
1275
+ description: "Scale width percentage",
1276
+ },
1277
+ scale_height: {
1278
+ type: "number",
1279
+ description: "Scale height percentage",
1280
+ },
1281
+ },
1282
+ required: ["node_id", "scale_width", "scale_height"],
1283
+ },
1284
+ handler: async (args) => {
1285
+ const script = buildToolScript(`
1286
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
1287
+ if (!result) return __error("Clip not found");
1288
+
1289
+ var clip = result.clip;
1290
+ var motion = null;
1291
+ for (var i = 0; i < clip.components.numItems; i++) {
1292
+ if (clip.components[i].displayName === "Motion") { motion = clip.components[i]; break; }
1293
+ }
1294
+ if (!motion) return __error("Motion component not found");
1295
+
1296
+ // Disable uniform scale first
1297
+ for (var p = 0; p < motion.properties.numItems; p++) {
1298
+ if (motion.properties[p].displayName === "Uniform Scale") {
1299
+ motion.properties[p].setValue(false, true);
1300
+ break;
1301
+ }
1302
+ }
1303
+
1304
+ var setW = false, setH = false;
1305
+ for (var p = 0; p < motion.properties.numItems; p++) {
1306
+ var pName = motion.properties[p].displayName;
1307
+ if (pName === "Scale Width") { motion.properties[p].setValue(${args.scale_width}, true); setW = true; }
1308
+ if (pName === "Scale Height") { motion.properties[p].setValue(${args.scale_height}, true); setH = true; }
1309
+ }
1310
+
1311
+ return __result({ clip: clip.name, scaleWidth: ${args.scale_width}, scaleHeight: ${args.scale_height}, widthSet: setW, heightSet: setH });
1312
+ `);
1313
+ return sendCommand(script, bridgeOptions);
1314
+ },
1315
+ },
1316
+ };
1317
+ }
1318
+ //# sourceMappingURL=track-targeting.js.map