@synthetic-ai/premiere-mcp 2.1.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 (58) hide show
  1. package/CHANGELOG.md +135 -123
  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/index.js +103 -4
  12. package/dist/license.js +38 -0
  13. package/dist/server.js +60 -60
  14. package/dist/tools/advanced.js +357 -357
  15. package/dist/tools/audio-advanced.js +1049 -1049
  16. package/dist/tools/audio.js +42 -42
  17. package/dist/tools/captions.js +27 -27
  18. package/dist/tools/clipboard.js +214 -214
  19. package/dist/tools/color-advanced.js +253 -253
  20. package/dist/tools/discovery.js +291 -291
  21. package/dist/tools/effects.js +285 -285
  22. package/dist/tools/export-presets.js +206 -206
  23. package/dist/tools/export.js +255 -255
  24. package/dist/tools/health.js +10 -10
  25. package/dist/tools/inspection.js +885 -885
  26. package/dist/tools/keyframes.js +369 -369
  27. package/dist/tools/markers.js +89 -89
  28. package/dist/tools/media.js +178 -178
  29. package/dist/tools/metadata.js +94 -94
  30. package/dist/tools/multicam.js +50 -50
  31. package/dist/tools/playback.js +15 -15
  32. package/dist/tools/playhead.js +95 -95
  33. package/dist/tools/project-manager.js +21 -21
  34. package/dist/tools/project.js +149 -149
  35. package/dist/tools/scripting.js +319 -319
  36. package/dist/tools/selection.js +202 -202
  37. package/dist/tools/sequence-advanced.js +379 -379
  38. package/dist/tools/sequence-settings.js +564 -564
  39. package/dist/tools/sequence.js +167 -167
  40. package/dist/tools/source-monitor.js +68 -68
  41. package/dist/tools/text.js +58 -58
  42. package/dist/tools/timeline.js +312 -312
  43. package/dist/tools/track-management.js +446 -446
  44. package/dist/tools/track-targeting.js +635 -635
  45. package/dist/tools/tracks.js +87 -87
  46. package/dist/tools/transitions-motion.js +174 -174
  47. package/dist/tools/transitions.js +111 -111
  48. package/dist/tools/utility.js +606 -606
  49. package/dist/tools/workspace.js +29 -29
  50. package/package.json +66 -66
  51. package/scripts/build-chat-dmg.sh +210 -210
  52. package/scripts/build-chat-zip.sh +291 -291
  53. package/scripts/install-cep-windows.ps1 +99 -99
  54. package/scripts/install-cep.sh +66 -66
  55. package/scripts/install-chat-plugin.bat +67 -67
  56. package/scripts/install-chat-plugin.sh +85 -85
  57. package/scripts/install-windows.bat +95 -95
  58. package/scripts/package.mjs +254 -254
@@ -15,13 +15,13 @@ export function getUtilityTools(bridgeOptions) {
15
15
  required: ["item_id"],
16
16
  },
