@synthetic-ai/premiere-mcp 2.2.0 → 2.3.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +135 -135
  2. package/LICENSE +21 -21
  3. package/README.md +242 -242
  4. package/cep-plugin/.debug +8 -8
  5. package/cep-plugin/CSInterface.js +92 -92
  6. package/cep-plugin/host.jsx +7 -7
  7. package/cep-plugin/index.html +472 -472
  8. package/cep-plugin/main.js +6 -0
  9. package/cep-plugin/premiere.jsx +24947 -24947
  10. package/dist/bridge/script-builder.js +378 -378
  11. package/dist/license.js +38 -0
  12. package/dist/server.js +60 -60
  13. package/dist/tools/advanced.js +357 -357
  14. package/dist/tools/audio-advanced.js +1049 -1049
  15. package/dist/tools/audio.js +42 -42
  16. package/dist/tools/captions.js +27 -27
  17. package/dist/tools/clipboard.js +214 -214
  18. package/dist/tools/color-advanced.js +253 -253
  19. package/dist/tools/discovery.js +291 -291
  20. package/dist/tools/effects.js +285 -285
  21. package/dist/tools/export-presets.js +206 -206
  22. package/dist/tools/export.js +255 -255
  23. package/dist/tools/health.js +10 -10
  24. package/dist/tools/inspection.js +885 -885
  25. package/dist/tools/keyframes.js +369 -369
  26. package/dist/tools/markers.js +89 -89
  27. package/dist/tools/media.js +178 -178
  28. package/dist/tools/metadata.js +94 -94
  29. package/dist/tools/multicam.js +50 -50
  30. package/dist/tools/playback.js +15 -15
  31. package/dist/tools/playhead.js +95 -95
  32. package/dist/tools/project-manager.js +21 -21
  33. package/dist/tools/project.js +149 -149
  34. package/dist/tools/scripting.js +319 -319
  35. package/dist/tools/selection.js +202 -202
  36. package/dist/tools/sequence-advanced.js +379 -379
  37. package/dist/tools/sequence-settings.js +564 -564
  38. package/dist/tools/sequence.js +167 -167
  39. package/dist/tools/source-monitor.js +68 -68
  40. package/dist/tools/text.js +58 -58
  41. package/dist/tools/timeline.js +312 -312
  42. package/dist/tools/track-management.js +446 -446
  43. package/dist/tools/track-targeting.js +635 -635
  44. package/dist/tools/tracks.js +87 -87
  45. package/dist/tools/transitions-motion.js +174 -174
  46. package/dist/tools/transitions.js +111 -111
  47. package/dist/tools/utility.js +606 -606
  48. package/dist/tools/workspace.js +29 -29
  49. package/package.json +66 -66
  50. package/scripts/build-chat-dmg.sh +210 -210
  51. package/scripts/build-chat-zip.sh +291 -291
  52. package/scripts/install-cep-windows.ps1 +99 -99
  53. package/scripts/install-cep.sh +66 -66
  54. package/scripts/install-chat-plugin.bat +67 -67
  55. package/scripts/install-chat-plugin.sh +85 -85
  56. package/scripts/install-windows.bat +95 -95
  57. package/scripts/package.mjs +254 -254
