@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
@@ -3,15 +3,15 @@ import { sendCommand } from "../bridge/file-bridge.js";
3
3
  export function getEffectsTools(bridgeOptions) {
4
4
  return {
5
5
  apply_effect: {
6
- description: `Apply a video effect to a clip by name. Uses QE DOM for effect lookup.
7
-
8
- Preconditions: Active sequence required. The clip must exist on the timeline. Use list_available_effects to find exact effect names before calling this tool.
9
-
10
- Common effect names: "Gaussian Blur", "Lumetri Color", "Warp Stabilizer", "Unsharp Mask", "Fast Color Corrector", "Brightness & Contrast", "Hue/Saturation", "Sharpen", "Video Limiter", "Timecode".
11
-
12
- After applying, use get_effect_properties to see the effect's parameters, then set_effect_property or add_keyframe to adjust values.
13
-
14
- Example: apply_effect({ node_id: "abc123", effect_name: "Gaussian Blur" })
6
+ description: `Apply a video effect to a clip by name. Uses QE DOM for effect lookup.
7
+
8
+ Preconditions: Active sequence required. The clip must exist on the timeline. Use list_available_effects to find exact effect names before calling this tool.
9
+
10
+ Common effect names: "Gaussian Blur", "Lumetri Color", "Warp Stabilizer", "Unsharp Mask", "Fast Color Corrector", "Brightness & Contrast", "Hue/Saturation", "Sharpen", "Video Limiter", "Timecode".
11
+
12
+ After applying, use get_effect_properties to see the effect's parameters, then set_effect_property or add_keyframe to adjust values.
13
+
14
+ Example: apply_effect({ node_id: "abc123", effect_name: "Gaussian Blur" })
15
15
  Example: apply_effect({ node_id: "abc123", effect_name: "Lumetri Color" })`,
16
16
  parameters: {
17
17
  type: "object",
@@ -28,38 +28,38 @@ Example: apply_effect({ node_id: "abc123", effect_name: "Lumetri Color" })`,
28
28
  required: ["node_id", "effect_name"],
29
29
  },
30
30
  handler: async (args) => {
31
- const script = buildToolScript(`
32
- app.enableQE();
33
- var qeSeq = qe.project.getActiveSequence();
34
- if (!qeSeq) return __error("No active sequence (QE)");
35
-
36
- var result = __findClip("${escapeForExtendScript(args.node_id)}");
37
- if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
38
-
39
- // Find the effect in QE
40
- var effectName = "${escapeForExtendScript(args.effect_name)}";
41
- var qeTrack = result.trackType === "video"
42
- ? qeSeq.getVideoTrackAt(result.trackIndex)
43
- : qeSeq.getAudioTrackAt(result.trackIndex);
44
-
45
- if (!qeTrack) return __error("QE track not found");
46
-
47
- var qeClip = qeTrack.getItemAt(result.clipIndex);
48
- if (!qeClip) return __error("QE clip not found");
49
-
50
- // Search for the effect
51
- var effects = qe.project.getVideoEffectList();
52
- var found = false;
53
- for (var i = 0; i < effects.numItems; i++) {
54
- if (effects[i].name === effectName) {
55
- qeClip.addVideoEffect(effects[i]);
56
- found = true;
57
- break;
58
- }
59
- }
60
-
61
- if (!found) return __error("Effect not found: " + effectName);
62
- return __result({ applied: true, effect: effectName, clipName: result.clip.name });
31
+ const script = buildToolScript(`
32
+ app.enableQE();
33
+ var qeSeq = qe.project.getActiveSequence();
34
+ if (!qeSeq) return __error("No active sequence (QE)");
35
+
36
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
37
+ if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
38
+
39
+ // Find the effect in QE
40
+ var effectName = "${escapeForExtendScript(args.effect_name)}";
41
+ var qeTrack = result.trackType === "video"
42
+ ? qeSeq.getVideoTrackAt(result.trackIndex)
43
+ : qeSeq.getAudioTrackAt(result.trackIndex);
44
+
45
+ if (!qeTrack) return __error("QE track not found");
46
+
47
+ var qeClip = qeTrack.getItemAt(result.clipIndex);
48
+ if (!qeClip) return __error("QE clip not found");
49
+
50
+ // Search for the effect
51
+ var effects = qe.project.getVideoEffectList();
52
+ var found = false;
53
+ for (var i = 0; i < effects.numItems; i++) {
54
+ if (effects[i].name === effectName) {
55
+ qeClip.addVideoEffect(effects[i]);
56
+ found = true;
57
+ break;
58
+ }
59
+ }
60
+
61
+ if (!found) return __error("Effect not found: " + effectName);
62
+ return __result({ applied: true, effect: effectName, clipName: result.clip.name });
63
63
  `);
64
64
  return sendCommand(script, bridgeOptions);
65
65
  },
@@ -81,34 +81,34 @@ Example: apply_effect({ node_id: "abc123", effect_name: "Lumetri Color" })`,
81
81
  required: ["node_id", "effect_name"],
82
82
  },
83
83
  handler: async (args) => {
84
- const script = buildToolScript(`
85
- app.enableQE();
86
- var qeSeq = qe.project.getActiveSequence();
87
- if (!qeSeq) return __error("No active sequence (QE)");
88
-
89
- var result = __findClip("${escapeForExtendScript(args.node_id)}");
90
- if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
91
- if (result.trackType !== "audio") return __error("node_id is not an audio clip");
92
-
93
- var effectName = "${escapeForExtendScript(args.effect_name)}";
94
- var qeTrack = qeSeq.getAudioTrackAt(result.trackIndex);
95
- if (!qeTrack) return __error("QE audio track not found");
96
-
97
- var qeClip = qeTrack.getItemAt(result.clipIndex);
98
- if (!qeClip) return __error("QE clip not found");
99
-
100
- var effects = qe.project.getAudioEffectList();
101
- var found = false;
102
- for (var i = 0; i < effects.numItems; i++) {
103
- if (effects[i].name === effectName) {
104
- qeClip.addAudioEffect(effects[i]);
105
- found = true;
106
- break;
107
- }
108
- }
109
-
110
- if (!found) return __error("Audio effect not found: " + effectName);
111
- return __result({ applied: true, effect: effectName });
84
+ const script = buildToolScript(`
85
+ app.enableQE();
86
+ var qeSeq = qe.project.getActiveSequence();
87
+ if (!qeSeq) return __error("No active sequence (QE)");
88
+
89
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
90
+ if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
91
+ if (result.trackType !== "audio") return __error("node_id is not an audio clip");
92
+
93
+ var effectName = "${escapeForExtendScript(args.effect_name)}";
94
+ var qeTrack = qeSeq.getAudioTrackAt(result.trackIndex);
95
+ if (!qeTrack) return __error("QE audio track not found");
96
+
97
+ var qeClip = qeTrack.getItemAt(result.clipIndex);
98
+ if (!qeClip) return __error("QE clip not found");
99
+
100
+ var effects = qe.project.getAudioEffectList();
101
+ var found = false;
102
+ for (var i = 0; i < effects.numItems; i++) {
103
+ if (effects[i].name === effectName) {
104
+ qeClip.addAudioEffect(effects[i]);
105
+ found = true;
106
+ break;
107
+ }
108
+ }
109
+
110
+ if (!found) return __error("Audio effect not found: " + effectName);
111
+ return __result({ applied: true, effect: effectName });
112
112
  `);
113
113
  return sendCommand(script, bridgeOptions);
114
114
  },
@@ -134,29 +134,29 @@ Example: apply_effect({ node_id: "abc123", effect_name: "Lumetri Color" })`,
134
134
  required: ["node_id"],
135
135
  },
136
136
  handler: async (args) => {
137
- const script = buildToolScript(`
138
- var result = __findClip("${escapeForExtendScript(args.node_id)}");
139
- if (!result) return __error("Clip not found");
140
-
141
- var clip = result.clip;
142
- ${args.effect_index !== undefined ? `
143
- if (${args.effect_index} >= clip.components.numItems) return __error("Effect index out of range");
144
- var effectName = clip.components[${args.effect_index}].displayName;
145
- clip.components[${args.effect_index}].remove();
146
- return __result({ removed: true, effect: effectName });
147
- ` : `
148
- var effectName = "${escapeForExtendScript(args.effect_name || "")}";
149
- var found = false;
150
- for (var i = clip.components.numItems - 1; i >= 0; i--) {
151
- if (clip.components[i].displayName === effectName) {
152
- clip.components[i].remove();
153
- found = true;
154
- break;
155
- }
156
- }
157
- if (!found) return __error("Effect not found: " + effectName);
158
- return __result({ removed: true, effect: effectName });
159
- `}
137
+ const script = buildToolScript(`
138
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
139
+ if (!result) return __error("Clip not found");
140
+
141
+ var clip = result.clip;
142
+ ${args.effect_index !== undefined ? `
143
+ if (${args.effect_index} >= clip.components.numItems) return __error("Effect index out of range");
144
+ var effectName = clip.components[${args.effect_index}].displayName;
145
+ clip.components[${args.effect_index}].remove();
146
+ return __result({ removed: true, effect: effectName });
147
+ ` : `
148
+ var effectName = "${escapeForExtendScript(args.effect_name || "")}";
149
+ var found = false;
150
+ for (var i = clip.components.numItems - 1; i >= 0; i--) {
151
+ if (clip.components[i].displayName === effectName) {
152
+ clip.components[i].remove();
153
+ found = true;
154
+ break;
155
+ }
156
+ }
157
+ if (!found) return __error("Effect not found: " + effectName);
158
+ return __result({ removed: true, effect: effectName });
159
+ `}
160
160
  `);
161
161
  return sendCommand(script, bridgeOptions);
162
162
  },
@@ -283,24 +283,24 @@ Example: apply_effect({ node_id: "abc123", effect_name: "Lumetri Color" })`,
283
283
  },
284
284
  },
285
285
  color_correct: {
286
- description: `Apply color correction to a clip using Lumetri Color. Automatically applies the Lumetri Color effect if not already present, then sets the specified parameters.
287
-
288
- Preconditions: Active sequence required. Clip must exist on the timeline.
289
-
290
- All parameters are optional — only the ones you provide will be changed. Values outside their valid range are clamped by Premiere Pro.
291
-
292
- Parameter ranges:
293
- - exposure: -5.0 to +5.0 (EV stops; 0 = no change)
294
- - contrast: -100 to +100 (0 = no change)
295
- - highlights: -100 to +100
296
- - shadows: -100 to +100
297
- - whites: -100 to +100
298
- - blacks: -100 to +100
299
- - temperature: -100 to +100 (negative = cooler/blue, positive = warmer/orange)
300
- - tint: -100 to +100 (negative = green, positive = magenta)
301
- - saturation: 0 to 200 (100 = no change, 0 = grayscale)
302
-
303
- Example: color_correct({ node_id: "abc123", exposure: 0.5, contrast: 10, saturation: 110 })
286
+ description: `Apply color correction to a clip using Lumetri Color. Automatically applies the Lumetri Color effect if not already present, then sets the specified parameters.
287
+
288
+ Preconditions: Active sequence required. Clip must exist on the timeline.
289
+
290
+ All parameters are optional — only the ones you provide will be changed. Values outside their valid range are clamped by Premiere Pro.
291
+
292
+ Parameter ranges:
293
+ - exposure: -5.0 to +5.0 (EV stops; 0 = no change)
294
+ - contrast: -100 to +100 (0 = no change)
295
+ - highlights: -100 to +100
296
+ - shadows: -100 to +100
297
+ - whites: -100 to +100
298
+ - blacks: -100 to +100
299
+ - temperature: -100 to +100 (negative = cooler/blue, positive = warmer/orange)
300
+ - tint: -100 to +100 (negative = green, positive = magenta)
301
+ - saturation: 0 to 200 (100 = no change, 0 = grayscale)
302
+
303
+ Example: color_correct({ node_id: "abc123", exposure: 0.5, contrast: 10, saturation: 110 })
304
304
  Example: color_correct({ node_id: "abc123", temperature: -20, highlights: -30, shadows: 15 })`,
305
305
  parameters: {
306
306
  type: "object",
@@ -323,98 +323,98 @@ Example: color_correct({ node_id: "abc123", temperature: -20, highlights: -30, s
323
323
  },
324
324
  handler: async (args) => {
325
325
  // First apply Lumetri Color effect, then set its properties
326
- const script = buildToolScript(`
327
- app.enableQE();
328
- var qeSeq = qe.project.getActiveSequence();
329
- if (!qeSeq) return __error("No active sequence (QE)");
330
-
331
- var result = __findClip("${escapeForExtendScript(args.node_id)}");
332
- if (!result) return __error("Clip not found");
333
-
334
- // Apply Lumetri Color if not already present
335
- var clip = result.clip;
336
- var lumetri = __findIntrinsic(clip, "lumetri");
337
-
338
- if (!lumetri) {
339
- var qeTrack = qeSeq.getVideoTrackAt(result.trackIndex);
340
- if (!qeTrack) return __error("QE video track not found");
341
- var qeClip = qeTrack.getItemAt(result.clipIndex);
342
- if (!qeClip) return __error("QE clip not found");
343
- var effects = qe.project.getVideoEffectList();
344
- // qe.project.getVideoEffectList() returns LOCALIZED displayNames in
345
- // non-English Premiere installs (e.g. "Color Lumetri" in es-ES).
346
- // Match against a list of candidate names across locales.
347
- var lumetriNames = ["Lumetri Color", "Color Lumetri", "Lumetri-Farbe", "Couleur Lumetri", "Lumetri カラー", "色彩Lumetri"];
348
- var lumetriEffect = null;
349
- for (var i = 0; i < effects.numItems; i++) {
350
- for (var n = 0; n < lumetriNames.length; n++) {
351
- if (effects[i].name === lumetriNames[n]) {
352
- lumetriEffect = effects[i];
353
- break;
354
- }
355
- }
356
- if (lumetriEffect) break;
357
- }
358
- // Fallback: any effect whose name contains "Lumetri"
359
- if (!lumetriEffect) {
360
- for (var j = 0; j < effects.numItems; j++) {
361
- if (effects[j].name && effects[j].name.indexOf("Lumetri") !== -1) {
362
- lumetriEffect = effects[j];
363
- break;
364
- }
365
- }
366
- }
367
- if (!lumetriEffect) return __error("Lumetri Color effect not found in available video effects");
368
- qeClip.addVideoEffect(lumetriEffect);
369
- // Re-find the component after applying
370
- lumetri = __findIntrinsic(clip, "lumetri");
371
- }
372
-
373
- if (!lumetri) return __error("Could not apply or find Lumetri Color effect");
374
-
375
- // Set Lumetri properties. Params are nested in sub-groups; __findProp
376
- // descends one level to locate them. Each setValue is guarded.
377
- var changes = {};
378
- var failed = {};
379
-
380
- function __setLumetri(displayNames, value, key) {
381
- var prop = __findProp(lumetri, displayNames);
382
- if (!prop) { failed[key] = "property not found"; return; }
383
- try {
384
- prop.setValue(value, true);
385
- changes[key] = value;
386
- } catch (e) {
387
- failed[key] = e.toString();
388
- }
389
- }
390
-
391
- ${args.exposure !== undefined ? `__setLumetri(["Exposure", "Exposición", "Belichtung", "Exposition"], ${args.exposure}, "exposure");` : ""}
392
- ${args.contrast !== undefined ? `__setLumetri(["Contrast", "Contraste", "Kontrast", "Contraste"], ${args.contrast}, "contrast");` : ""}
393
- ${args.highlights !== undefined ? `__setLumetri(["Highlights", "Iluminaciones", "Lichter", "Hautes lumières"], ${args.highlights}, "highlights");` : ""}
394
- ${args.shadows !== undefined ? `__setLumetri(["Shadows", "Sombras", "Schatten", "Ombres"], ${args.shadows}, "shadows");` : ""}
395
- ${args.whites !== undefined ? `__setLumetri(["Whites", "Blancos", "Weißwerte", "Blancs"], ${args.whites}, "whites");` : ""}
396
- ${args.blacks !== undefined ? `__setLumetri(["Blacks", "Negros", "Schwarzwerte", "Noirs"], ${args.blacks}, "blacks");` : ""}
397
- ${args.temperature !== undefined ? `__setLumetri(["Temperature", "Temperatura", "Temperatur", "Température"], ${args.temperature}, "temperature");` : ""}
398
- ${args.tint !== undefined ? `__setLumetri(["Tint", "Matiz", "Farbton", "Teinte"], ${args.tint}, "tint");` : ""}
399
- ${args.saturation !== undefined ? `__setLumetri(["Saturation", "Saturación", "Sättigung", "Saturation"], ${args.saturation}, "saturation");` : ""}
400
-
401
- return __result({ colorCorrected: true, clipName: clip.name, changes: changes, failed: failed });
326
+ const script = buildToolScript(`
327
+ app.enableQE();
328
+ var qeSeq = qe.project.getActiveSequence();
329
+ if (!qeSeq) return __error("No active sequence (QE)");
330
+
331
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
332
+ if (!result) return __error("Clip not found");
333
+
334
+ // Apply Lumetri Color if not already present
335
+ var clip = result.clip;
336
+ var lumetri = __findIntrinsic(clip, "lumetri");
337
+
338
+ if (!lumetri) {
339
+ var qeTrack = qeSeq.getVideoTrackAt(result.trackIndex);
340
+ if (!qeTrack) return __error("QE video track not found");
341
+ var qeClip = qeTrack.getItemAt(result.clipIndex);
342
+ if (!qeClip) return __error("QE clip not found");
343
+ var effects = qe.project.getVideoEffectList();
344
+ // qe.project.getVideoEffectList() returns LOCALIZED displayNames in
345
+ // non-English Premiere installs (e.g. "Color Lumetri" in es-ES).
346
+ // Match against a list of candidate names across locales.
347
+ var lumetriNames = ["Lumetri Color", "Color Lumetri", "Lumetri-Farbe", "Couleur Lumetri", "Lumetri カラー", "色彩Lumetri"];
348
+ var lumetriEffect = null;
349
+ for (var i = 0; i < effects.numItems; i++) {
350
+ for (var n = 0; n < lumetriNames.length; n++) {
351
+ if (effects[i].name === lumetriNames[n]) {
352
+ lumetriEffect = effects[i];
353
+ break;
354
+ }
355
+ }
356
+ if (lumetriEffect) break;
357
+ }
358
+ // Fallback: any effect whose name contains "Lumetri"
359
+ if (!lumetriEffect) {
360
+ for (var j = 0; j < effects.numItems; j++) {
361
+ if (effects[j].name && effects[j].name.indexOf("Lumetri") !== -1) {
362
+ lumetriEffect = effects[j];
363
+ break;
364
+ }
365
+ }
366
+ }
367
+ if (!lumetriEffect) return __error("Lumetri Color effect not found in available video effects");
368
+ qeClip.addVideoEffect(lumetriEffect);
369
+ // Re-find the component after applying
370
+ lumetri = __findIntrinsic(clip, "lumetri");
371
+ }
372
+
373
+ if (!lumetri) return __error("Could not apply or find Lumetri Color effect");
374
+
375
+ // Set Lumetri properties. Params are nested in sub-groups; __findProp
376
+ // descends one level to locate them. Each setValue is guarded.
377
+ var changes = {};
378
+ var failed = {};
379
+
380
+ function __setLumetri(displayNames, value, key) {
381
+ var prop = __findProp(lumetri, displayNames);
382
+ if (!prop) { failed[key] = "property not found"; return; }
383
+ try {
384
+ prop.setValue(value, true);
385
+ changes[key] = value;
386
+ } catch (e) {
387
+ failed[key] = e.toString();
388
+ }
389
+ }
390
+
391
+ ${args.exposure !== undefined ? `__setLumetri(["Exposure", "Exposición", "Belichtung", "Exposition"], ${args.exposure}, "exposure");` : ""}
392
+ ${args.contrast !== undefined ? `__setLumetri(["Contrast", "Contraste", "Kontrast", "Contraste"], ${args.contrast}, "contrast");` : ""}
393
+ ${args.highlights !== undefined ? `__setLumetri(["Highlights", "Iluminaciones", "Lichter", "Hautes lumières"], ${args.highlights}, "highlights");` : ""}
394
+ ${args.shadows !== undefined ? `__setLumetri(["Shadows", "Sombras", "Schatten", "Ombres"], ${args.shadows}, "shadows");` : ""}
395
+ ${args.whites !== undefined ? `__setLumetri(["Whites", "Blancos", "Weißwerte", "Blancs"], ${args.whites}, "whites");` : ""}
396
+ ${args.blacks !== undefined ? `__setLumetri(["Blacks", "Negros", "Schwarzwerte", "Noirs"], ${args.blacks}, "blacks");` : ""}
397
+ ${args.temperature !== undefined ? `__setLumetri(["Temperature", "Temperatura", "Temperatur", "Température"], ${args.temperature}, "temperature");` : ""}
398
+ ${args.tint !== undefined ? `__setLumetri(["Tint", "Matiz", "Farbton", "Teinte"], ${args.tint}, "tint");` : ""}
399
+ ${args.saturation !== undefined ? `__setLumetri(["Saturation", "Saturación", "Sättigung", "Saturation"], ${args.saturation}, "saturation");` : ""}
400
+
401
+ return __result({ colorCorrected: true, clipName: clip.name, changes: changes, failed: failed });
402
402
  `);
403
403
  return sendCommand(script, bridgeOptions);
404
404
  },
405
405
  },
406
406
  apply_lut: {
407
- description: `Apply a LUT (Look-Up Table) file to a clip via Lumetri Color's Creative or Input LUT slot.
408
-
409
- Preconditions: Active sequence required. The LUT file must be accessible on disk at the specified path. Lumetri Color effect is automatically applied to the clip if not already present.
410
-
411
- Supported LUT formats: .cube, .look, .lut, .3dl, .mga.
412
-
413
- Parameters:
414
- - node_id: Clip to apply the LUT to.
415
- - lut_path: Absolute path to the LUT file (e.g., "C:\\LUTs\\rec709.cube" or "/Users/you/LUTs/film.cube").
416
- - lut_strength: Intensity of the LUT effect (0.0 = no effect, 1.0 = full strength, default: 1.0).
417
-
407
+ description: `Apply a LUT (Look-Up Table) file to a clip via Lumetri Color's Creative or Input LUT slot.
408
+
409
+ Preconditions: Active sequence required. The LUT file must be accessible on disk at the specified path. Lumetri Color effect is automatically applied to the clip if not already present.
410
+
411
+ Supported LUT formats: .cube, .look, .lut, .3dl, .mga.
412
+
413
+ Parameters:
414
+ - node_id: Clip to apply the LUT to.
415
+ - lut_path: Absolute path to the LUT file (e.g., "C:\\LUTs\\rec709.cube" or "/Users/you/LUTs/film.cube").
416
+ - lut_strength: Intensity of the LUT effect (0.0 = no effect, 1.0 = full strength, default: 1.0).
417
+
418
418
  Example: apply_lut({ node_id: "abc123", lut_path: "/Users/you/LUTs/orange-teal.cube", lut_strength: 0.8 })`,
419
419
  parameters: {
420
420
  type: "object",
@@ -431,39 +431,39 @@ Example: apply_lut({ node_id: "abc123", lut_path: "/Users/you/LUTs/orange-teal.c
431
431
  required: ["node_id", "lut_path"],
432
432
  },
433
433
  handler: async (args) => {
434
- const script = buildToolScript(`
435
- app.enableQE();
436
- var result = __findClip("${escapeForExtendScript(args.node_id)}");
437
- if (!result) return __error("Clip not found");
438
-
439
- var clip = result.clip;
440
-
441
- // Find or apply Lumetri Color
442
- var lumetriComp = __findIntrinsic(clip, "lumetri");
443
-
444
- if (!lumetriComp) {
445
- var qeSeq = qe.project.getActiveSequence();
446
- var qeTrack = qeSeq.getVideoTrackAt(result.trackIndex);
447
- var qeClip = qeTrack.getItemAt(result.clipIndex);
448
- var effects = qe.project.getVideoEffectList();
449
- for (var i = 0; i < effects.numItems; i++) {
450
- if (effects[i].name === "Lumetri Color") {
451
- qeClip.addVideoEffect(effects[i]);
452
- break;
453
- }
454
- }
455
- // Re-find the component
456
- lumetriComp = __findIntrinsic(clip, "lumetri");
457
- }
458
-
459
- if (!lumetriComp) return __error("Could not apply Lumetri Color effect");
460
-
461
- // Set the LUT path (Input LUT is nested within Lumetri groups)
462
- var lutProp = __findProp(lumetriComp, ["Input LUT", "Creative Look", "LUT de entrada"]);
463
- if (!lutProp) return __error("Input LUT property not found on Lumetri Color");
464
- lutProp.setValue("${escapeForExtendScript(args.lut_path)}", true);
465
-
466
- return __result({ lutApplied: true, clipName: clip.name, lutPath: "${escapeForExtendScript(args.lut_path)}" });
434
+ const script = buildToolScript(`
435
+ app.enableQE();
436
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
437
+ if (!result) return __error("Clip not found");
438
+
439
+ var clip = result.clip;
440
+
441
+ // Find or apply Lumetri Color
442
+ var lumetriComp = __findIntrinsic(clip, "lumetri");
443
+
444
+ if (!lumetriComp) {
445
+ var qeSeq = qe.project.getActiveSequence();
446
+ var qeTrack = qeSeq.getVideoTrackAt(result.trackIndex);
447
+ var qeClip = qeTrack.getItemAt(result.clipIndex);
448
+ var effects = qe.project.getVideoEffectList();
449
+ for (var i = 0; i < effects.numItems; i++) {
450
+ if (effects[i].name === "Lumetri Color") {
451
+ qeClip.addVideoEffect(effects[i]);
452
+ break;
453
+ }
454
+ }
455
+ // Re-find the component
456
+ lumetriComp = __findIntrinsic(clip, "lumetri");
457
+ }
458
+
459
+ if (!lumetriComp) return __error("Could not apply Lumetri Color effect");
460
+
461
+ // Set the LUT path (Input LUT is nested within Lumetri groups)
462
+ var lutProp = __findProp(lumetriComp, ["Input LUT", "Creative Look", "LUT de entrada"]);
463
+ if (!lutProp) return __error("Input LUT property not found on Lumetri Color");
464
+ lutProp.setValue("${escapeForExtendScript(args.lut_path)}", true);
465
+
466
+ return __result({ lutApplied: true, clipName: clip.name, lutPath: "${escapeForExtendScript(args.lut_path)}" });
467
467
  `);
468
468
  return sendCommand(script, bridgeOptions);
469
469
  },
@@ -490,61 +490,61 @@ Example: apply_lut({ node_id: "abc123", lut_path: "/Users/you/LUTs/orange-teal.c
490
490
  required: ["node_id"],
491
491
  },
492
492
  handler: async (args) => {
493
- const script = buildToolScript(`
494
- app.enableQE();
495
- var qeSeq = qe.project.getActiveSequence();
496
- if (!qeSeq) return __error("No active sequence (QE)");
497
-
498
- var result = __findClip("${escapeForExtendScript(args.node_id)}");
499
- if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
500
-
501
- var qeTrack = result.trackType === "video"
502
- ? qeSeq.getVideoTrackAt(result.trackIndex)
503
- : null;
504
- if (!qeTrack) return __error("Warp Stabilizer can only be applied to video clips");
505
-
506
- var qeClip = qeTrack.getItemAt(result.clipIndex);
507
- if (!qeClip) return __error("QE clip not found");
508
-
509
- // Find and apply Warp Stabilizer
510
- var effects = qe.project.getVideoEffectList();
511
- var found = false;
512
- for (var i = 0; i < effects.numItems; i++) {
513
- if (effects[i].name === "Warp Stabilizer") {
514
- qeClip.addVideoEffect(effects[i]);
515
- found = true;
516
- break;
517
- }
518
- }
519
-
520
- if (!found) return __error("Warp Stabilizer effect not found");
521
-
522
- // Set properties if specified
523
- var clip = result.clip;
524
- var changes = { stabilized: true };
525
- ${args.smoothness !== undefined || args.method !== undefined ? `
526
- for (var i = 0; i < clip.components.numItems; i++) {
527
- var comp = clip.components[i];
528
- if (comp.displayName === "Warp Stabilizer") {
529
- for (var p = 0; p < comp.properties.numItems; p++) {
530
- var prop = comp.properties[p];
531
- ${args.smoothness !== undefined ? `
532
- if (prop.displayName === "Smoothness") {
533
- prop.setValue(${args.smoothness}, true);
534
- changes.smoothness = ${args.smoothness};
535
- }` : ""}
536
- ${args.method !== undefined ? `
537
- if (prop.displayName === "Method") {
538
- prop.setValue("${escapeForExtendScript(args.method)}", true);
539
- changes.method = "${escapeForExtendScript(args.method)}";
540
- }` : ""}
541
- }
542
- break;
543
- }
544
- }
545
- ` : ""}
546
-
547
- return __result({ clipName: clip.name, info: "Warp Stabilizer applied. Analysis will begin automatically.", changes: changes });
493
+ const script = buildToolScript(`
494
+ app.enableQE();
495
+ var qeSeq = qe.project.getActiveSequence();
496
+ if (!qeSeq) return __error("No active sequence (QE)");
497
+
498
+ var result = __findClip("${escapeForExtendScript(args.node_id)}");
499
+ if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
500
+
501
+ var qeTrack = result.trackType === "video"
502
+ ? qeSeq.getVideoTrackAt(result.trackIndex)
503
+ : null;
504
+ if (!qeTrack) return __error("Warp Stabilizer can only be applied to video clips");
505
+
506
+ var qeClip = qeTrack.getItemAt(result.clipIndex);
507
+ if (!qeClip) return __error("QE clip not found");
508
+
509
+ // Find and apply Warp Stabilizer
510
+ var effects = qe.project.getVideoEffectList();
511
+ var found = false;
512
+ for (var i = 0; i < effects.numItems; i++) {
513
+ if (effects[i].name === "Warp Stabilizer") {
514
+ qeClip.addVideoEffect(effects[i]);
515
+ found = true;
516
+ break;
517
+ }
518
+ }
519
+
520
+ if (!found) return __error("Warp Stabilizer effect not found");
521
+
522
+ // Set properties if specified
523
+ var clip = result.clip;
524
+ var changes = { stabilized: true };
525
+ ${args.smoothness !== undefined || args.method !== undefined ? `
526
+ for (var i = 0; i < clip.components.numItems; i++) {
527
+ var comp = clip.components[i];
528
+ if (comp.displayName === "Warp Stabilizer") {
529
+ for (var p = 0; p < comp.properties.numItems; p++) {
530
+ var prop = comp.properties[p];
531
+ ${args.smoothness !== undefined ? `
532
+ if (prop.displayName === "Smoothness") {
533
+ prop.setValue(${args.smoothness}, true);
534
+ changes.smoothness = ${args.smoothness};
535
+ }` : ""}
536
+ ${args.method !== undefined ? `
537
+ if (prop.displayName === "Method") {
538
+ prop.setValue("${escapeForExtendScript(args.method)}", true);
539
+ changes.method = "${escapeForExtendScript(args.method)}";
540
+ }` : ""}
541
+ }
542
+ break;
543
+ }
544
+ }
545
+ ` : ""}
546
+
547
+ return __result({ clipName: clip.name, info: "Warp Stabilizer applied. Analysis will begin automatically.", changes: changes });
548
548
  `);
549
549
  return sendCommand(script, bridgeOptions);
550
550
  },