17
17
  handler: async (args) => {
18
- const script = buildToolScript(`
19
- var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
20
- if (!item) return __error("Item not found: ${escapeForExtendScript(args.item_id)}");
21
-
22
- var name = item.name;
23
- app.project.deleteProjectItem(item);
24
- return __result({ deleted: true, name: name });
18
+ const script = buildToolScript(`
19
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
20
+ if (!item) return __error("Item not found: ${escapeForExtendScript(args.item_id)}");
21
+
22
+ var name = item.name;
23
+ app.project.deleteProjectItem(item);
24
+ return __result({ deleted: true, name: name });
25
25
  `);
26
26
  return sendCommand(script, bridgeOptions);
27
27
  },
@@ -40,18 +40,18 @@ export function getUtilityTools(bridgeOptions) {
40
40
  },
41
41
  handler: async (args) => {
42
42
  const idsJson = JSON.stringify(args.item_ids);
43
- const script = buildToolScript(`
44
- var ids = ${idsJson};
45
- var deleted = 0;
46
- var names = [];
47
- for (var i = 0; i < ids.length; i++) {
48
- var item = __findProjectItem(ids[i]);
49
- if (item) {
50
- names.push(item.name);
51
- try { app.project.deleteProjectItem(item); deleted++; } catch(e) {}
52
- }
53
- }
54
- return __result({ deleted: deleted, total: ids.length, names: names });
43
+ const script = buildToolScript(`
44
+ var ids = ${idsJson};
45
+ var deleted = 0;
46
+ var names = [];
47
+ for (var i = 0; i < ids.length; i++) {
48
+ var item = __findProjectItem(ids[i]);
49
+ if (item) {
50
+ names.push(item.name);
51
+ try { app.project.deleteProjectItem(item); deleted++; } catch(e) {}
52
+ }
53
+ }
54
+ return __result({ deleted: deleted, total: ids.length, names: names });
55
55
  `);
56
56
  return sendCommand(script, bridgeOptions);
57
57
  },
@@ -73,13 +73,13 @@ export function getUtilityTools(bridgeOptions) {
73
73
  required: ["item_id", "new_name"],
74
74
  },
75
75
  handler: async (args) => {
76
- const script = buildToolScript(`
77
- var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
78
- if (!item) return __error("Item not found");
79
-
80
- var oldName = item.name;
81
- item.name = "${escapeForExtendScript(args.new_name)}";
82
- return __result({ oldName: oldName, newName: item.name });
76
+ const script = buildToolScript(`
77
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
78
+ if (!item) return __error("Item not found");
79
+
80
+ var oldName = item.name;
81
+ item.name = "${escapeForExtendScript(args.new_name)}";
82
+ return __result({ oldName: oldName, newName: item.name });
83
83
  `);
84
84
  return sendCommand(script, bridgeOptions);
85
85
  },
@@ -97,22 +97,22 @@ export function getUtilityTools(bridgeOptions) {
97
97
  },
98
98
  handler: async (args) => {
99
99
  const track = args.track_index ?? 0;
100
- const script = buildToolScript(`
101
- var seq = app.project.activeSequence;
102
- if (!seq) return __error("No active sequence");
103
-
104
- app.enableQE();
105
- try {
106
- var qeSeq = qe.project.getActiveSequence();
107
- qeSeq.addAdjustmentLayer(${track});
108
-
109
- return __result({
110
- added: true,
111
- trackIndex: ${track}
112
- });
113
- } catch(e) {
114
- return __error("Failed to add adjustment layer: " + e.message);
115
- }
100
+ const script = buildToolScript(`
101
+ var seq = app.project.activeSequence;
102
+ if (!seq) return __error("No active sequence");
103
+
104
+ app.enableQE();
105
+ try {
106
+ var qeSeq = qe.project.getActiveSequence();
107
+ qeSeq.addAdjustmentLayer(${track});
108
+
109
+ return __result({
110
+ added: true,
111
+ trackIndex: ${track}
112
+ });
113
+ } catch(e) {
114
+ return __error("Failed to add adjustment layer: " + e.message);
115
+ }
116
116
  `);
117
117
  return sendCommand(script, bridgeOptions);
118
118
  },
@@ -138,26 +138,26 @@ export function getUtilityTools(bridgeOptions) {
138
138
  required: ["output_path"],
139
139
  },
140
140
  handler: async (args) => {
141
- const script = buildToolScript(`
142
- var seq = app.project.activeSequence;
143
- if (!seq) return __error("No active sequence");
144
-
141
+ const script = buildToolScript(`
142
+ var seq = app.project.activeSequence;
143
+ if (!seq) return __error("No active sequence");
144
+
145
145
  ${args.time_seconds !== undefined
146
146
  ? `var timeTicks = __secondsToTicks(${args.time_seconds}).toString();`
147
- : `var timeTicks = seq.getPlayerPosition().ticks;`}
148
-
149
- // Export frame
150
- seq.exportFramePNG(timeTicks, "${escapeForExtendScript(args.output_path)}");
151
-
152
- // Import back
153
- app.project.importFiles(["${escapeForExtendScript(args.output_path)}"], false, app.project.rootItem, false);
154
-
155
- return __result({
156
- exported: true,
157
- path: "${escapeForExtendScript(args.output_path)}",
158
- atSeconds: __ticksToSeconds(timeTicks),
159
- note: "Frame exported and imported. Add to timeline with add_to_timeline."
160
- });
147
+ : `var timeTicks = seq.getPlayerPosition().ticks;`}
148
+
149
+ // Export frame
150
+ seq.exportFramePNG(timeTicks, "${escapeForExtendScript(args.output_path)}");
151
+
152
+ // Import back
153
+ app.project.importFiles(["${escapeForExtendScript(args.output_path)}"], false, app.project.rootItem, false);
154
+
155
+ return __result({
156
+ exported: true,
157
+ path: "${escapeForExtendScript(args.output_path)}",
158
+ atSeconds: __ticksToSeconds(timeTicks),
159
+ note: "Frame exported and imported. Add to timeline with add_to_timeline."
160
+ });
161
161
  `);
162
162
  return sendCommand(script, bridgeOptions);
163
163
  },
@@ -175,26 +175,26 @@ export function getUtilityTools(bridgeOptions) {
175
175
  required: ["frame_rate"],
176
176
  },
177
177
  handler: async (args) => {
178
- const script = buildToolScript(`
179
- var seq = app.project.activeSequence;
180
- if (!seq) return __error("No active sequence");
181
-
182
- var settings = seq.getSettings();
183
- if (!settings) return __error("Could not get sequence settings");
184
-
185
- var fps = ${args.frame_rate};
186
- if (!fps || fps <= 0) return __error("Invalid frame rate: " + fps);
187
-
188
- try {
189
- var t = new Time();
190
- t.ticks = Math.round(TICKS_PER_SECOND / fps).toString();
191
- settings.videoFrameRate = t;
192
- seq.setSettings(settings);
193
- } catch(e) {
194
- return __error("Failed to set frame rate: " + e.message);
195
- }
196
-
197
- return __result({ frameRate: fps, ticksPerFrame: Math.round(TICKS_PER_SECOND / fps), sequence: seq.name });
178
+ const script = buildToolScript(`
179
+ var seq = app.project.activeSequence;
180
+ if (!seq) return __error("No active sequence");
181
+
182
+ var settings = seq.getSettings();
183
+ if (!settings) return __error("Could not get sequence settings");
184
+
185
+ var fps = ${args.frame_rate};
186
+ if (!fps || fps <= 0) return __error("Invalid frame rate: " + fps);
187
+
188
+ try {
189
+ var t = new Time();
190
+ t.ticks = Math.round(TICKS_PER_SECOND / fps).toString();
191
+ settings.videoFrameRate = t;
192
+ seq.setSettings(settings);
193
+ } catch(e) {
194
+ return __error("Failed to set frame rate: " + e.message);
195
+ }
196
+
197
+ return __result({ frameRate: fps, ticksPerFrame: Math.round(TICKS_PER_SECOND / fps), sequence: seq.name });
198
198
  `);
199
199
  return sendCommand(script, bridgeOptions);
200
200
  },
@@ -216,18 +216,18 @@ export function getUtilityTools(bridgeOptions) {
216
216
  required: ["width", "height"],
217
217
  },
218
218
  handler: async (args) => {
219
- const script = buildToolScript(`
220
- var seq = app.project.activeSequence;
221
- if (!seq) return __error("No active sequence");
222
-
223
- var settings = seq.getSettings();
224
- if (!settings) return __error("Could not get sequence settings");
225
-
226
- settings.videoFrameWidth = ${args.width};
227
- settings.videoFrameHeight = ${args.height};
228
- seq.setSettings(settings);
229
-
230
- return __result({ width: ${args.width}, height: ${args.height}, sequence: seq.name });
219
+ const script = buildToolScript(`
220
+ var seq = app.project.activeSequence;
221
+ if (!seq) return __error("No active sequence");
222
+
223
+ var settings = seq.getSettings();
224
+ if (!settings) return __error("Could not get sequence settings");
225
+
226
+ settings.videoFrameWidth = ${args.width};
227
+ settings.videoFrameHeight = ${args.height};
228
+ seq.setSettings(settings);
229
+
230
+ return __result({ width: ${args.width}, height: ${args.height}, sequence: seq.name });
231
231
  `);
232
232
  return sendCommand(script, bridgeOptions);
233
233
  },
@@ -248,18 +248,18 @@ export function getUtilityTools(bridgeOptions) {
248
248
  },
249
249
  },
250
250
  handler: async (args) => {
251
- const script = buildToolScript(`
252
- var seq = app.project.activeSequence;
253
- if (!seq) return __error("No active sequence");
254
-
255
- var settings = seq.getSettings();
256
- if (!settings) return __error("Could not get sequence settings");
257
-
258
- ${args.sample_rate !== undefined ? `settings.audioSampleRate = ${args.sample_rate};` : ""}
259
- ${args.channel_type !== undefined ? `settings.audioChannelType = ${args.channel_type};` : ""}
260
- seq.setSettings(settings);
261
-
262
- return __result({ sequence: seq.name, sampleRate: settings.audioSampleRate, channelType: settings.audioChannelType });
251
+ const script = buildToolScript(`
252
+ var seq = app.project.activeSequence;
253
+ if (!seq) return __error("No active sequence");
254
+
255
+ var settings = seq.getSettings();
256
+ if (!settings) return __error("Could not get sequence settings");
257
+
258
+ ${args.sample_rate !== undefined ? `settings.audioSampleRate = ${args.sample_rate};` : ""}
259
+ ${args.channel_type !== undefined ? `settings.audioChannelType = ${args.channel_type};` : ""}
260
+ seq.setSettings(settings);
261
+
262
+ return __result({ sequence: seq.name, sampleRate: settings.audioSampleRate, channelType: settings.audioChannelType });
263
263
  `);
264
264
  return sendCommand(script, bridgeOptions);
265
265
  },
@@ -277,17 +277,17 @@ export function getUtilityTools(bridgeOptions) {
277
277
  required: ["ratio"],
278
278
  },
279
279
  handler: async (args) => {
280
- const script = buildToolScript(`
281
- var seq = app.project.activeSequence;
282
- if (!seq) return __error("No active sequence");
283
-
284
- var settings = seq.getSettings();
285
- if (!settings) return __error("Could not get sequence settings");
286
-
287
- settings.videoPixelAspectRatio = ${args.ratio};
288
- seq.setSettings(settings);
289
-
290
- return __result({ ratio: ${args.ratio}, sequence: seq.name });
280
+ const script = buildToolScript(`
281
+ var seq = app.project.activeSequence;
282
+ if (!seq) return __error("No active sequence");
283
+
284
+ var settings = seq.getSettings();
285
+ if (!settings) return __error("Could not get sequence settings");
286
+
287
+ settings.videoPixelAspectRatio = ${args.ratio};
288
+ seq.setSettings(settings);
289
+
290
+ return __result({ ratio: ${args.ratio}, sequence: seq.name });
291
291
  `);
292
292
  return sendCommand(script, bridgeOptions);
293
293
  },
@@ -305,17 +305,17 @@ export function getUtilityTools(bridgeOptions) {
305
305
  required: ["field_type"],
306
306
  },
307
307
  handler: async (args) => {
308
- const script = buildToolScript(`
309
- var seq = app.project.activeSequence;
310
- if (!seq) return __error("No active sequence");
311
-
312
- var settings = seq.getSettings();
313
- if (!settings) return __error("Could not get sequence settings");
314
-
315
- settings.videoFieldType = ${args.field_type};
316
- seq.setSettings(settings);
317
-
318
- return __result({ fieldType: ${args.field_type}, sequence: seq.name });
308
+ const script = buildToolScript(`
309
+ var seq = app.project.activeSequence;
310
+ if (!seq) return __error("No active sequence");
311
+
312
+ var settings = seq.getSettings();
313
+ if (!settings) return __error("Could not get sequence settings");
314
+
315
+ settings.videoFieldType = ${args.field_type};
316
+ seq.setSettings(settings);
317
+
318
+ return __result({ fieldType: ${args.field_type}, sequence: seq.name });
319
319
  `);
320
320
  return sendCommand(script, bridgeOptions);
321
321
  },
@@ -324,33 +324,33 @@ export function getUtilityTools(bridgeOptions) {
324
324
  description: "Get all unique media file paths used in the project. Useful for asset management and archiving.",
325
325
  parameters: {},
326
326
  handler: async () => {
327
- const script = buildToolScript(`
328
- var paths = {};
329
- function scan(bin) {
330
- for (var i = 0; i < bin.children.numItems; i++) {
331
- var item = bin.children[i];
332
- try {
333
- var mp = item.getMediaPath();
334
- if (mp && !paths[mp]) {
335
- paths[mp] = {
336
- path: mp,
337
- name: item.name,
338
- nodeId: item.nodeId,
339
- offline: false
340
- };
341
- try { paths[mp].offline = item.isOffline(); } catch(e) {}
342
- }
343
- } catch(e) {}
344
- if (item.type === 2) scan(item);
345
- }
346
- }
347
- scan(app.project.rootItem);
348
-
349
- var result = [];
350
- for (var key in paths) {
351
- if (paths.hasOwnProperty(key)) result.push(paths[key]);
352
- }
353
- return __result({ pathCount: result.length, paths: result });
327
+ const script = buildToolScript(`
328
+ var paths = {};
329
+ function scan(bin) {
330
+ for (var i = 0; i < bin.children.numItems; i++) {
331
+ var item = bin.children[i];
332
+ try {
333
+ var mp = item.getMediaPath();
334
+ if (mp && !paths[mp]) {
335
+ paths[mp] = {
336
+ path: mp,
337
+ name: item.name,
338
+ nodeId: item.nodeId,
339
+ offline: false
340
+ };
341
+ try { paths[mp].offline = item.isOffline(); } catch(e) {}
342
+ }
343
+ } catch(e) {}
344
+ if (item.type === 2) scan(item);
345
+ }
346
+ }
347
+ scan(app.project.rootItem);
348
+
349
+ var result = [];
350
+ for (var key in paths) {
351
+ if (paths.hasOwnProperty(key)) result.push(paths[key]);
352
+ }
353
+ return __result({ pathCount: result.length, paths: result });
354
354
  `);
355
355
  return sendCommand(script, bridgeOptions);
356
356
  },
@@ -359,47 +359,47 @@ export function getUtilityTools(bridgeOptions) {
359
359
  description: "Find all project items that are NOT used in any sequence. Useful for cleaning up projects.",
360
360
  parameters: {},
361
361
  handler: async () => {
362
- const script = buildToolScript(`
363
- // First, collect all project item nodeIds used in any sequence
364
- var usedIds = {};
365
- for (var s = 0; s < app.project.sequences.numSequences; s++) {
366
- var seq = app.project.sequences[s];
367
- function scanTracks(tracks) {
368
- for (var t = 0; t < tracks.numTracks; t++) {
369
- for (var c = 0; c < tracks[t].clips.numItems; c++) {
370
- try {
371
- var src = tracks[t].clips[c].projectItem;
372
- if (src) usedIds[src.nodeId] = true;
373
- } catch(e) {}
374
- }
375
- }
376
- }
377
- scanTracks(seq.videoTracks);
378
- scanTracks(seq.audioTracks);
379
- }
380
-
381
- // Then find items not in usedIds
382
- var unused = [];
383
- function findUnused(bin) {
384
- for (var i = 0; i < bin.children.numItems; i++) {
385
- var item = bin.children[i];
386
- if (item.type === 1 || item.type === 4) { // clips and files
387
- if (!usedIds[item.nodeId]) {
388
- var entry = {
389
- nodeId: item.nodeId,
390
- name: item.name,
391
- treePath: item.treePath
392
- };
393
- try { entry.mediaPath = item.getMediaPath(); } catch(e) {}
394
- unused.push(entry);
395
- }
396
- }
397
- if (item.type === 2) findUnused(item);
398
- }
399
- }
400
- findUnused(app.project.rootItem);
401
-
402
- return __result({ unusedCount: unused.length, items: unused });
362
+ const script = buildToolScript(`
363
+ // First, collect all project item nodeIds used in any sequence
364
+ var usedIds = {};
365
+ for (var s = 0; s < app.project.sequences.numSequences; s++) {
366
+ var seq = app.project.sequences[s];
367
+ function scanTracks(tracks) {
368
+ for (var t = 0; t < tracks.numTracks; t++) {
369
+ for (var c = 0; c < tracks[t].clips.numItems; c++) {
370
+ try {
371
+ var src = tracks[t].clips[c].projectItem;
372
+ if (src) usedIds[src.nodeId] = true;
373
+ } catch(e) {}
374
+ }
375
+ }
376
+ }
377
+ scanTracks(seq.videoTracks);
378
+ scanTracks(seq.audioTracks);
379
+ }
380
+
381
+ // Then find items not in usedIds
382
+ var unused = [];
383
+ function findUnused(bin) {
384
+ for (var i = 0; i < bin.children.numItems; i++) {
385
+ var item = bin.children[i];
386
+ if (item.type === 1 || item.type === 4) { // clips and files
387
+ if (!usedIds[item.nodeId]) {
388
+ var entry = {
389
+ nodeId: item.nodeId,
390
+ name: item.name,
391
+ treePath: item.treePath
392
+ };
393
+ try { entry.mediaPath = item.getMediaPath(); } catch(e) {}
394
+ unused.push(entry);
395
+ }
396
+ }
397
+ if (item.type === 2) findUnused(item);
398
+ }
399
+ }
400
+ findUnused(app.project.rootItem);
401
+
402
+ return __result({ unusedCount: unused.length, items: unused });
403
403
  `);
404
404
  return sendCommand(script, bridgeOptions);
405
405
  },
@@ -408,31 +408,31 @@ export function getUtilityTools(bridgeOptions) {
408
408
  description: "Find project items that reference the same source media file. Useful for consolidation.",
409
409
  parameters: {},
410
410
  handler: async () => {
411
- const script = buildToolScript(`
412
- var pathMap = {};
413
- function scan(bin) {
414
- for (var i = 0; i < bin.children.numItems; i++) {
415
- var item = bin.children[i];
416
- try {
417
- var mp = item.getMediaPath();
418
- if (mp) {
419
- if (!pathMap[mp]) pathMap[mp] = [];
420
- pathMap[mp].push({ nodeId: item.nodeId, name: item.name, treePath: item.treePath });
421
- }
422
- } catch(e) {}
423
- if (item.type === 2) scan(item);
424
- }
425
- }
426
- scan(app.project.rootItem);
427
-
428
- var duplicates = [];
429
- for (var path in pathMap) {
430
- if (pathMap.hasOwnProperty(path) && pathMap[path].length > 1) {
431
- duplicates.push({ mediaPath: path, count: pathMap[path].length, items: pathMap[path] });
432
- }
433
- }
434
-
435
- return __result({ duplicateGroupCount: duplicates.length, duplicates: duplicates });
411
+ const script = buildToolScript(`
412
+ var pathMap = {};
413
+ function scan(bin) {
414
+ for (var i = 0; i < bin.children.numItems; i++) {
415
+ var item = bin.children[i];
416
+ try {
417
+ var mp = item.getMediaPath();
418
+ if (mp) {
419
+ if (!pathMap[mp]) pathMap[mp] = [];
420
+ pathMap[mp].push({ nodeId: item.nodeId, name: item.name, treePath: item.treePath });
421
+ }
422
+ } catch(e) {}
423
+ if (item.type === 2) scan(item);
424
+ }
425
+ }
426
+ scan(app.project.rootItem);
427
+
428
+ var duplicates = [];
429
+ for (var path in pathMap) {
430
+ if (pathMap.hasOwnProperty(path) && pathMap[path].length > 1) {
431
+ duplicates.push({ mediaPath: path, count: pathMap[path].length, items: pathMap[path] });
432
+ }
433
+ }
434
+
435
+ return __result({ duplicateGroupCount: duplicates.length, duplicates: duplicates });
436
436
  `);
437
437
  return sendCommand(script, bridgeOptions);
438
438
  },
@@ -441,18 +441,18 @@ export function getUtilityTools(bridgeOptions) {
441
441
  description: "Lift (remove without closing gap) the content between sequence in/out points or selected clips.",
442
442
  parameters: {},
443
443
  handler: async () => {
444
- const script = buildToolScript(`
445
- app.enableQE();
446
- var seq = app.project.activeSequence;
447
- if (!seq) return __error("No active sequence");
448
-
449
- try {
450
- var qeSeq = qe.project.getActiveSequence();
451
- qeSeq.lift();
452
- return __result({ lifted: true });
453
- } catch(e) {
454
- return __error("Lift failed: " + e.message);
455
- }
444
+ const script = buildToolScript(`
445
+ app.enableQE();
446
+ var seq = app.project.activeSequence;
447
+ if (!seq) return __error("No active sequence");
448
+
449
+ try {
450
+ var qeSeq = qe.project.getActiveSequence();
451
+ qeSeq.lift();
452
+ return __result({ lifted: true });
453
+ } catch(e) {
454
+ return __error("Lift failed: " + e.message);
455
+ }
456
456
  `);
457
457
  return sendCommand(script, bridgeOptions);
458
458
  },
@@ -461,18 +461,18 @@ export function getUtilityTools(bridgeOptions) {
461
461
  description: "Extract (remove and close gap) the content between sequence in/out points.",
462
462
  parameters: {},
463
463
  handler: async () => {
464
- const script = buildToolScript(`
465
- app.enableQE();
466
- var seq = app.project.activeSequence;
467
- if (!seq) return __error("No active sequence");
468
-
469
- try {
470
- var qeSeq = qe.project.getActiveSequence();
471
- qeSeq.extract();
472
- return __result({ extracted: true });
473
- } catch(e) {
474
- return __error("Extract failed: " + e.message);
475
- }
464
+ const script = buildToolScript(`
465
+ app.enableQE();
466
+ var seq = app.project.activeSequence;
467
+ if (!seq) return __error("No active sequence");
468
+
469
+ try {
470
+ var qeSeq = qe.project.getActiveSequence();
471
+ qeSeq.extract();
472
+ return __result({ extracted: true });
473
+ } catch(e) {
474
+ return __error("Extract failed: " + e.message);
475
+ }
476
476
  `);
477
477
  return sendCommand(script, bridgeOptions);
478
478
  },
@@ -490,56 +490,56 @@ export function getUtilityTools(bridgeOptions) {
490
490
  required: ["node_id"],
491
491
  },
492
492
  handler: async (args) => {
493
- const script = buildToolScript(`
494
- var result = __findClip("${escapeForExtendScript(args.node_id)}");
495
- if (!result) return __error("Clip not found");
496
-
497
- var clip = result.clip;
498
- var info = {
499
- nodeId: clip.nodeId,
500
- name: clip.name,
501
- trackType: result.trackType,
502
- trackIndex: result.trackIndex
503
- };
504
-
505
- // Find linked clips by matching projectItem and overlapping time
506
- var linked = [];
507
- var seq = app.project.activeSequence;
508
- var clipStart = clip.start.ticks;
509
- var clipEnd = clip.end.ticks;
510
- var srcId = null;
511
- try { srcId = clip.projectItem ? clip.projectItem.nodeId : null; } catch(e) {}
512
-
513
- function findLinked(tracks, type) {
514
- for (var t = 0; t < tracks.numTracks; t++) {
515
- for (var c = 0; c < tracks[t].clips.numItems; c++) {
516
- var other = tracks[t].clips[c];
517
- if (other.nodeId === clip.nodeId) continue;
518
- // Check same source and overlapping time
519
- try {
520
- var otherSrcId = other.projectItem ? other.projectItem.nodeId : null;
521
- if (srcId && otherSrcId === srcId && other.start.ticks === clipStart) {
522
- linked.push({
523
- nodeId: other.nodeId,
524
- name: other.name,
525
- trackType: type,
526
- trackIndex: t,
527
- startSeconds: __ticksToSeconds(other.start.ticks),
528
- endSeconds: __ticksToSeconds(other.end.ticks)
529
- });
530
- }
531
- } catch(e) {}
532
- }
533
- }
534
- }
535
-
536
- findLinked(seq.videoTracks, "video");
537
- findLinked(seq.audioTracks, "audio");
538
-
539
- info.linkedClips = linked;
540
- info.linkedCount = linked.length;
541
-
542
- return __result(info);
493
+ const script = buildToolScript(`
494
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
495
+ if (!result) return __error("Clip not found");
496
+
497
+ var clip = result.clip;
498
+ var info = {
499
+ nodeId: clip.nodeId,
500
+ name: clip.name,
501
+ trackType: result.trackType,
502
+ trackIndex: result.trackIndex
503
+ };
504
+
505
+ // Find linked clips by matching projectItem and overlapping time
506
+ var linked = [];
507
+ var seq = app.project.activeSequence;
508
+ var clipStart = clip.start.ticks;
509
+ var clipEnd = clip.end.ticks;
510
+ var srcId = null;
511
+ try { srcId = clip.projectItem ? clip.projectItem.nodeId : null; } catch(e) {}
512
+
513
+ function findLinked(tracks, type) {
514
+ for (var t = 0; t < tracks.numTracks; t++) {
515
+ for (var c = 0; c < tracks[t].clips.numItems; c++) {
516
+ var other = tracks[t].clips[c];
517
+ if (other.nodeId === clip.nodeId) continue;
518
+ // Check same source and overlapping time
519
+ try {
520
+ var otherSrcId = other.projectItem ? other.projectItem.nodeId : null;
521
+ if (srcId && otherSrcId === srcId && other.start.ticks === clipStart) {
522
+ linked.push({
523
+ nodeId: other.nodeId,
524
+ name: other.name,
525
+ trackType: type,
526
+ trackIndex: t,
527
+ startSeconds: __ticksToSeconds(other.start.ticks),
528
+ endSeconds: __ticksToSeconds(other.end.ticks)
529
+ });
530
+ }
531
+ } catch(e) {}
532
+ }
533
+ }
534
+ }
535
+
536
+ findLinked(seq.videoTracks, "video");
537
+ findLinked(seq.audioTracks, "audio");
538
+
539
+ info.linkedClips = linked;
540
+ info.linkedCount = linked.length;
541
+
542
+ return __result(info);
543
543
  `);
544
544
  return sendCommand(script, bridgeOptions);
545
545
  },
@@ -565,25 +565,25 @@ export function getUtilityTools(bridgeOptions) {
565
565
  const seqLookup = args.sequence_id
566
566
  ? `var seq = __findSequence("${escapeForExtendScript(args.sequence_id)}"); if (!seq) return __error("Sequence not found");`
567
567
  : `var seq = app.project.activeSequence; if (!seq) return __error("No active sequence");`;
568
- const script = buildToolScript(`
569
- ${seqLookup}
570
-
571
- var markers = [];
572
- var m = seq.markers.getFirstMarker();
573
- while (m) {
574
- if (m.type === "${escapeForExtendScript(args.marker_type)}") {
575
- markers.push({
576
- name: m.name,
577
- comments: m.comments,
578
- startSeconds: __ticksToSeconds(m.start.ticks),
579
- endSeconds: __ticksToSeconds(m.end.ticks),
580
- type: m.type
581
- });
582
- }
583
- m = seq.markers.getNextMarker(m);
584
- }
585
-
586
- return __result({ type: "${escapeForExtendScript(args.marker_type)}", count: markers.length, markers: markers });
568
+ const script = buildToolScript(`
569
+ ${seqLookup}
570
+
571
+ var markers = [];
572
+ var m = seq.markers.getFirstMarker();
573
+ while (m) {
574
+ if (m.type === "${escapeForExtendScript(args.marker_type)}") {
575
+ markers.push({
576
+ name: m.name,
577
+ comments: m.comments,
578
+ startSeconds: __ticksToSeconds(m.start.ticks),
579
+ endSeconds: __ticksToSeconds(m.end.ticks),
580
+ type: m.type
581
+ });
582
+ }
583
+ m = seq.markers.getNextMarker(m);
584
+ }
585
+
586
+ return __result({ type: "${escapeForExtendScript(args.marker_type)}", count: markers.length, markers: markers });
587
587
  `);
588
588
  return sendCommand(script, bridgeOptions);
589
589
  },
@@ -601,29 +601,29 @@ export function getUtilityTools(bridgeOptions) {
601
601
  required: ["item_id"],
602
602
  },
603
603
  handler: async (args) => {
604
- const script = buildToolScript(`
605
- // Accept project item IDs/names AND timeline clip nodeIds.
606
- var item = __resolveProjectItemOrTimelineClip("${escapeForExtendScript(args.item_id)}");
607
- if (!item) return __error("Item not found in project tree or timeline: ${escapeForExtendScript(args.item_id)}");
608
-
609
- var markers = [];
610
- try {
611
- var m = item.getMarkers().getFirstMarker();
612
- while (m) {
613
- var mi = {
614
- name: m.name,
615
- comments: m.comments,
616
- startSeconds: __ticksToSeconds(m.start.ticks),
617
- type: m.type
618
- };
619
- try { mi.endSeconds = __ticksToSeconds(m.end.ticks); } catch(e) {}
620
- try { mi.colorIndex = m.getColorByIndex(); } catch(e) {}
621
- markers.push(mi);
622
- m = item.getMarkers().getNextMarker(m);
623
- }
624
- } catch(e) {}
625
-
626
- return __result({ item: item.name, markerCount: markers.length, markers: markers });
604
+ const script = buildToolScript(`
605
+ // Accept project item IDs/names AND timeline clip nodeIds.
606
+ var item = __resolveProjectItemOrTimelineClip("${escapeForExtendScript(args.item_id)}");
607
+ if (!item) return __error("Item not found in project tree or timeline: ${escapeForExtendScript(args.item_id)}");
608
+
609
+ var markers = [];
610
+ try {
611
+ var m = item.getMarkers().getFirstMarker();
612
+ while (m) {
613
+ var mi = {
614
+ name: m.name,
615
+ comments: m.comments,
616
+ startSeconds: __ticksToSeconds(m.start.ticks),
617
+ type: m.type
618
+ };
619
+ try { mi.endSeconds = __ticksToSeconds(m.end.ticks); } catch(e) {}
620
+ try { mi.colorIndex = m.getColorByIndex(); } catch(e) {}
621
+ markers.push(mi);
622
+ m = item.getMarkers().getNextMarker(m);
623
+ }
624
+ } catch(e) {}
625
+
626
+ return __result({ item: item.name, markerCount: markers.length, markers: markers });
627
627
  `);
628
628
  return sendCommand(script, bridgeOptions);
629
629
  },
@@ -666,24 +666,24 @@ export function getUtilityTools(bridgeOptions) {
666
666
  required: ["item_id", "time_seconds"],
667
667
  },
668
668
  handler: async (args) => {
669
- const script = buildToolScript(`
670
- var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
671
- if (!item) return __error("Item not found");
672
-
673
- var markers = item.getMarkers();
674
- var marker = markers.createMarker(${args.time_seconds});
675
-
676
- ${args.name ? `marker.name = "${escapeForExtendScript(args.name)}";` : ""}
677
- ${args.comments ? `marker.comments = "${escapeForExtendScript(args.comments)}";` : ""}
678
- ${args.type ? `marker.type = "${escapeForExtendScript(args.type)}";` : ""}
679
- ${args.duration_seconds !== undefined ? `
680
- var endTime = new Time();
681
- endTime.seconds = ${args.time_seconds + args.duration_seconds};
682
- marker.end = endTime;
683
- ` : ""}
684
- ${args.color_index !== undefined ? `marker.setColorByIndex(${args.color_index});` : ""}
685
-
686
- return __result({ added: true, item: item.name, timeSeconds: ${args.time_seconds} });
669
+ const script = buildToolScript(`
670
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
671
+ if (!item) return __error("Item not found");
672
+
673
+ var markers = item.getMarkers();
674
+ var marker = markers.createMarker(${args.time_seconds});
675
+
676
+ ${args.name ? `marker.name = "${escapeForExtendScript(args.name)}";` : ""}
677
+ ${args.comments ? `marker.comments = "${escapeForExtendScript(args.comments)}";` : ""}
678
+ ${args.type ? `marker.type = "${escapeForExtendScript(args.type)}";` : ""}
679
+ ${args.duration_seconds !== undefined ? `
680
+ var endTime = new Time();
681
+ endTime.seconds = ${args.time_seconds + args.duration_seconds};
682
+ marker.end = endTime;
683
+ ` : ""}
684
+ ${args.color_index !== undefined ? `marker.setColorByIndex(${args.color_index});` : ""}
685
+
686
+ return __result({ added: true, item: item.name, timeSeconds: ${args.time_seconds} });
687
687
  `);
688
688
  return sendCommand(script, bridgeOptions);
689
689
  },
@@ -704,18 +704,18 @@ export function getUtilityTools(bridgeOptions) {
704
704
  },
705
705
  },
706
706
  handler: async (args) => {
707
- const script = buildToolScript(`
708
- var seq = app.project.activeSequence;
709
- if (!seq) return __error("No active sequence");
710
-
711
- var settings = seq.getSettings();
712
- if (!settings) return __error("Could not get sequence settings");
713
-
714
- ${args.video_display_format !== undefined ? `settings.videoDisplayFormat = ${args.video_display_format};` : ""}
715
- ${args.audio_display_format !== undefined ? `settings.audioDisplayFormat = ${args.audio_display_format};` : ""}
716
- seq.setSettings(settings);
717
-
718
- return __result({ sequence: seq.name, videoDisplayFormat: settings.videoDisplayFormat, audioDisplayFormat: settings.audioDisplayFormat });
707
+ const script = buildToolScript(`
708
+ var seq = app.project.activeSequence;
709
+ if (!seq) return __error("No active sequence");
710
+
711
+ var settings = seq.getSettings();
712
+ if (!settings) return __error("Could not get sequence settings");
713
+
714
+ ${args.video_display_format !== undefined ? `settings.videoDisplayFormat = ${args.video_display_format};` : ""}
715
+ ${args.audio_display_format !== undefined ? `settings.audioDisplayFormat = ${args.audio_display_format};` : ""}
716
+ seq.setSettings(settings);
717
+
718
+ return __result({ sequence: seq.name, videoDisplayFormat: settings.videoDisplayFormat, audioDisplayFormat: settings.audioDisplayFormat });
719
719
  `);
720
720
  return sendCommand(script, bridgeOptions);
721
721
  },
@@ -734,45 +734,45 @@ export function getUtilityTools(bridgeOptions) {
734
734
  },
735
735
  handler: async (args) => {
736
736
  const trackType = args.track_type || "both";
737
- const script = buildToolScript(`
738
- var seq = app.project.activeSequence;
739
- if (!seq) return __error("No active sequence");
740
-
741
- var posTicks = parseFloat(seq.getPlayerPosition().ticks);
742
- var clips = [];
743
-
744
- function findAtPlayhead(tracks, type) {
745
- for (var t = 0; t < tracks.numTracks; t++) {
746
- for (var c = 0; c < tracks[t].clips.numItems; c++) {
747
- var clip = tracks[t].clips[c];
748
- var cs = parseFloat(clip.start.ticks);
749
- var ce = parseFloat(clip.end.ticks);
750
- if (cs <= posTicks && ce > posTicks) {
751
- var ci = {
752
- nodeId: clip.nodeId,
753
- name: clip.name,
754
- trackType: type,
755
- trackIndex: t,
756
- trackName: tracks[t].name,
757
- clipIndex: c,
758
- startSeconds: __ticksToSeconds(clip.start.ticks),
759
- endSeconds: __ticksToSeconds(clip.end.ticks)
760
- };
761
- try { ci.enabled = !clip.isDisabled(); } catch(e) { ci.enabled = true; }
762
- clips.push(ci);
763
- }
764
- }
765
- }
766
- }
767
-
768
- if ("${trackType}" !== "audio") findAtPlayhead(seq.videoTracks, "video");
769
- if ("${trackType}" !== "video") findAtPlayhead(seq.audioTracks, "audio");
770
-
771
- return __result({
772
- playheadSeconds: __ticksToSeconds(seq.getPlayerPosition().ticks),
773
- clipCount: clips.length,
774
- clips: clips
775
- });
737
+ const script = buildToolScript(`
738
+ var seq = app.project.activeSequence;
739
+ if (!seq) return __error("No active sequence");
740
+
741
+ var posTicks = parseFloat(seq.getPlayerPosition().ticks);
742
+ var clips = [];
743
+
744
+ function findAtPlayhead(tracks, type) {
745
+ for (var t = 0; t < tracks.numTracks; t++) {
746
+ for (var c = 0; c < tracks[t].clips.numItems; c++) {
747
+ var clip = tracks[t].clips[c];
748
+ var cs = parseFloat(clip.start.ticks);
749
+ var ce = parseFloat(clip.end.ticks);
750
+ if (cs <= posTicks && ce > posTicks) {
751
+ var ci = {
752
+ nodeId: clip.nodeId,
753
+ name: clip.name,
754
+ trackType: type,
755
+ trackIndex: t,
756
+ trackName: tracks[t].name,
757
+ clipIndex: c,
758
+ startSeconds: __ticksToSeconds(clip.start.ticks),
759
+ endSeconds: __ticksToSeconds(clip.end.ticks)
760
+ };
761
+ try { ci.enabled = !clip.isDisabled(); } catch(e) { ci.enabled = true; }
762
+ clips.push(ci);
763
+ }
764
+ }
765
+ }
766
+ }
767
+
768
+ if ("${trackType}" !== "audio") findAtPlayhead(seq.videoTracks, "video");
769
+ if ("${trackType}" !== "video") findAtPlayhead(seq.audioTracks, "audio");
770
+
771
+ return __result({
772
+ playheadSeconds: __ticksToSeconds(seq.getPlayerPosition().ticks),
773
+ clipCount: clips.length,
774
+ clips: clips
775
+ });
776
776
  `);
777
777
  return sendCommand(script, bridgeOptions);
778
778
  },
@@ -797,54 +797,54 @@ export function getUtilityTools(bridgeOptions) {
797
797
  handler: async (args) => {
798
798
  const direction = args.direction || "next";
799
799
  const trackType = args.track_type || "both";
800
- const script = buildToolScript(`
801
- var seq = app.project.activeSequence;
802
- if (!seq) return __error("No active sequence");
803
-
804
- var posTicks = parseFloat(seq.getPlayerPosition().ticks);
805
- var editPoints = [];
806
-
807
- function collectPoints(tracks, type) {
808
- for (var t = 0; t < tracks.numTracks; t++) {
809
- for (var c = 0; c < tracks[t].clips.numItems; c++) {
810
- var clip = tracks[t].clips[c];
811
- editPoints.push(parseFloat(clip.start.ticks));
812
- editPoints.push(parseFloat(clip.end.ticks));
813
- }
814
- }
815
- }
816
-
817
- if ("${trackType}" !== "audio") collectPoints(seq.videoTracks, "video");
818
- if ("${trackType}" !== "video") collectPoints(seq.audioTracks, "audio");
819
-
820
- // Sort and deduplicate
821
- editPoints.sort(function(a, b) { return a - b; });
822
- var unique = [];
823
- for (var i = 0; i < editPoints.length; i++) {
824
- if (unique.length === 0 || editPoints[i] !== unique[unique.length - 1]) {
825
- unique.push(editPoints[i]);
826
- }
827
- }
828
-
829
- var found = null;
830
- if ("${direction}" === "next") {
831
- for (var i = 0; i < unique.length; i++) {
832
- if (unique[i] > posTicks + 1) { found = unique[i]; break; }
833
- }
834
- } else {
835
- for (var i = unique.length - 1; i >= 0; i--) {
836
- if (unique[i] < posTicks - 1) { found = unique[i]; break; }
837
- }
838
- }
839
-
840
- if (found === null) return __result({ found: false, direction: "${direction}" });
841
-
842
- return __result({
843
- found: true,
844
- direction: "${direction}",
845
- editPointSeconds: __ticksToSeconds("" + found),
846
- playheadSeconds: __ticksToSeconds("" + posTicks)
847
- });
800
+ const script = buildToolScript(`
801
+ var seq = app.project.activeSequence;
802
+ if (!seq) return __error("No active sequence");
803
+
804
+ var posTicks = parseFloat(seq.getPlayerPosition().ticks);
805
+ var editPoints = [];
806
+
807
+ function collectPoints(tracks, type) {
808
+ for (var t = 0; t < tracks.numTracks; t++) {
809
+ for (var c = 0; c < tracks[t].clips.numItems; c++) {
810
+ var clip = tracks[t].clips[c];
811
+ editPoints.push(parseFloat(clip.start.ticks));
812
+ editPoints.push(parseFloat(clip.end.ticks));
813
+ }
814
+ }
815
+ }
816
+
817
+ if ("${trackType}" !== "audio") collectPoints(seq.videoTracks, "video");
818
+ if ("${trackType}" !== "video") collectPoints(seq.audioTracks, "audio");
819
+
820
+ // Sort and deduplicate
821
+ editPoints.sort(function(a, b) { return a - b; });
822
+ var unique = [];
823
+ for (var i = 0; i < editPoints.length; i++) {
824
+ if (unique.length === 0 || editPoints[i] !== unique[unique.length - 1]) {
825
+ unique.push(editPoints[i]);
826
+ }
827
+ }
828
+
829
+ var found = null;
830
+ if ("${direction}" === "next") {
831
+ for (var i = 0; i < unique.length; i++) {
832
+ if (unique[i] > posTicks + 1) { found = unique[i]; break; }
833
+ }
834
+ } else {
835
+ for (var i = unique.length - 1; i >= 0; i--) {
836
+ if (unique[i] < posTicks - 1) { found = unique[i]; break; }
837
+ }
838
+ }
839
+
840
+ if (found === null) return __result({ found: false, direction: "${direction}" });
841
+
842
+ return __result({
843
+ found: true,
844
+ direction: "${direction}",
845
+ editPointSeconds: __ticksToSeconds("" + found),
846
+ playheadSeconds: __ticksToSeconds("" + posTicks)
847
+ });
848
848
  `);
849
849
  return sendCommand(script, bridgeOptions);
850
850
  },
@@ -863,47 +863,47 @@ export function getUtilityTools(bridgeOptions) {
863
863
  },
864
864
  handler: async (args) => {
865
865
  const direction = args.direction || "next";
866
- const script = buildToolScript(`
867
- var seq = app.project.activeSequence;
868
- if (!seq) return __error("No active sequence");
869
-
870
- var posTicks = parseFloat(seq.getPlayerPosition().ticks);
871
- var editPoints = [];
872
-
873
- function collectPoints(tracks) {
874
- for (var t = 0; t < tracks.numTracks; t++) {
875
- for (var c = 0; c < tracks[t].clips.numItems; c++) {
876
- editPoints.push(parseFloat(tracks[t].clips[c].start.ticks));
877
- editPoints.push(parseFloat(tracks[t].clips[c].end.ticks));
878
- }
879
- }
880
- }
881
-
882
- collectPoints(seq.videoTracks);
883
- collectPoints(seq.audioTracks);
884
-
885
- editPoints.sort(function(a, b) { return a - b; });
886
- var unique = [];
887
- for (var i = 0; i < editPoints.length; i++) {
888
- if (unique.length === 0 || editPoints[i] !== unique[unique.length - 1]) unique.push(editPoints[i]);
889
- }
890
-
891
- var found = null;
892
- if ("${direction}" === "next") {
893
- for (var i = 0; i < unique.length; i++) {
894
- if (unique[i] > posTicks + 1) { found = unique[i]; break; }
895
- }
896
- } else {
897
- for (var i = unique.length - 1; i >= 0; i--) {
898
- if (unique[i] < posTicks - 1) { found = unique[i]; break; }
899
- }
900
- }
901
-
902
- if (found === null) return __error("No " + "${direction}" + " edit point found");
903
-
904
- seq.setPlayerPosition("" + found);
905
-
906
- return __result({ movedTo: __ticksToSeconds("" + found), direction: "${direction}" });
866
+ const script = buildToolScript(`
867
+ var seq = app.project.activeSequence;
868
+ if (!seq) return __error("No active sequence");
869
+
870
+ var posTicks = parseFloat(seq.getPlayerPosition().ticks);
871
+ var editPoints = [];
872
+
873
+ function collectPoints(tracks) {
874
+ for (var t = 0; t < tracks.numTracks; t++) {
875
+ for (var c = 0; c < tracks[t].clips.numItems; c++) {
876
+ editPoints.push(parseFloat(tracks[t].clips[c].start.ticks));
877
+ editPoints.push(parseFloat(tracks[t].clips[c].end.ticks));
878
+ }
879
+ }
880
+ }
881
+
882
+ collectPoints(seq.videoTracks);
883
+ collectPoints(seq.audioTracks);
884
+
885
+ editPoints.sort(function(a, b) { return a - b; });
886
+ var unique = [];
887
+ for (var i = 0; i < editPoints.length; i++) {
888
+ if (unique.length === 0 || editPoints[i] !== unique[unique.length - 1]) unique.push(editPoints[i]);
889
+ }
890
+
891
+ var found = null;
892
+ if ("${direction}" === "next") {
893
+ for (var i = 0; i < unique.length; i++) {
894
+ if (unique[i] > posTicks + 1) { found = unique[i]; break; }
895
+ }
896
+ } else {
897
+ for (var i = unique.length - 1; i >= 0; i--) {
898
+ if (unique[i] < posTicks - 1) { found = unique[i]; break; }
899
+ }
900
+ }
901
+
902
+ if (found === null) return __error("No " + "${direction}" + " edit point found");
903
+
904
+ seq.setPlayerPosition("" + found);
905
+
906
+ return __result({ movedTo: __ticksToSeconds("" + found), direction: "${direction}" });
907
907
  `);
908
908
  return sendCommand(script, bridgeOptions);
909
909
  },
@@ -932,21 +932,21 @@ export function getUtilityTools(bridgeOptions) {
932
932
  },
933
933
  },
934
934
  handler: async (args) => {
935
- const script = buildToolScript(`
936
- var project = app.project;
937
- if (!project) return __error("No project open");
938
-
939
- var set = {};
940
- ${args.captured_video ? `
941
- try { project.setScratchDiskPath("${escapeForExtendScript(args.captured_video)}", 0); set.capturedVideo = "${escapeForExtendScript(args.captured_video)}"; } catch(e) {}` : ""}
942
- ${args.captured_audio ? `
943
- try { project.setScratchDiskPath("${escapeForExtendScript(args.captured_audio)}", 1); set.capturedAudio = "${escapeForExtendScript(args.captured_audio)}"; } catch(e) {}` : ""}
944
- ${args.video_previews ? `
945
- try { project.setScratchDiskPath("${escapeForExtendScript(args.video_previews)}", 2); set.videoPreviews = "${escapeForExtendScript(args.video_previews)}"; } catch(e) {}` : ""}
946
- ${args.audio_previews ? `
947
- try { project.setScratchDiskPath("${escapeForExtendScript(args.audio_previews)}", 3); set.audioPreviews = "${escapeForExtendScript(args.audio_previews)}"; } catch(e) {}` : ""}
948
-
949
- return __result(set);
935
+ const script = buildToolScript(`
936
+ var project = app.project;
937
+ if (!project) return __error("No project open");
938
+
939
+ var set = {};
940
+ ${args.captured_video ? `
941
+ try { project.setScratchDiskPath("${escapeForExtendScript(args.captured_video)}", 0); set.capturedVideo = "${escapeForExtendScript(args.captured_video)}"; } catch(e) {}` : ""}
942
+ ${args.captured_audio ? `
943
+ try { project.setScratchDiskPath("${escapeForExtendScript(args.captured_audio)}", 1); set.capturedAudio = "${escapeForExtendScript(args.captured_audio)}"; } catch(e) {}` : ""}
944
+ ${args.video_previews ? `
945
+ try { project.setScratchDiskPath("${escapeForExtendScript(args.video_previews)}", 2); set.videoPreviews = "${escapeForExtendScript(args.video_previews)}"; } catch(e) {}` : ""}
946
+ ${args.audio_previews ? `
947
+ try { project.setScratchDiskPath("${escapeForExtendScript(args.audio_previews)}", 3); set.audioPreviews = "${escapeForExtendScript(args.audio_previews)}"; } catch(e) {}` : ""}
948
+
949
+ return __result(set);
950
950
  `);
951
951
  return sendCommand(script, bridgeOptions);
952
952
  },
@@ -955,17 +955,17 @@ export function getUtilityTools(bridgeOptions) {
955
955
  description: "Get the current scratch disk paths for the project.",
956
956
  parameters: {},
957
957
  handler: async () => {
958
- const script = buildToolScript(`
959
- var project = app.project;
960
- if (!project) return __error("No project open");
961
-
962
- var disks = {};
963
- try { disks.capturedVideo = project.getScratchDiskPath(0); } catch(e) {}
964
- try { disks.capturedAudio = project.getScratchDiskPath(1); } catch(e) {}
965
- try { disks.videoPreviews = project.getScratchDiskPath(2); } catch(e) {}
966
- try { disks.audioPreviews = project.getScratchDiskPath(3); } catch(e) {}
967
-
968
- return __result(disks);
958
+ const script = buildToolScript(`
959
+ var project = app.project;
960
+ if (!project) return __error("No project open");
961
+
962
+ var disks = {};
963
+ try { disks.capturedVideo = project.getScratchDiskPath(0); } catch(e) {}
964
+ try { disks.capturedAudio = project.getScratchDiskPath(1); } catch(e) {}
965
+ try { disks.videoPreviews = project.getScratchDiskPath(2); } catch(e) {}
966
+ try { disks.audioPreviews = project.getScratchDiskPath(3); } catch(e) {}
967
+
968
+ return __result(disks);
969
969
  `);
970
970
  return sendCommand(script, bridgeOptions);
971
971
  },
@@ -983,40 +983,40 @@ export function getUtilityTools(bridgeOptions) {
983
983
  required: ["name"],
984
984
  },
985
985
  handler: async (args) => {
986
- const script = buildToolScript(`
987
- var seq = app.project.activeSequence;
988
- if (!seq) return __error("No active sequence");
989
-
990
- // Get selected clips
991
- var selected = [];
992
- function getSelected(tracks) {
993
- for (var t = 0; t < tracks.numTracks; t++) {
994
- for (var c = 0; c < tracks[t].clips.numItems; c++) {
995
- if (tracks[t].clips[c].isSelected()) selected.push(tracks[t].clips[c]);
996
- }
997
- }
998
- }
999
- getSelected(seq.videoTracks);
1000
- getSelected(seq.audioTracks);
1001
-
1002
- if (selected.length === 0) return __error("No clips selected. Select clips first.");
1003
-
1004
- try {
1005
- var sub = seq.createSubsequence(true);
1006
- var appliedName = null;
1007
- if (sub) {
1008
- try { sub.name = "${escapeForExtendScript(args.name)}"; appliedName = sub.name; } catch(e) {}
1009
- }
1010
- var res = {
1011
- nested: true,
1012
- clipCount: selected.length,
1013
- note: "createSubsequence nests the sequence in/out range, not the clip selection."
1014
- };
1015
- if (appliedName !== null) res.name = appliedName;
1016
- return __result(res);
1017
- } catch(e) {
1018
- return __error("Nesting failed: " + e.message);
1019
- }
986
+ const script = buildToolScript(`
987
+ var seq = app.project.activeSequence;
988
+ if (!seq) return __error("No active sequence");
989
+
990
+ // Get selected clips
991
+ var selected = [];
992
+ function getSelected(tracks) {
993
+ for (var t = 0; t < tracks.numTracks; t++) {
994
+ for (var c = 0; c < tracks[t].clips.numItems; c++) {
995
+ if (tracks[t].clips[c].isSelected()) selected.push(tracks[t].clips[c]);
996
+ }
997
+ }
998
+ }
999
+ getSelected(seq.videoTracks);
1000
+ getSelected(seq.audioTracks);
1001
+
1002
+ if (selected.length === 0) return __error("No clips selected. Select clips first.");
1003
+
1004
+ try {
1005
+ var sub = seq.createSubsequence(true);
1006
+ var appliedName = null;
1007
+ if (sub) {
1008
+ try { sub.name = "${escapeForExtendScript(args.name)}"; appliedName = sub.name; } catch(e) {}
1009
+ }
1010
+ var res = {
1011
+ nested: true,
1012
+ clipCount: selected.length,
1013
+ note: "createSubsequence nests the sequence in/out range, not the clip selection."
1014
+ };
1015
+ if (appliedName !== null) res.name = appliedName;
1016
+ return __result(res);
1017
+ } catch(e) {
1018
+ return __error("Nesting failed: " + e.message);
1019
+ }
1020
1020
  `);
1021
1021
  return sendCommand(script, bridgeOptions);
1022
1022
  },
@@ -1025,10 +1025,10 @@ export function getUtilityTools(bridgeOptions) {
1025
1025
  description: "Get the total number of sequences in the project.",
1026
1026
  parameters: {},
1027
1027
  handler: async () => {
1028
- const script = buildToolScript(`
1029
- var project = app.project;
1030
- if (!project) return __error("No project open");
1031
- return __result({ count: project.sequences.numSequences });
1028
+ const script = buildToolScript(`
1029
+ var project = app.project;
1030
+ if (!project) return __error("No project open");
1031
+ return __result({ count: project.sequences.numSequences });
1032
1032
  `);
1033
1033
  return sendCommand(script, bridgeOptions);
1034
1034
  },
@@ -1037,15 +1037,15 @@ export function getUtilityTools(bridgeOptions) {
1037
1037
  description: "Get the total number of clips across all tracks in the active sequence.",
1038
1038
  parameters: {},
1039
1039
  handler: async () => {
1040
- const script = buildToolScript(`
1041
- var seq = app.project.activeSequence;
1042
- if (!seq) return __error("No active sequence");
1043
-
1044
- var video = 0, audio = 0;
1045
- for (var t = 0; t < seq.videoTracks.numTracks; t++) video += seq.videoTracks[t].clips.numItems;
1046
- for (var t = 0; t < seq.audioTracks.numTracks; t++) audio += seq.audioTracks[t].clips.numItems;
1047
-
1048
- return __result({ videoClips: video, audioClips: audio, total: video + audio });
1040
+ const script = buildToolScript(`
1041
+ var seq = app.project.activeSequence;
1042
+ if (!seq) return __error("No active sequence");
1043
+
1044
+ var video = 0, audio = 0;
1045
+ for (var t = 0; t < seq.videoTracks.numTracks; t++) video += seq.videoTracks[t].clips.numItems;
1046
+ for (var t = 0; t < seq.audioTracks.numTracks; t++) audio += seq.audioTracks[t].clips.numItems;
1047
+
1048
+ return __result({ videoClips: video, audioClips: audio, total: video + audio });
1049
1049
  `);
1050
1050
  return sendCommand(script, bridgeOptions);
1051
1051
  },
@@ -1069,46 +1069,46 @@ export function getUtilityTools(bridgeOptions) {
1069
1069
  handler: async (args) => {
1070
1070
  const trackType = args.track_type || "video";
1071
1071
  const trackIndex = args.track_index ?? 0;
1072
- const script = buildToolScript(`
1073
- var seq = app.project.activeSequence;
1074
- if (!seq) return __error("No active sequence");
1075
-
1076
- var tracks = ${trackType === "video" ? "seq.videoTracks" : "seq.audioTracks"};
1077
- if (${trackIndex} >= tracks.numTracks) return __error("Track index out of range");
1078
-
1079
- var posTicks = parseFloat(seq.getPlayerPosition().ticks);
1080
- var track = tracks[${trackIndex}];
1081
- var found = null;
1082
-
1083
- for (var c = 0; c < track.clips.numItems; c++) {
1084
- var clip = track.clips[c];
1085
- if (parseFloat(clip.start.ticks) <= posTicks && parseFloat(clip.end.ticks) > posTicks) {
1086
- found = clip;
1087
- break;
1088
- }
1089
- }
1090
-
1091
- if (!found) return __error("No clip at playhead on ${trackType} track ${trackIndex}");
1092
-
1093
- var offsetTicks = posTicks - parseFloat(found.start.ticks) + parseFloat(found.inPoint.ticks);
1094
-
1095
- var result = {
1096
- clipName: found.name,
1097
- clipNodeId: found.nodeId,
1098
- timelineSeconds: __ticksToSeconds("" + posTicks),
1099
- sourceSeconds: __ticksToSeconds("" + offsetTicks)
1100
- };
1101
-
1102
- try {
1103
- var src = found.projectItem;
1104
- if (src) {
1105
- result.sourceNodeId = src.nodeId;
1106
- result.sourceName = src.name;
1107
- try { result.sourceMediaPath = src.getMediaPath(); } catch(e) {}
1108
- }
1109
- } catch(e) {}
1110
-
1111
- return __result(result);
1072
+ const script = buildToolScript(`
1073
+ var seq = app.project.activeSequence;
1074
+ if (!seq) return __error("No active sequence");
1075
+
1076
+ var tracks = ${trackType === "video" ? "seq.videoTracks" : "seq.audioTracks"};
1077
+ if (${trackIndex} >= tracks.numTracks) return __error("Track index out of range");
1078
+
1079
+ var posTicks = parseFloat(seq.getPlayerPosition().ticks);
1080
+ var track = tracks[${trackIndex}];
1081
+ var found = null;
1082
+
1083
+ for (var c = 0; c < track.clips.numItems; c++) {
1084
+ var clip = track.clips[c];
1085
+ if (parseFloat(clip.start.ticks) <= posTicks && parseFloat(clip.end.ticks) > posTicks) {
1086
+ found = clip;
1087
+ break;
1088
+ }
1089
+ }
1090
+
1091
+ if (!found) return __error("No clip at playhead on ${trackType} track ${trackIndex}");
1092
+
1093
+ var offsetTicks = posTicks - parseFloat(found.start.ticks) + parseFloat(found.inPoint.ticks);
1094
+
1095
+ var result = {
1096
+ clipName: found.name,
1097
+ clipNodeId: found.nodeId,
1098
+ timelineSeconds: __ticksToSeconds("" + posTicks),
1099
+ sourceSeconds: __ticksToSeconds("" + offsetTicks)
1100
+ };
1101
+
1102
+ try {
1103
+ var src = found.projectItem;
1104
+ if (src) {
1105
+ result.sourceNodeId = src.nodeId;
1106
+ result.sourceName = src.name;
1107
+ try { result.sourceMediaPath = src.getMediaPath(); } catch(e) {}
1108
+ }
1109
+ } catch(e) {}
1110
+
1111
+ return __result(result);
1112
1112
  `);
1113
1113
  return sendCommand(script, bridgeOptions);
1114
1114
  },