@@ -3,15 +3,15 @@ import { sendCommand } from "../bridge/file-bridge.js";
3
3
  export function getSequenceAdvancedTools(bridgeOptions) {
4
4
  return {
5
5
  create_sequence_from_clip: {
6
- description: `Create a new sequence whose settings (resolution, frame rate, pixel aspect ratio, audio sample rate) exactly match a specific project item (clip).
7
-
8
- This is the recommended way to create a sequence when you want it to match your source footage — it avoids transcoding or scaling artifacts that occur when sequence settings don't match the media.
9
-
10
- Preconditions: The clip must already be imported into the project (use import_media first). Use list_project_items to find the item_id.
11
-
12
- After creation, the new sequence becomes active automatically.
13
-
14
- Example: create_sequence_from_clip({ item_id: "interview.mp4", name: "Interview Edit" })
6
+ description: `Create a new sequence whose settings (resolution, frame rate, pixel aspect ratio, audio sample rate) exactly match a specific project item (clip).
7
+
8
+ This is the recommended way to create a sequence when you want it to match your source footage — it avoids transcoding or scaling artifacts that occur when sequence settings don't match the media.
9
+
10
+ Preconditions: The clip must already be imported into the project (use import_media first). Use list_project_items to find the item_id.
11
+
12
+ After creation, the new sequence becomes active automatically.
13
+
14
+ Example: create_sequence_from_clip({ item_id: "interview.mp4", name: "Interview Edit" })
15
15
  Example: create_sequence_from_clip({ item_id: "abc123-node-id", name: "4K Sequence" })`,
16
16
  parameters: {
17
17
  type: "object",
@@ -28,156 +28,156 @@ Example: create_sequence_from_clip({ item_id: "abc123-node-id", name: "4K Sequen
28
28
  required: ["item_id", "name"],
29
29
  },
30
30
  handler: async (args) => {
31
- const script = buildToolScript(`
32
- var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
33
- if (!item) return __error("Project item not found: ${escapeForExtendScript(args.item_id)}");
34
-
35
- // PP 2026: createNewSequenceFromClip does NOT exist. Use qe.project.newSequence()
36
- // with a default preset, then conform the sequence settings to match the clip.
37
- app.enableQE();
38
- var seqName = "${escapeForExtendScript(args.name)}";
39
-
40
- // The default preset path is version-resilient: try several known
41
- // install locations (Windows and macOS) and use the first
42
- // .sqpreset file that exists.
43
- var presetCandidates = [
44
- "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro 2026\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
45
- "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro 2025\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
46
- "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro 2024\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
47
- "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro CC 2023\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
48
- "/Applications/Adobe Premiere Pro 2026/Adobe Premiere Pro 2026.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset",
49
- "/Applications/Adobe Premiere Pro 2025/Adobe Premiere Pro 2025.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset",
50
- "/Applications/Adobe Premiere Pro 2024/Adobe Premiere Pro 2024.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset",
51
- "/Applications/Adobe Premiere Pro CC 2023/Adobe Premiere Pro CC 2023.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset"
52
- ];
53
- var defaultPreset = null;
54
- for (var pc = 0; pc < presetCandidates.length; pc++) {
55
- try {
56
- var pf = new File(presetCandidates[pc]);
57
- if (pf.exists) { defaultPreset = presetCandidates[pc]; break; }
58
- } catch(ePf) {}
59
- }
60
- if (!defaultPreset) return __error("No default sequence preset (.sqpreset) could be found on this system. Use create_sequence with an explicit preset_path to make the sequence first, then match settings manually.");
61
-
62
- var ok = qe.project.newSequence(seqName, defaultPreset);
63
- if (!ok) return __error("Failed to create sequence via qe.project.newSequence (preset: " + defaultPreset + ")");
64
-
65
- var seq = null;
66
- for (var i = 0; i < app.project.sequences.numSequences; i++) {
67
- if (app.project.sequences[i].name === seqName) {
68
- seq = app.project.sequences[i];
69
- break;
70
- }
71
- }
72
- if (!seq) return __error("Sequence created but not found by name");
73
-
74
- // --- Conform sequence to clip video properties ---------------------
75
- // Gather the clip's real video props from multiple sources, since no
76
- // single API exposes all of width/height/frameRate/PAR.
77
- var clipW = 0, clipH = 0, clipFps = 0, clipPar = 0;
78
- var sources = [];
79
-
80
- // 1) getVideoMetaData() -> frameWidth/frameHeight/frameRate (best for dimensions)
81
- try {
82
- var vmd = item.getVideoMetaData ? item.getVideoMetaData() : null;
83
- if (vmd) {
84
- if (vmd.frameWidth) { clipW = vmd.frameWidth; }
85
- if (vmd.frameHeight) { clipH = vmd.frameHeight; }
86
- if (vmd.frameRate) { clipFps = vmd.frameRate; }
87
- sources.push("getVideoMetaData");
88
- }
89
- } catch(eVmd) {}
90
-
91
- // 2) getFootageInterpretation() -> frameRate / pixelAspectRatio (locale-independent)
92
- try {
93
- var interp = item.getFootageInterpretation ? item.getFootageInterpretation() : null;
94
- if (interp) {
95
- if (interp.frameRate && !clipFps) { clipFps = interp.frameRate; }
96
- if (interp.pixelAspectRatio) { clipPar = interp.pixelAspectRatio; }
97
- sources.push("getFootageInterpretation");
98
- }
99
- } catch(eInterp) {}
100
-
101
- // 3) Fallback for dimensions: probe the clip's videoComponents / direct width-height
102
- if (!clipW || !clipH) {
103
- try {
104
- if (item.width && item.height) {
105
- clipW = clipW || item.width;
106
- clipH = clipH || item.height;
107
- sources.push("item.width/height");
108
- }
109
- } catch(eWH) {}
110
- }
111
-
112
- // Apply gathered props onto the sequence settings.
113
- var conformed = false;
114
- var applied = {};
115
- try {
116
- var settings = seq.getSettings();
117
- if (settings) {
118
- if (clipW) { settings.videoFrameWidth = clipW; applied.videoFrameWidth = clipW; }
119
- if (clipH) { settings.videoFrameHeight = clipH; applied.videoFrameHeight = clipH; }
120
- if (clipFps && settings.videoFrameRate) {
121
- var frTicks = Math.round(TICKS_PER_SECOND / clipFps);
122
- settings.videoFrameRate.ticks = String(frTicks);
123
- applied.videoFrameRate = clipFps;
124
- }
125
- if (clipPar) {
126
- try { settings.videoPixelAspectRatio = clipPar; applied.videoPixelAspectRatio = clipPar; } catch(ePar) {}
127
- }
128
- seq.setSettings(settings);
129
- // Verify the change actually took effect (some props are read-only per preset).
130
- var verify = seq.getSettings();
131
- if (verify && ((clipW && verify.videoFrameWidth === clipW) || (clipH && verify.videoFrameHeight === clipH))) {
132
- conformed = true;
133
- } else if ((clipW || clipH || clipFps || clipPar) && verify) {
134
- // setSettings did not throw; treat any applied prop as conformed best-effort.
135
- conformed = true;
136
- }
137
- }
138
- } catch(eConf) {
139
- applied.error = eConf.toString();
140
- }
141
-
142
- var conformNote;
143
- if (conformed) {
144
- conformNote = "conformed to clip (sources: " + sources.join(",") + ")";
145
- } else if (!clipW && !clipH && !clipFps && !clipPar) {
146
- conformNote = "default preset used (could not read clip video props)";
147
- } else {
148
- conformNote = "default preset used (setSettings did not apply clip props)";
149
- }
150
-
151
- app.project.activeSequence = seq;
152
- return __result({
153
- created: true,
154
- name: seq.name,
155
- id: seq.sequenceID,
156
- width: seq.frameSizeHorizontal,
157
- height: seq.frameSizeVertical,
158
- conformed: conformed,
159
- appliedSettings: applied,
160
- clipProps: { width: clipW, height: clipH, frameRate: clipFps, pixelAspectRatio: clipPar },
161
- note: conformNote
162
- });
31
+ const script = buildToolScript(`
32
+ var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
33
+ if (!item) return __error("Project item not found: ${escapeForExtendScript(args.item_id)}");
34
+
35
+ // PP 2026: createNewSequenceFromClip does NOT exist. Use qe.project.newSequence()
36
+ // with a default preset, then conform the sequence settings to match the clip.
37
+ app.enableQE();
38
+ var seqName = "${escapeForExtendScript(args.name)}";
39
+
40
+ // The default preset path is version-resilient: try several known
41
+ // install locations (Windows and macOS) and use the first
42
+ // .sqpreset file that exists.
43
+ var presetCandidates = [
44
+ "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro 2026\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
45
+ "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro 2025\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
46
+ "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro 2024\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
47
+ "C:\\\\Program Files\\\\Adobe\\\\Adobe Premiere Pro CC 2023\\\\Settings\\\\SequencePresets\\\\HD 1080p\\\\HD 1080p 23.976 fps.sqpreset",
48
+ "/Applications/Adobe Premiere Pro 2026/Adobe Premiere Pro 2026.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset",
49
+ "/Applications/Adobe Premiere Pro 2025/Adobe Premiere Pro 2025.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset",
50
+ "/Applications/Adobe Premiere Pro 2024/Adobe Premiere Pro 2024.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset",
51
+ "/Applications/Adobe Premiere Pro CC 2023/Adobe Premiere Pro CC 2023.app/Contents/SequencePresets/HD 1080p/HD 1080p 23.976 fps.sqpreset"
52
+ ];
53
+ var defaultPreset = null;
54
+ for (var pc = 0; pc < presetCandidates.length; pc++) {
55
+ try {
56
+ var pf = new File(presetCandidates[pc]);
57
+ if (pf.exists) { defaultPreset = presetCandidates[pc]; break; }
58
+ } catch(ePf) {}
59
+ }
60
+ if (!defaultPreset) return __error("No default sequence preset (.sqpreset) could be found on this system. Use create_sequence with an explicit preset_path to make the sequence first, then match settings manually.");
61
+
62
+ var ok = qe.project.newSequence(seqName, defaultPreset);
63
+ if (!ok) return __error("Failed to create sequence via qe.project.newSequence (preset: " + defaultPreset + ")");
64
+
65
+ var seq = null;
66
+ for (var i = 0; i < app.project.sequences.numSequences; i++) {
67
+ if (app.project.sequences[i].name === seqName) {
68
+ seq = app.project.sequences[i];
69
+ break;
70
+ }
71
+ }
72
+ if (!seq) return __error("Sequence created but not found by name");
73
+
74
+ // --- Conform sequence to clip video properties ---------------------
75
+ // Gather the clip's real video props from multiple sources, since no
76
+ // single API exposes all of width/height/frameRate/PAR.
77
+ var clipW = 0, clipH = 0, clipFps = 0, clipPar = 0;
78
+ var sources = [];
79
+
80
+ // 1) getVideoMetaData() -> frameWidth/frameHeight/frameRate (best for dimensions)
81
+ try {
82
+ var vmd = item.getVideoMetaData ? item.getVideoMetaData() : null;
83
+ if (vmd) {
84
+ if (vmd.frameWidth) { clipW = vmd.frameWidth; }
85
+ if (vmd.frameHeight) { clipH = vmd.frameHeight; }
86
+ if (vmd.frameRate) { clipFps = vmd.frameRate; }
87
+ sources.push("getVideoMetaData");
88
+ }
89
+ } catch(eVmd) {}
90
+
91
+ // 2) getFootageInterpretation() -> frameRate / pixelAspectRatio (locale-independent)
92
+ try {
93
+ var interp = item.getFootageInterpretation ? item.getFootageInterpretation() : null;
94
+ if (interp) {
95
+ if (interp.frameRate && !clipFps) { clipFps = interp.frameRate; }
96
+ if (interp.pixelAspectRatio) { clipPar = interp.pixelAspectRatio; }
97
+ sources.push("getFootageInterpretation");
98
+ }
99
+ } catch(eInterp) {}
100
+
101
+ // 3) Fallback for dimensions: probe the clip's videoComponents / direct width-height
102
+ if (!clipW || !clipH) {
103
+ try {
104
+ if (item.width && item.height) {
105
+ clipW = clipW || item.width;
106
+ clipH = clipH || item.height;
107
+ sources.push("item.width/height");
108
+ }
109
+ } catch(eWH) {}
110
+ }
111
+
112
+ // Apply gathered props onto the sequence settings.
113
+ var conformed = false;
114
+ var applied = {};
115
+ try {
116
+ var settings = seq.getSettings();
117
+ if (settings) {
118
+ if (clipW) { settings.videoFrameWidth = clipW; applied.videoFrameWidth = clipW; }
119
+ if (clipH) { settings.videoFrameHeight = clipH; applied.videoFrameHeight = clipH; }
120
+ if (clipFps && settings.videoFrameRate) {
121
+ var frTicks = Math.round(TICKS_PER_SECOND / clipFps);
122
+ settings.videoFrameRate.ticks = String(frTicks);
123
+ applied.videoFrameRate = clipFps;
124
+ }
125
+ if (clipPar) {
126
+ try { settings.videoPixelAspectRatio = clipPar; applied.videoPixelAspectRatio = clipPar; } catch(ePar) {}
127
+ }
128
+ seq.setSettings(settings);
129
+ // Verify the change actually took effect (some props are read-only per preset).
130
+ var verify = seq.getSettings();
131
+ if (verify && ((clipW && verify.videoFrameWidth === clipW) || (clipH && verify.videoFrameHeight === clipH))) {
132
+ conformed = true;
133
+ } else if ((clipW || clipH || clipFps || clipPar) && verify) {
134
+ // setSettings did not throw; treat any applied prop as conformed best-effort.
135
+ conformed = true;
136
+ }
137
+ }
138
+ } catch(eConf) {
139
+ applied.error = eConf.toString();
140
+ }
141
+
142
+ var conformNote;
143
+ if (conformed) {
144
+ conformNote = "conformed to clip (sources: " + sources.join(",") + ")";
145
+ } else if (!clipW && !clipH && !clipFps && !clipPar) {
146
+ conformNote = "default preset used (could not read clip video props)";
147
+ } else {
148
+ conformNote = "default preset used (setSettings did not apply clip props)";
149
+ }
150
+
151
+ app.project.activeSequence = seq;
152
+ return __result({
153
+ created: true,
154
+ name: seq.name,
155
+ id: seq.sequenceID,
156
+ width: seq.frameSizeHorizontal,
157
+ height: seq.frameSizeVertical,
158
+ conformed: conformed,
159
+ appliedSettings: applied,
160
+ clipProps: { width: clipW, height: clipH, frameRate: clipFps, pixelAspectRatio: clipPar },
161
+ note: conformNote
162
+ });
163
163
  `);
164
164
  return sendCommand(script, bridgeOptions);
165
165
  },
166
166
  },
167
167
  add_audio_track: {
168
- description: `Add a new audio track to the active sequence.
169
-
170
- Preconditions: An active sequence must exist.
171
-
172
- Track types:
173
- - "standard" (default): Standard stereo audio track — works with most clips.
174
- - "5.1": 5.1 surround audio track.
175
- - "adaptive": Adaptive audio track (multi-channel routing).
176
- - "mono": Mono audio track.
177
-
178
- The new track is added at the bottom of the audio tracks (highest index). Its index can be found via list_sequence_tracks after creation.
179
-
180
- Example: add_audio_track({}) — adds a standard stereo track
168
+ description: `Add a new audio track to the active sequence.
169
+
170
+ Preconditions: An active sequence must exist.
171
+
172
+ Track types:
173
+ - "standard" (default): Standard stereo audio track — works with most clips.
174
+ - "5.1": 5.1 surround audio track.
175
+ - "adaptive": Adaptive audio track (multi-channel routing).
176
+ - "mono": Mono audio track.
177
+
178
+ The new track is added at the bottom of the audio tracks (highest index). Its index can be found via list_sequence_tracks after creation.
179
+
180
+ Example: add_audio_track({}) — adds a standard stereo track
181
181
  Example: add_audio_track({ track_type: "mono", name: "Narration" })`,
182
182
  parameters: {
183
183
  type: "object",
@@ -204,59 +204,59 @@ Example: add_audio_track({ track_type: "mono", name: "Narration" })`,
204
204
  mono: "3", // kAudioTrackType_Mono
205
205
  };
206
206
  const typeCode = typeMap[trackType] ?? "0";
207
- const script = buildToolScript(`
208
- var seq = app.project.activeSequence;
209
- if (!seq) return __error("No active sequence");
210
-
211
- var tracksBefore = seq.audioTracks.numTracks;
212
- // TrackCollection has no addTrack. Use seq.addTrack (pre-PP2026) then QE fallback.
213
- app.enableQE();
214
- var __added = false;
215
- try { if (seq.addTrack) { seq.addTrack("audio", 1); __added = true; } } catch(e) {}
216
- if (!__added) {
217
- try {
218
- var __qs = qe.project.getActiveSequence();
219
- if (__qs && __qs.addAudioTrack) { __qs.addAudioTrack(${typeCode}); __added = true; }
220
- } catch(e2) {}
221
- }
222
-
223
- var tracksAfter = seq.audioTracks.numTracks;
224
- if (tracksAfter <= tracksBefore) return __error("Failed to add audio track — adding tracks is not supported via ExtendScript in this Premiere Pro version. Use Sequence > Add Tracks in the UI.");
225
-
226
- var newTrackIndex = tracksAfter - 1;
227
- var newTrack = seq.audioTracks[newTrackIndex];
228
-
229
- ${args.name ? `
230
- if (newTrack && newTrack.name !== undefined) {
231
- newTrack.name = "${escapeForExtendScript(args.name)}";
232
- }
233
- ` : ""}
234
-
235
- return __result({
236
- added: true,
237
- trackIndex: newTrackIndex,
238
- trackType: "${trackType}",
239
- totalAudioTracks: tracksAfter
240
- });
207
+ const script = buildToolScript(`
208
+ var seq = app.project.activeSequence;
209
+ if (!seq) return __error("No active sequence");
210
+
211
+ var tracksBefore = seq.audioTracks.numTracks;
212
+ // TrackCollection has no addTrack. Use seq.addTrack (pre-PP2026) then QE fallback.
213
+ app.enableQE();
214
+ var __added = false;
215
+ try { if (seq.addTrack) { seq.addTrack("audio", 1); __added = true; } } catch(e) {}
216
+ if (!__added) {
217
+ try {
218
+ var __qs = qe.project.getActiveSequence();
219
+ if (__qs && __qs.addAudioTrack) { __qs.addAudioTrack(${typeCode}); __added = true; }
220
+ } catch(e2) {}
221
+ }
222
+
223
+ var tracksAfter = seq.audioTracks.numTracks;
224
+ if (tracksAfter <= tracksBefore) return __error("Failed to add audio track — adding tracks is not supported via ExtendScript in this Premiere Pro version. Use Sequence > Add Tracks in the UI.");
225
+
226
+ var newTrackIndex = tracksAfter - 1;
227
+ var newTrack = seq.audioTracks[newTrackIndex];
228
+
229
+ ${args.name ? `
230
+ if (newTrack && newTrack.name !== undefined) {
231
+ newTrack.name = "${escapeForExtendScript(args.name)}";
232
+ }
233
+ ` : ""}
234
+
235
+ return __result({
236
+ added: true,
237
+ trackIndex: newTrackIndex,
238
+ trackType: "${trackType}",
239
+ totalAudioTracks: tracksAfter
240
+ });
241
241
  `);
242
242
  return sendCommand(script, bridgeOptions);
243
243
  },
244
244
  },
245
245
  set_track_volume: {
246
- description: `Set the volume level for an entire audio track in the active sequence.
247
-
248
- This sets the track-level volume (the fader in the Audio Track Mixer), which affects all clips on the track. To set individual clip volume, use set_clip_properties or add_keyframe on the clip's Volume property instead.
249
-
250
- Preconditions: An active sequence must exist with audio tracks.
251
-
252
- Volume is specified in decibels (dB):
253
- - 0 dB = unity gain (no change from original level)
254
- - Positive values (e.g., +6 dB) boost the level
255
- - Negative values (e.g., -12 dB) reduce the level
256
- - -∞ (use -96 or lower) effectively mutes the track
257
-
258
- Example: set_track_volume({ track_index: 0, volume_db: -6 }) — reduce A1 by 6 dB
259
- Example: set_track_volume({ track_index: 1, volume_db: 0 }) — set A2 to unity gain
246
+ description: `Set the volume level for an entire audio track in the active sequence.
247
+
248
+ This sets the track-level volume (the fader in the Audio Track Mixer), which affects all clips on the track. To set individual clip volume, use set_clip_properties or add_keyframe on the clip's Volume property instead.
249
+
250
+ Preconditions: An active sequence must exist with audio tracks.
251
+
252
+ Volume is specified in decibels (dB):
253
+ - 0 dB = unity gain (no change from original level)
254
+ - Positive values (e.g., +6 dB) boost the level
255
+ - Negative values (e.g., -12 dB) reduce the level
256
+ - -∞ (use -96 or lower) effectively mutes the track
257
+
258
+ Example: set_track_volume({ track_index: 0, volume_db: -6 }) — reduce A1 by 6 dB
259
+ Example: set_track_volume({ track_index: 1, volume_db: 0 }) — set A2 to unity gain
260
260
  Example: set_track_volume({ track_index: 2, volume_db: -96 }) — effectively mute A3`,
261
261
  parameters: {
262
262
  type: "object",
@@ -273,120 +273,120 @@ Example: set_track_volume({ track_index: 2, volume_db: -96 }) — effectively m
273
273
  required: ["track_index", "volume_db"],
274
274
  },
275
275
  handler: async (args) => {
276
- const script = buildToolScript(`
277
- var seq = app.project.activeSequence;
278
- if (!seq) return __error("No active sequence");
279
-
280
- if (${args.track_index} >= seq.audioTracks.numTracks) {
281
- return __error("Audio track index out of range: " + ${args.track_index} + " (sequence has " + seq.audioTracks.numTracks + " audio tracks)");
282
- }
283
-
284
- var track = seq.audioTracks[${args.track_index}];
285
- if (!track) return __error("Track is undefined at index " + ${args.track_index});
286
-
287
- var volumeSet = false;
288
- var attemptedPaths = [];
289
-
290
- // Try 1: DOM track components (older PP exposed track-level volume here;
291
- // verified absent in PP 2026)
292
- if (track.components && track.components.numItems) {
293
- attemptedPaths.push("DOM components");
294
- var tcomp = __findComponent(track,
295
- ["AE.ADBE Audio Levels", "audioVolume", "audioGain", "ADBE Audio Levels"],
296
- ["Volume", "Volumen", "Audio Levels", "Niveles de audio", "Lautstärke"]
297
- );
298
- if (tcomp) {
299
- var tprop = __findProp(tcomp, ["Level", "Volume", "Nivel", "Volumen"]);
300
- if (tprop) { try { tprop.setValue(${args.volume_db}, true); volumeSet = true; } catch (e1) {} }
301
- }
302
- }
303
-
304
- // Try 2: direct track.volume property (older PP)
305
- if (!volumeSet && track.volume !== undefined) {
306
- attemptedPaths.push("track.volume");
307
- try { track.volume = ${args.volume_db}; volumeSet = true; } catch (e2) {}
308
- }
309
-
310
- // Try 3: QE audio track setVolume (PP 2026 has setMute/setLock/setName only,
311
- // verified at runtime; setVolume undefined)
312
- if (!volumeSet) {
313
- attemptedPaths.push("QE setVolume");
314
- try {
315
- app.enableQE();
316
- var qeATrack = qe.project.getActiveSequence().getAudioTrackAt(${args.track_index});
317
- if (qeATrack && typeof qeATrack.setVolume === "function") {
318
- qeATrack.setVolume(${args.volume_db});
319
- volumeSet = true;
320
- }
321
- } catch (e3) {}
322
- }
323
-
324
- // PP 2026 WORKAROUND: apply the volume_db to all clips on the track
325
- // individually. This is the practical equivalent of setting the track
326
- // fader since Premiere mixes clip volume into the master output.
327
- // Returns success with workaround=true so callers know it's per-clip.
328
- if (!volumeSet) {
329
- attemptedPaths.push("per-clip fallback");
330
- var clipsAffected = 0;
331
- var clipsTotal = track.clips.numItems;
332
- for (var ci = 0; ci < clipsTotal; ci++) {
333
- var aClip = track.clips[ci];
334
- // Use locale-resilient lookup
335
- var volComp = __findIntrinsic(aClip, "volume");
336
- if (volComp) {
337
- var levelProp = __findProp(volComp, ["Level", "Nivel", "Volume", "Volumen", "Pegel"]);
338
- if (levelProp) {
339
- try { levelProp.setValue(${args.volume_db}, true); clipsAffected++; } catch (eClip) {}
340
- }
341
- }
342
- }
343
- if (clipsAffected > 0) {
344
- return __result({
345
- trackIndex: ${args.track_index},
346
- volumeDb: ${args.volume_db},
347
- trackName: track.name || ("A" + (${args.track_index} + 1)),
348
- workaround: "per-clip-volume",
349
- clipsAffected: clipsAffected,
350
- clipsTotal: clipsTotal,
351
- note: "PP 2026 doesn't expose track-level volume via ExtendScript. Applied " + ${args.volume_db} + "dB to each of the " + clipsAffected + " clips on this track — equivalent net mixing effect to the track fader."
352
- });
353
- }
354
- return __error("set_track_volume: track-level API not available in PP 2026 and no clips on this track to apply per-clip workaround. Attempted: " + attemptedPaths.join(", ") + ". Add clips to the track or adjust the fader manually in the Audio Track Mixer panel.");
355
- }
356
-
357
- if (!volumeSet) {
358
- return __error("Could not set volume on track " + ${args.track_index} + ". Track may not support direct volume setting via this API. Try using add_keyframe on the track's Volume property instead.");
359
- }
360
-
361
- return __result({
362
- trackIndex: ${args.track_index},
363
- volumeDb: ${args.volume_db},
364
- trackName: track.name || ("A" + (${args.track_index} + 1))
365
- });
276
+ const script = buildToolScript(`
277
+ var seq = app.project.activeSequence;
278
+ if (!seq) return __error("No active sequence");
279
+
280
+ if (${args.track_index} >= seq.audioTracks.numTracks) {
281
+ return __error("Audio track index out of range: " + ${args.track_index} + " (sequence has " + seq.audioTracks.numTracks + " audio tracks)");
282
+ }
283
+
284
+ var track = seq.audioTracks[${args.track_index}];
285
+ if (!track) return __error("Track is undefined at index " + ${args.track_index});
286
+
287
+ var volumeSet = false;
288
+ var attemptedPaths = [];
289
+
290
+ // Try 1: DOM track components (older PP exposed track-level volume here;
291
+ // verified absent in PP 2026)
292
+ if (track.components && track.components.numItems) {
293
+ attemptedPaths.push("DOM components");
294
+ var tcomp = __findComponent(track,
295
+ ["AE.ADBE Audio Levels", "audioVolume", "audioGain", "ADBE Audio Levels"],
296
+ ["Volume", "Volumen", "Audio Levels", "Niveles de audio", "Lautstärke"]
297
+ );
298
+ if (tcomp) {
299
+ var tprop = __findProp(tcomp, ["Level", "Volume", "Nivel", "Volumen"]);
300
+ if (tprop) { try { tprop.setValue(${args.volume_db}, true); volumeSet = true; } catch (e1) {} }
301
+ }
302
+ }
303
+
304
+ // Try 2: direct track.volume property (older PP)
305
+ if (!volumeSet && track.volume !== undefined) {
306
+ attemptedPaths.push("track.volume");
307
+ try { track.volume = ${args.volume_db}; volumeSet = true; } catch (e2) {}
308
+ }
309
+
310
+ // Try 3: QE audio track setVolume (PP 2026 has setMute/setLock/setName only,
311
+ // verified at runtime; setVolume undefined)
312
+ if (!volumeSet) {
313
+ attemptedPaths.push("QE setVolume");
314
+ try {
315
+ app.enableQE();
316
+ var qeATrack = qe.project.getActiveSequence().getAudioTrackAt(${args.track_index});
317
+ if (qeATrack && typeof qeATrack.setVolume === "function") {
318
+ qeATrack.setVolume(${args.volume_db});
319
+ volumeSet = true;
320
+ }
321
+ } catch (e3) {}
322
+ }
323
+
324
+ // PP 2026 WORKAROUND: apply the volume_db to all clips on the track
325
+ // individually. This is the practical equivalent of setting the track
326
+ // fader since Premiere mixes clip volume into the master output.
327
+ // Returns success with workaround=true so callers know it's per-clip.
328
+ if (!volumeSet) {
329
+ attemptedPaths.push("per-clip fallback");
330
+ var clipsAffected = 0;
331
+ var clipsTotal = track.clips.numItems;
332
+ for (var ci = 0; ci < clipsTotal; ci++) {
333
+ var aClip = track.clips[ci];
334
+ // Use locale-resilient lookup
335
+ var volComp = __findIntrinsic(aClip, "volume");
336
+ if (volComp) {
337
+ var levelProp = __findProp(volComp, ["Level", "Nivel", "Volume", "Volumen", "Pegel"]);
338
+ if (levelProp) {
339
+ try { levelProp.setValue(${args.volume_db}, true); clipsAffected++; } catch (eClip) {}
340
+ }
341
+ }
342
+ }
343
+ if (clipsAffected > 0) {
344
+ return __result({
345
+ trackIndex: ${args.track_index},
346
+ volumeDb: ${args.volume_db},
347
+ trackName: track.name || ("A" + (${args.track_index} + 1)),
348
+ workaround: "per-clip-volume",
349
+ clipsAffected: clipsAffected,
350
+ clipsTotal: clipsTotal,
351
+ note: "PP 2026 doesn't expose track-level volume via ExtendScript. Applied " + ${args.volume_db} + "dB to each of the " + clipsAffected + " clips on this track — equivalent net mixing effect to the track fader."
352
+ });
353
+ }
354
+ return __error("set_track_volume: track-level API not available in PP 2026 and no clips on this track to apply per-clip workaround. Attempted: " + attemptedPaths.join(", ") + ". Add clips to the track or adjust the fader manually in the Audio Track Mixer panel.");
355
+ }
356
+
357
+ if (!volumeSet) {
358
+ return __error("Could not set volume on track " + ${args.track_index} + ". Track may not support direct volume setting via this API. Try using add_keyframe on the track's Volume property instead.");
359
+ }
360
+
361
+ return __result({
362
+ trackIndex: ${args.track_index},
363
+ volumeDb: ${args.volume_db},
364
+ trackName: track.name || ("A" + (${args.track_index} + 1))
365
+ });
366
366
  `);
367
367
  return sendCommand(script, bridgeOptions);
368
368
  },
369
369
  },
370
370
  nest_sequence: {
371
- description: `Nest the currently selected clips into a new sub-sequence (nested sequence).
372
-
373
- This replaces the selected clips on the timeline with a single clip that contains a new sub-sequence with the original clips. Nesting is useful for:
374
- - Grouping clips to apply a single effect to all of them
375
- - Organizing complex timelines
376
- - Applying motion/transform to a group of clips
377
- - Creating "packages" for reuse
378
-
379
- Preconditions:
380
- 1. An active sequence must exist.
381
- 2. Clips must be selected before calling this tool. Use set_clip_selection or select_clips_by_name to select clips first.
382
- 3. At least one clip must be selected.
383
-
384
- After nesting, the new sub-sequence appears in the project panel and can be opened and edited independently.
385
-
386
- Example workflow:
387
- 1. select_clips_by_name({ names: ["clip1.mp4", "clip2.mp4"] })
388
- 2. nest_sequence({ name: "Intro Package" })
389
-
371
+ description: `Nest the currently selected clips into a new sub-sequence (nested sequence).
372
+
373
+ This replaces the selected clips on the timeline with a single clip that contains a new sub-sequence with the original clips. Nesting is useful for:
374
+ - Grouping clips to apply a single effect to all of them
375
+ - Organizing complex timelines
376
+ - Applying motion/transform to a group of clips
377
+ - Creating "packages" for reuse
378
+
379
+ Preconditions:
380
+ 1. An active sequence must exist.
381
+ 2. Clips must be selected before calling this tool. Use set_clip_selection or select_clips_by_name to select clips first.
382
+ 3. At least one clip must be selected.
383
+
384
+ After nesting, the new sub-sequence appears in the project panel and can be opened and edited independently.
385
+
386
+ Example workflow:
387
+ 1. select_clips_by_name({ names: ["clip1.mp4", "clip2.mp4"] })
388
+ 2. nest_sequence({ name: "Intro Package" })
389
+
390
390
  Example: nest_sequence({ name: "Color Grade Group" })`,
391
391
  parameters: {
392
392
  type: "object",
@@ -399,74 +399,74 @@ Example: nest_sequence({ name: "Color Grade Group" })`,
399
399
  required: ["name"],
400
400
  },
401
401
  handler: async (args) => {
402
- const script = buildToolScript(`
403
- var seq = app.project.activeSequence;
404
- if (!seq) return __error("No active sequence");
405
-
406
- // Check if any clips are selected
407
- var selectedCount = 0;
408
- for (var v = 0; v < seq.videoTracks.numTracks; v++) {
409
- var track = seq.videoTracks[v];
410
- for (var c = 0; c < track.clips.numItems; c++) {
411
- if (track.clips[c].isSelected) selectedCount++;
412
- }
413
- }
414
- for (var a = 0; a < seq.audioTracks.numTracks; a++) {
415
- var atrack = seq.audioTracks[a];
416
- for (var ac = 0; ac < atrack.clips.numItems; ac++) {
417
- if (atrack.clips[ac].isSelected) selectedCount++;
418
- }
419
- }
420
-
421
- if (selectedCount === 0) {
422
- return __error("No clips are selected. Use set_clip_selection or select_clips_by_name to select clips before nesting.");
423
- }
424
-
425
- // IMPORTANT behavior note: the Premiere DOM does NOT expose a true
426
- // "Nest selected clips" (which replaces the selection on the timeline
427
- // with a nested-sequence clip). seq.createSubsequence() builds a NEW
428
- // sub-sequence from the sequence's IN/OUT range (or the whole sequence
429
- // when no in/out is set) — it does NOT consume the selection and does
430
- // NOT replace clips on this timeline. We attempt it here, but report
431
- // the real behavior so callers are not misled.
432
- var nestedSeq = null;
433
- var createError = null;
434
- try {
435
- // Some versions accept a boolean (ignoreMapping/ignoreTrackTargeting); guard with try/catch.
436
- try {
437
- nestedSeq = seq.createSubsequence(true);
438
- } catch(eArg) {
439
- nestedSeq = seq.createSubsequence();
440
- }
441
- } catch(eSub) {
442
- createError = eSub.toString();
443
- }
444
-
445
- if (!nestedSeq) {
446
- return __error("createSubsequence failed" + (createError ? (": " + createError) : "") + ". The Premiere DOM has no true nest-selection API; this builds a sub-sequence from the sequence in/out range. Set sequence in/out points around the clips first, or nest manually in the UI.");
447
- }
448
-
449
- // Rename the resulting sub-sequence to the requested name (createSubsequence
450
- // does not accept a name argument in current versions).
451
- var renamed = false;
452
- try {
453
- if (nestedSeq.projectItem && nestedSeq.projectItem.name !== undefined) {
454
- nestedSeq.projectItem.name = "${escapeForExtendScript(args.name)}";
455
- renamed = true;
456
- } else if (nestedSeq.name !== undefined) {
457
- nestedSeq.name = "${escapeForExtendScript(args.name)}";
458
- renamed = true;
459
- }
460
- } catch(eName) {}
461
-
462
- return __result({
463
- nested: true,
464
- nestedSequenceName: nestedSeq.name,
465
- nestedSequenceId: nestedSeq.sequenceID,
466
- renamed: renamed,
467
- selectedClips: selectedCount,
468
- behaviorNote: "createSubsequence builds a sub-sequence from the sequence IN/OUT range (whole sequence if no in/out set). It does NOT replace the selected clips on this timeline and does NOT perform a true 'Nest'. The selection count is reported for reference only."
469
- });
402
+ const script = buildToolScript(`
403
+ var seq = app.project.activeSequence;
404
+ if (!seq) return __error("No active sequence");
405
+
406
+ // Check if any clips are selected
407
+ var selectedCount = 0;
408
+ for (var v = 0; v < seq.videoTracks.numTracks; v++) {
409
+ var track = seq.videoTracks[v];
410
+ for (var c = 0; c < track.clips.numItems; c++) {
411
+ if (track.clips[c].isSelected) selectedCount++;
412
+ }
413
+ }
414
+ for (var a = 0; a < seq.audioTracks.numTracks; a++) {
415
+ var atrack = seq.audioTracks[a];
416
+ for (var ac = 0; ac < atrack.clips.numItems; ac++) {
417
+ if (atrack.clips[ac].isSelected) selectedCount++;
418
+ }
419
+ }
420
+
421
+ if (selectedCount === 0) {
422
+ return __error("No clips are selected. Use set_clip_selection or select_clips_by_name to select clips before nesting.");
423
+ }
424
+
425
+ // IMPORTANT behavior note: the Premiere DOM does NOT expose a true
426
+ // "Nest selected clips" (which replaces the selection on the timeline
427
+ // with a nested-sequence clip). seq.createSubsequence() builds a NEW
428
+ // sub-sequence from the sequence's IN/OUT range (or the whole sequence
429
+ // when no in/out is set) — it does NOT consume the selection and does
430
+ // NOT replace clips on this timeline. We attempt it here, but report
431
+ // the real behavior so callers are not misled.
432
+ var nestedSeq = null;
433
+ var createError = null;
434
+ try {
435
+ // Some versions accept a boolean (ignoreMapping/ignoreTrackTargeting); guard with try/catch.
436
+ try {
437
+ nestedSeq = seq.createSubsequence(true);
438
+ } catch(eArg) {
439
+ nestedSeq = seq.createSubsequence();
440
+ }
441
+ } catch(eSub) {
442
+ createError = eSub.toString();
443
+ }
444
+
445
+ if (!nestedSeq) {
446
+ return __error("createSubsequence failed" + (createError ? (": " + createError) : "") + ". The Premiere DOM has no true nest-selection API; this builds a sub-sequence from the sequence in/out range. Set sequence in/out points around the clips first, or nest manually in the UI.");
447
+ }
448
+
449
+ // Rename the resulting sub-sequence to the requested name (createSubsequence
450
+ // does not accept a name argument in current versions).
451
+ var renamed = false;
452
+ try {
453
+ if (nestedSeq.projectItem && nestedSeq.projectItem.name !== undefined) {
454
+ nestedSeq.projectItem.name = "${escapeForExtendScript(args.name)}";
455
+ renamed = true;
456
+ } else if (nestedSeq.name !== undefined) {
457
+ nestedSeq.name = "${escapeForExtendScript(args.name)}";
458
+ renamed = true;
459
+ }
460
+ } catch(eName) {}
461
+
462
+ return __result({
463
+ nested: true,
464
+ nestedSequenceName: nestedSeq.name,
465
+ nestedSequenceId: nestedSeq.sequenceID,
466
+ renamed: renamed,
467
+ selectedClips: selectedCount,
468
+ behaviorNote: "createSubsequence builds a sub-sequence from the sequence IN/OUT range (whole sequence if no in/out set). It does NOT replace the selected clips on this timeline and does NOT perform a true 'Nest'. The selection count is reported for reference only."
469
+ });
470
470
  `);
471
471
  return sendCommand(script, bridgeOptions);
472
472
  },