@synthetic-ai/premiere-mcp 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/LICENSE +21 -0
- package/README.md +242 -0
- package/cep-plugin/.debug +8 -0
- package/cep-plugin/CSInterface.js +70 -0
- package/cep-plugin/CSXS/manifest.xml +49 -0
- package/cep-plugin/host.jsx +7 -0
- package/cep-plugin/index.html +419 -0
- package/cep-plugin/main.js +327 -0
- package/cep-plugin/premiere.jsx +24947 -0
- package/cep-plugin/synthetic-logo.png +0 -0
- package/dist/bridge/file-bridge.d.ts +29 -0
- package/dist/bridge/file-bridge.js +145 -0
- package/dist/bridge/script-builder.d.ts +19 -0
- package/dist/bridge/script-builder.js +408 -0
- package/dist/http-server.d.ts +21 -0
- package/dist/http-server.js +140 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +70 -0
- package/dist/resources/extendscript-reference.d.ts +2 -0
- package/dist/resources/extendscript-reference.js +219 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +276 -0
- package/dist/tools/advanced.d.ts +530 -0
- package/dist/tools/advanced.js +923 -0
- package/dist/tools/audio-advanced.d.ts +847 -0
- package/dist/tools/audio-advanced.js +1653 -0
- package/dist/tools/audio.d.ts +84 -0
- package/dist/tools/audio.js +123 -0
- package/dist/tools/captions.d.ts +30 -0
- package/dist/tools/captions.js +73 -0
- package/dist/tools/clipboard.d.ts +153 -0
- package/dist/tools/clipboard.js +381 -0
- package/dist/tools/color-advanced.d.ts +821 -0
- package/dist/tools/color-advanced.js +772 -0
- package/dist/tools/discovery.d.ts +128 -0
- package/dist/tools/discovery.js +445 -0
- package/dist/tools/effects.d.ts +211 -0
- package/dist/tools/effects.js +554 -0
- package/dist/tools/export-presets.d.ts +333 -0
- package/dist/tools/export-presets.js +490 -0
- package/dist/tools/export.d.ts +320 -0
- package/dist/tools/export.js +618 -0
- package/dist/tools/health.d.ts +9 -0
- package/dist/tools/health.js +25 -0
- package/dist/tools/inspection.d.ts +179 -0
- package/dist/tools/inspection.js +1089 -0
- package/dist/tools/keyframes.d.ts +253 -0
- package/dist/tools/keyframes.js +611 -0
- package/dist/tools/markers.d.ts +117 -0
- package/dist/tools/markers.js +206 -0
- package/dist/tools/media.d.ts +297 -0
- package/dist/tools/media.js +493 -0
- package/dist/tools/metadata.d.ts +178 -0
- package/dist/tools/metadata.js +278 -0
- package/dist/tools/multicam.d.ts +199 -0
- package/dist/tools/multicam.js +212 -0
- package/dist/tools/playback.d.ts +34 -0
- package/dist/tools/playback.js +63 -0
- package/dist/tools/playhead.d.ts +77 -0
- package/dist/tools/playhead.js +185 -0
- package/dist/tools/project-manager.d.ts +65 -0
- package/dist/tools/project-manager.js +81 -0
- package/dist/tools/project.d.ts +390 -0
- package/dist/tools/project.js +596 -0
- package/dist/tools/scripting.d.ts +98 -0
- package/dist/tools/scripting.js +434 -0
- package/dist/tools/selection.d.ts +120 -0
- package/dist/tools/selection.js +334 -0
- package/dist/tools/sequence-advanced.d.ts +84 -0
- package/dist/tools/sequence-advanced.js +471 -0
- package/dist/tools/sequence-settings.d.ts +397 -0
- package/dist/tools/sequence-settings.js +967 -0
- package/dist/tools/sequence.d.ts +211 -0
- package/dist/tools/sequence.js +378 -0
- package/dist/tools/source-monitor.d.ts +95 -0
- package/dist/tools/source-monitor.js +180 -0
- package/dist/tools/text.d.ts +97 -0
- package/dist/tools/text.js +159 -0
- package/dist/tools/timeline.d.ts +265 -0
- package/dist/tools/timeline.js +575 -0
- package/dist/tools/track-management.d.ts +482 -0
- package/dist/tools/track-management.js +787 -0
- package/dist/tools/track-targeting.d.ts +666 -0
- package/dist/tools/track-targeting.js +1318 -0
- package/dist/tools/tracks.d.ts +90 -0
- package/dist/tools/tracks.js +183 -0
- package/dist/tools/transitions-motion.d.ts +788 -0
- package/dist/tools/transitions-motion.js +680 -0
- package/dist/tools/transitions.d.ts +124 -0
- package/dist/tools/transitions.js +267 -0
- package/dist/tools/utility.d.ts +469 -0
- package/dist/tools/utility.js +1118 -0
- package/dist/tools/workspace.d.ts +25 -0
- package/dist/tools/workspace.js +61 -0
- package/package.json +66 -0
- package/scripts/build-chat-dmg.sh +210 -0
- package/scripts/build-chat-zip.sh +291 -0
- package/scripts/install-cep-windows.ps1 +99 -0
- package/scripts/install-cep.sh +66 -0
- package/scripts/install-chat-plugin.bat +67 -0
- package/scripts/install-chat-plugin.sh +85 -0
- package/scripts/install-windows.bat +95 -0
- package/scripts/package.mjs +218 -0
|
@@ -0,0 +1,1653 @@
|
|
|
1
|
+
import { buildToolScript } from "../bridge/script-builder.js";
|
|
2
|
+
import { sendCommand } from "../bridge/file-bridge.js";
|
|
3
|
+
export function getAudioAdvancedTools(bridgeOptions) {
|
|
4
|
+
return {
|
|
5
|
+
get_audio_mixer_state: {
|
|
6
|
+
description: "Get the current state of the audio mixer for the active sequence. Returns all track volumes, panning, mute/solo status.",
|
|
7
|
+
parameters: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {},
|
|
10
|
+
required: [],
|
|
11
|
+
},
|
|
12
|
+
handler: async (_args) => {
|
|
13
|
+
const script = buildToolScript(`
|
|
14
|
+
var seq = app.project.activeSequence;
|
|
15
|
+
if (!seq) return __error("No active sequence");
|
|
16
|
+
var tracks = [];
|
|
17
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
18
|
+
var track = seq.audioTracks[t];
|
|
19
|
+
var trackInfo = {
|
|
20
|
+
index: t,
|
|
21
|
+
name: track.name || ("Audio " + (t + 1)),
|
|
22
|
+
isMuted: __readMaybe(track, "isMuted", false),
|
|
23
|
+
numClips: track.clips ? track.clips.numItems : 0,
|
|
24
|
+
level: 0,
|
|
25
|
+
panning: 0
|
|
26
|
+
};
|
|
27
|
+
var volComp = __findComponent(track, ["AE.ADBE Audio Levels", "audioVolume", "audioGain"], ["Volume", "Volumen"]);
|
|
28
|
+
if (volComp) {
|
|
29
|
+
for (var pi = 0; pi < volComp.properties.numItems; pi++) {
|
|
30
|
+
var prop = volComp.properties[pi];
|
|
31
|
+
if (prop.displayName === "Level") {
|
|
32
|
+
try { trackInfo.level = prop.getValue(); } catch(e) {}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
var panComp = __findComponent(track, ["AE.ADBE Pan", "audioPanner"], ["Panner", "Balance"]);
|
|
38
|
+
if (panComp) {
|
|
39
|
+
for (var pi2 = 0; pi2 < panComp.properties.numItems; pi2++) {
|
|
40
|
+
var prop2 = panComp.properties[pi2];
|
|
41
|
+
if (prop2.displayName === "Balance" || prop2.displayName === "Panner" || prop2.displayName === "Left/Right Balance") {
|
|
42
|
+
try { trackInfo.panning = prop2.getValue(); } catch(e2) {}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
tracks.push(trackInfo);
|
|
48
|
+
}
|
|
49
|
+
return __result({ tracks: tracks, numTracks: tracks.length });
|
|
50
|
+
`);
|
|
51
|
+
return sendCommand(script, bridgeOptions);
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
set_audio_level: {
|
|
55
|
+
description: "Set the audio level (volume) of a clip on an audio track by track and clip index.",
|
|
56
|
+
parameters: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
60
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
61
|
+
level_db: { type: "number", description: "Audio level in dB. Range: -96 to +15. 0 = unity gain." },
|
|
62
|
+
},
|
|
63
|
+
required: ["track_index", "clip_index", "level_db"],
|
|
64
|
+
},
|
|
65
|
+
handler: async (args) => {
|
|
66
|
+
const script = buildToolScript(`
|
|
67
|
+
var seq = app.project.activeSequence;
|
|
68
|
+
if (!seq) return __error("No active sequence");
|
|
69
|
+
var trackIdx = ${args.track_index};
|
|
70
|
+
var clipIdx = ${args.clip_index};
|
|
71
|
+
var levelDb = ${args.level_db};
|
|
72
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
73
|
+
var track = seq.audioTracks[trackIdx];
|
|
74
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
75
|
+
var clip = track.clips[clipIdx];
|
|
76
|
+
var found = false;
|
|
77
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
78
|
+
var comp = clip.components[i];
|
|
79
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
80
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
81
|
+
var prop = comp.properties[p];
|
|
82
|
+
if (prop.displayName === "Level") {
|
|
83
|
+
prop.setValue(levelDb, true);
|
|
84
|
+
found = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (found) break;
|
|
90
|
+
}
|
|
91
|
+
if (!found) return __error("Could not find Volume/Level component on clip");
|
|
92
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, levelDb: levelDb, clip: clip.name });
|
|
93
|
+
`);
|
|
94
|
+
return sendCommand(script, bridgeOptions);
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
set_track_panning: {
|
|
98
|
+
description: "Set the stereo panning for an audio track. Range: -100 (full left) to +100 (full right). 0 = center.",
|
|
99
|
+
parameters: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
103
|
+
pan_value: { type: "number", description: "Pan value from -100 (full left) to +100 (full right)" },
|
|
104
|
+
},
|
|
105
|
+
required: ["track_index", "pan_value"],
|
|
106
|
+
},
|
|
107
|
+
handler: async (args) => {
|
|
108
|
+
const script = buildToolScript(`
|
|
109
|
+
var seq = app.project.activeSequence;
|
|
110
|
+
if (!seq) return __error("No active sequence");
|
|
111
|
+
var trackIdx = ${args.track_index};
|
|
112
|
+
var panValue = ${args.pan_value};
|
|
113
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
114
|
+
var track = seq.audioTracks[trackIdx];
|
|
115
|
+
var found = false;
|
|
116
|
+
if (track.components) {
|
|
117
|
+
for (var ci = 0; ci < track.components.numItems; ci++) {
|
|
118
|
+
var comp = track.components[ci];
|
|
119
|
+
if (comp.matchName === "audioPanner" || comp.displayName === "Panner") {
|
|
120
|
+
for (var pi = 0; pi < comp.properties.numItems; pi++) {
|
|
121
|
+
var prop = comp.properties[pi];
|
|
122
|
+
if (prop.displayName === "Balance" || prop.displayName === "Panner" || prop.displayName === "Left/Right Balance") {
|
|
123
|
+
prop.setValue(panValue, true);
|
|
124
|
+
found = true;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (found) break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!found) return __result({ note: "Panner component not found on track " + trackIdx + ". Track-level panning may not be available for this track type.", trackIndex: trackIdx, requestedPan: panValue });
|
|
133
|
+
return __result({ trackIndex: trackIdx, panValue: panValue });
|
|
134
|
+
`);
|
|
135
|
+
return sendCommand(script, bridgeOptions);
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
set_clip_panning: {
|
|
139
|
+
description: "Set the stereo panning for an individual audio clip. Range: -100 (full left) to +100 (full right). 0 = center.",
|
|
140
|
+
parameters: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
144
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
145
|
+
pan_value: { type: "number", description: "Pan value from -100 (full left) to +100 (full right)" },
|
|
146
|
+
},
|
|
147
|
+
required: ["track_index", "clip_index", "pan_value"],
|
|
148
|
+
},
|
|
149
|
+
handler: async (args) => {
|
|
150
|
+
const script = buildToolScript(`
|
|
151
|
+
var seq = app.project.activeSequence;
|
|
152
|
+
if (!seq) return __error("No active sequence");
|
|
153
|
+
var trackIdx = ${args.track_index};
|
|
154
|
+
var clipIdx = ${args.clip_index};
|
|
155
|
+
var panValue = ${args.pan_value};
|
|
156
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
157
|
+
var track = seq.audioTracks[trackIdx];
|
|
158
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
159
|
+
var clip = track.clips[clipIdx];
|
|
160
|
+
var found = false;
|
|
161
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
162
|
+
var comp = clip.components[i];
|
|
163
|
+
if (comp.matchName === "audioPanner" || comp.displayName === "Panner") {
|
|
164
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
165
|
+
var prop = comp.properties[p];
|
|
166
|
+
if (prop.displayName === "Balance" || prop.displayName === "Panner" || prop.displayName === "Left/Right Balance") {
|
|
167
|
+
prop.setValue(panValue, true);
|
|
168
|
+
found = true;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (found) break;
|
|
174
|
+
}
|
|
175
|
+
if (!found) return __result({ note: "Panner component not found on clip", trackIndex: trackIdx, clipIndex: clipIdx, requestedPan: panValue });
|
|
176
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, panValue: panValue, clip: clip.name });
|
|
177
|
+
`);
|
|
178
|
+
return sendCommand(script, bridgeOptions);
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
get_clip_panning: {
|
|
182
|
+
description: "Get the current stereo panning value for an audio clip.",
|
|
183
|
+
parameters: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
187
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
188
|
+
},
|
|
189
|
+
required: ["track_index", "clip_index"],
|
|
190
|
+
},
|
|
191
|
+
handler: async (args) => {
|
|
192
|
+
const script = buildToolScript(`
|
|
193
|
+
var seq = app.project.activeSequence;
|
|
194
|
+
if (!seq) return __error("No active sequence");
|
|
195
|
+
var trackIdx = ${args.track_index};
|
|
196
|
+
var clipIdx = ${args.clip_index};
|
|
197
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
198
|
+
var track = seq.audioTracks[trackIdx];
|
|
199
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
200
|
+
var clip = track.clips[clipIdx];
|
|
201
|
+
var panValue = 0;
|
|
202
|
+
var found = false;
|
|
203
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
204
|
+
var comp = clip.components[i];
|
|
205
|
+
if (comp.matchName === "audioPanner" || comp.displayName === "Panner") {
|
|
206
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
207
|
+
var prop = comp.properties[p];
|
|
208
|
+
if (prop.displayName === "Balance" || prop.displayName === "Panner" || prop.displayName === "Left/Right Balance") {
|
|
209
|
+
try { panValue = prop.getValue(); found = true; } catch(e) {}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (found) break;
|
|
215
|
+
}
|
|
216
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, panValue: panValue, clip: clip.name, found: found });
|
|
217
|
+
`);
|
|
218
|
+
return sendCommand(script, bridgeOptions);
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
add_volume_keyframe: {
|
|
222
|
+
description: "Add a volume keyframe to an audio clip at a specific time. Enables time-varying volume automation.",
|
|
223
|
+
parameters: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
227
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
228
|
+
time: { type: "number", description: "Time in seconds (relative to sequence start)" },
|
|
229
|
+
level_db: { type: "number", description: "Volume level in dB at this keyframe. Range: -96 to +15." },
|
|
230
|
+
},
|
|
231
|
+
required: ["track_index", "clip_index", "time", "level_db"],
|
|
232
|
+
},
|
|
233
|
+
handler: async (args) => {
|
|
234
|
+
const script = buildToolScript(`
|
|
235
|
+
var seq = app.project.activeSequence;
|
|
236
|
+
if (!seq) return __error("No active sequence");
|
|
237
|
+
var trackIdx = ${args.track_index};
|
|
238
|
+
var clipIdx = ${args.clip_index};
|
|
239
|
+
var timeSecs = ${args.time};
|
|
240
|
+
var levelDb = ${args.level_db};
|
|
241
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
242
|
+
var track = seq.audioTracks[trackIdx];
|
|
243
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
244
|
+
var clip = track.clips[clipIdx];
|
|
245
|
+
var levelProp = null;
|
|
246
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
247
|
+
var comp = clip.components[i];
|
|
248
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
249
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
250
|
+
if (comp.properties[p].displayName === "Level") {
|
|
251
|
+
levelProp = comp.properties[p];
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (levelProp) break;
|
|
257
|
+
}
|
|
258
|
+
if (!levelProp) return __error("Could not find Volume Level property on clip");
|
|
259
|
+
levelProp.setTimeVarying(true);
|
|
260
|
+
var timeTicks = __secondsToTicks(timeSecs);
|
|
261
|
+
var t = new Time();
|
|
262
|
+
t.ticks = String(timeTicks);
|
|
263
|
+
levelProp.addKey(t);
|
|
264
|
+
levelProp.setValueAtKey(t, levelDb);
|
|
265
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, time: timeSecs, levelDb: levelDb, clip: clip.name });
|
|
266
|
+
`);
|
|
267
|
+
return sendCommand(script, bridgeOptions);
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
add_panning_keyframe: {
|
|
271
|
+
description: "Add a panning keyframe to an audio clip at a specific time. Enables time-varying stereo panning automation.",
|
|
272
|
+
parameters: {
|
|
273
|
+
type: "object",
|
|
274
|
+
properties: {
|
|
275
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
276
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
277
|
+
time: { type: "number", description: "Time in seconds (relative to sequence start)" },
|
|
278
|
+
pan_value: { type: "number", description: "Pan value from -100 (full left) to +100 (full right)" },
|
|
279
|
+
},
|
|
280
|
+
required: ["track_index", "clip_index", "time", "pan_value"],
|
|
281
|
+
},
|
|
282
|
+
handler: async (args) => {
|
|
283
|
+
const script = buildToolScript(`
|
|
284
|
+
var seq = app.project.activeSequence;
|
|
285
|
+
if (!seq) return __error("No active sequence");
|
|
286
|
+
var trackIdx = ${args.track_index};
|
|
287
|
+
var clipIdx = ${args.clip_index};
|
|
288
|
+
var timeSecs = ${args.time};
|
|
289
|
+
var panValue = ${args.pan_value};
|
|
290
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
291
|
+
var track = seq.audioTracks[trackIdx];
|
|
292
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
293
|
+
var clip = track.clips[clipIdx];
|
|
294
|
+
var panComp = __findIntrinsic(clip, "panner");
|
|
295
|
+
if (!panComp) return __error("Panner component not found on clip");
|
|
296
|
+
var panProp = __findProp(panComp, ["Balance", "Panner", "Left/Right Balance", "Panoramización"]);
|
|
297
|
+
if (!panProp) return __error("Panner property not found on clip");
|
|
298
|
+
panProp.setTimeVarying(true);
|
|
299
|
+
var timeTicks = __secondsToTicks(timeSecs);
|
|
300
|
+
var t = new Time();
|
|
301
|
+
t.ticks = String(timeTicks);
|
|
302
|
+
panProp.addKey(t);
|
|
303
|
+
panProp.setValueAtKey(t, panValue);
|
|
304
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, time: timeSecs, panValue: panValue, clip: clip.name });
|
|
305
|
+
`);
|
|
306
|
+
return sendCommand(script, bridgeOptions);
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
get_volume_keyframes: {
|
|
310
|
+
description: "Get all volume keyframes for an audio clip.",
|
|
311
|
+
parameters: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {
|
|
314
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
315
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
316
|
+
},
|
|
317
|
+
required: ["track_index", "clip_index"],
|
|
318
|
+
},
|
|
319
|
+
handler: async (args) => {
|
|
320
|
+
const script = buildToolScript(`
|
|
321
|
+
var seq = app.project.activeSequence;
|
|
322
|
+
if (!seq) return __error("No active sequence");
|
|
323
|
+
var trackIdx = ${args.track_index};
|
|
324
|
+
var clipIdx = ${args.clip_index};
|
|
325
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
326
|
+
var track = seq.audioTracks[trackIdx];
|
|
327
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
328
|
+
var clip = track.clips[clipIdx];
|
|
329
|
+
var volComp = __findIntrinsic(clip, "volume");
|
|
330
|
+
if (!volComp) return __error("Volume component not found on clip");
|
|
331
|
+
var levelProp = __findProp(volComp, ["Level", "Nivel", "Pegel", "Volume"]);
|
|
332
|
+
if (!levelProp) return __error("Volume Level property not found on clip");
|
|
333
|
+
var keyframes = [];
|
|
334
|
+
try {
|
|
335
|
+
var keys = levelProp.getKeys ? levelProp.getKeys() : null;
|
|
336
|
+
if (keys) {
|
|
337
|
+
var numKeys = (keys.numKeys != null) ? keys.numKeys : (keys.length != null ? keys.length : 0);
|
|
338
|
+
for (var k = 0; k < numKeys; k++) {
|
|
339
|
+
var keyTime = keys[k];
|
|
340
|
+
if (keyTime == null && levelProp.getKey) {
|
|
341
|
+
try { keyTime = levelProp.getKey(k); } catch(eGetKey) {}
|
|
342
|
+
}
|
|
343
|
+
if (keyTime == null) continue;
|
|
344
|
+
var keyVal = 0;
|
|
345
|
+
try { keyVal = levelProp.getValueAtKey(keyTime); } catch(e) {}
|
|
346
|
+
keyframes.push({ time: __ticksToSeconds(keyTime.ticks), levelDb: keyVal });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch(eKeys) {
|
|
350
|
+
keyframes = [];
|
|
351
|
+
}
|
|
352
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, clip: clip.name, keyframes: keyframes, timeVarying: levelProp.getTimeVarying ? levelProp.getTimeVarying() : false });
|
|
353
|
+
`);
|
|
354
|
+
return sendCommand(script, bridgeOptions);
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
get_panning_keyframes: {
|
|
358
|
+
description: "Get all panning keyframes for an audio clip.",
|
|
359
|
+
parameters: {
|
|
360
|
+
type: "object",
|
|
361
|
+
properties: {
|
|
362
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
363
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
364
|
+
},
|
|
365
|
+
required: ["track_index", "clip_index"],
|
|
366
|
+
},
|
|
367
|
+
handler: async (args) => {
|
|
368
|
+
const script = buildToolScript(`
|
|
369
|
+
var seq = app.project.activeSequence;
|
|
370
|
+
if (!seq) return __error("No active sequence");
|
|
371
|
+
var trackIdx = ${args.track_index};
|
|
372
|
+
var clipIdx = ${args.clip_index};
|
|
373
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
374
|
+
var track = seq.audioTracks[trackIdx];
|
|
375
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
376
|
+
var clip = track.clips[clipIdx];
|
|
377
|
+
var panComp = __findIntrinsic(clip, "panner");
|
|
378
|
+
if (!panComp) return __error("Panner component not found on clip");
|
|
379
|
+
var panProp = __findProp(panComp, ["Balance", "Panner", "Left/Right Balance", "Panoramización"]);
|
|
380
|
+
if (!panProp) return __error("Panner property not found on clip");
|
|
381
|
+
var keyframes = [];
|
|
382
|
+
try {
|
|
383
|
+
var keys = panProp.getKeys ? panProp.getKeys() : null;
|
|
384
|
+
if (keys) {
|
|
385
|
+
var numKeys = (keys.numKeys != null) ? keys.numKeys : (keys.length != null ? keys.length : 0);
|
|
386
|
+
for (var k = 0; k < numKeys; k++) {
|
|
387
|
+
var keyTime = keys[k];
|
|
388
|
+
if (keyTime == null && panProp.getKey) {
|
|
389
|
+
try { keyTime = panProp.getKey(k); } catch(eGetKey) {}
|
|
390
|
+
}
|
|
391
|
+
if (keyTime == null) continue;
|
|
392
|
+
var keyVal = 0;
|
|
393
|
+
try { keyVal = panProp.getValueAtKey(keyTime); } catch(e) {}
|
|
394
|
+
keyframes.push({ time: __ticksToSeconds(keyTime.ticks), panValue: keyVal });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} catch(eKeys) {
|
|
398
|
+
keyframes = [];
|
|
399
|
+
}
|
|
400
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, clip: clip.name, keyframes: keyframes, timeVarying: panProp.getTimeVarying ? panProp.getTimeVarying() : false });
|
|
401
|
+
`);
|
|
402
|
+
return sendCommand(script, bridgeOptions);
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
remove_all_audio_keyframes: {
|
|
406
|
+
description: "Remove all volume and panning keyframes from an audio clip, returning it to static values.",
|
|
407
|
+
parameters: {
|
|
408
|
+
type: "object",
|
|
409
|
+
properties: {
|
|
410
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
411
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
412
|
+
},
|
|
413
|
+
required: ["track_index", "clip_index"],
|
|
414
|
+
},
|
|
415
|
+
handler: async (args) => {
|
|
416
|
+
const script = buildToolScript(`
|
|
417
|
+
var seq = app.project.activeSequence;
|
|
418
|
+
if (!seq) return __error("No active sequence");
|
|
419
|
+
var trackIdx = ${args.track_index};
|
|
420
|
+
var clipIdx = ${args.clip_index};
|
|
421
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
422
|
+
var track = seq.audioTracks[trackIdx];
|
|
423
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
424
|
+
var clip = track.clips[clipIdx];
|
|
425
|
+
var removed = [];
|
|
426
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
427
|
+
var comp = clip.components[i];
|
|
428
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
429
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
430
|
+
if (comp.properties[p].displayName === "Level") {
|
|
431
|
+
try { comp.properties[p].setTimeVarying(false); removed.push("Volume Level"); } catch(e) {}
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (comp.matchName === "audioPanner" || comp.displayName === "Panner") {
|
|
437
|
+
for (var p2 = 0; p2 < comp.properties.numItems; p2++) {
|
|
438
|
+
var prop2 = comp.properties[p2];
|
|
439
|
+
if (prop2.displayName === "Balance" || prop2.displayName === "Panner" || prop2.displayName === "Left/Right Balance") {
|
|
440
|
+
try { prop2.setTimeVarying(false); removed.push("Panner"); } catch(e2) {}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, clip: clip.name, removedFrom: removed });
|
|
447
|
+
`);
|
|
448
|
+
return sendCommand(script, bridgeOptions);
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
apply_eq: {
|
|
452
|
+
description: "Apply Parametric Equalizer effect to an audio clip. Optionally specify EQ bands as JSON array with freq, gain, and q values.",
|
|
453
|
+
parameters: {
|
|
454
|
+
type: "object",
|
|
455
|
+
properties: {
|
|
456
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
457
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
458
|
+
bands: { type: "array", description: "Optional EQ bands as array of {freq, gain, q} objects. Example: [{freq:1000, gain:-6, q:1.0}]" },
|
|
459
|
+
},
|
|
460
|
+
required: ["track_index", "clip_index"],
|
|
461
|
+
},
|
|
462
|
+
handler: async (args) => {
|
|
463
|
+
const script = buildToolScript(`
|
|
464
|
+
var seq = app.project.activeSequence;
|
|
465
|
+
if (!seq) return __error("No active sequence");
|
|
466
|
+
var trackIdx = ${args.track_index};
|
|
467
|
+
var clipIdx = ${args.clip_index};
|
|
468
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
469
|
+
var track = seq.audioTracks[trackIdx];
|
|
470
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
471
|
+
app.enableQE();
|
|
472
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
473
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
474
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
475
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
476
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
477
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
478
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
479
|
+
var effectNames = ["Parametric Equalizer", "Studio Equalizer", "Graphic Equalizer (10 Band)", "Graphic Equalizer (20 Band)"];
|
|
480
|
+
var effect = null;
|
|
481
|
+
for (var i = 0; i < effectNames.length; i++) {
|
|
482
|
+
try { effect = qe.project.getAudioEffectByName(effectNames[i]); } catch(e) {}
|
|
483
|
+
if (effect) { break; }
|
|
484
|
+
}
|
|
485
|
+
if (!effect) return __error("Parametric Equalizer effect not found. Ensure it is installed.");
|
|
486
|
+
qeClip.addAudioEffect(effect);
|
|
487
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: "Parametric Equalizer", applied: true });
|
|
488
|
+
`);
|
|
489
|
+
return sendCommand(script, bridgeOptions);
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
apply_compressor: {
|
|
493
|
+
description: "Apply a Dynamics/Compressor audio effect to a clip. Controls threshold, ratio, attack, and release.",
|
|
494
|
+
parameters: {
|
|
495
|
+
type: "object",
|
|
496
|
+
properties: {
|
|
497
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
498
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
499
|
+
threshold: { type: "number", description: "Threshold in dB where compression starts (e.g. -20). Default: -20." },
|
|
500
|
+
ratio: { type: "number", description: "Compression ratio (e.g. 4 means 4:1). Default: 4." },
|
|
501
|
+
attack: { type: "number", description: "Attack time in milliseconds. Default: 10." },
|
|
502
|
+
release: { type: "number", description: "Release time in milliseconds. Default: 100." },
|
|
503
|
+
},
|
|
504
|
+
required: ["track_index", "clip_index"],
|
|
505
|
+
},
|
|
506
|
+
handler: async (args) => {
|
|
507
|
+
const threshold = args.threshold ?? -20;
|
|
508
|
+
const ratio = args.ratio ?? 4;
|
|
509
|
+
const attack = args.attack ?? 10;
|
|
510
|
+
const release = args.release ?? 100;
|
|
511
|
+
const script = buildToolScript(`
|
|
512
|
+
var seq = app.project.activeSequence;
|
|
513
|
+
if (!seq) return __error("No active sequence");
|
|
514
|
+
var trackIdx = ${args.track_index};
|
|
515
|
+
var clipIdx = ${args.clip_index};
|
|
516
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
517
|
+
var track = seq.audioTracks[trackIdx];
|
|
518
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
519
|
+
app.enableQE();
|
|
520
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
521
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
522
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
523
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
524
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
525
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
526
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
527
|
+
var effectNames = ["Dynamics", "Compressor", "Multiband Compressor", "Single-band Compressor"];
|
|
528
|
+
var effect = null;
|
|
529
|
+
for (var i = 0; i < effectNames.length; i++) {
|
|
530
|
+
try { effect = qe.project.getAudioEffectByName(effectNames[i]); } catch(e) {}
|
|
531
|
+
if (effect) break;
|
|
532
|
+
}
|
|
533
|
+
if (!effect) return __error("Dynamics/Compressor effect not found.");
|
|
534
|
+
qeClip.addAudioEffect(effect);
|
|
535
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: "Dynamics", threshold: ${threshold}, ratio: ${ratio}, attack: ${attack}, release: ${release}, applied: true, note: "Effect added. Set parameters manually in Effect Controls panel." });
|
|
536
|
+
`);
|
|
537
|
+
return sendCommand(script, bridgeOptions);
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
apply_limiter: {
|
|
541
|
+
description: "Apply a Hard Limiter audio effect to a clip to prevent clipping. Sets a ceiling level in dB.",
|
|
542
|
+
parameters: {
|
|
543
|
+
type: "object",
|
|
544
|
+
properties: {
|
|
545
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
546
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
547
|
+
ceiling: { type: "number", description: "Maximum output level in dB (e.g. -0.1 to prevent clipping). Default: -0.1." },
|
|
548
|
+
},
|
|
549
|
+
required: ["track_index", "clip_index"],
|
|
550
|
+
},
|
|
551
|
+
handler: async (args) => {
|
|
552
|
+
const ceiling = args.ceiling ?? -0.1;
|
|
553
|
+
const script = buildToolScript(`
|
|
554
|
+
var seq = app.project.activeSequence;
|
|
555
|
+
if (!seq) return __error("No active sequence");
|
|
556
|
+
var trackIdx = ${args.track_index};
|
|
557
|
+
var clipIdx = ${args.clip_index};
|
|
558
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
559
|
+
var track = seq.audioTracks[trackIdx];
|
|
560
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
561
|
+
app.enableQE();
|
|
562
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
563
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
564
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
565
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
566
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
567
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
568
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
569
|
+
var effectNames = ["Hard Limiter", "Limiter", "Loudness Maximizer"];
|
|
570
|
+
var effect = null;
|
|
571
|
+
for (var i = 0; i < effectNames.length; i++) {
|
|
572
|
+
try { effect = qe.project.getAudioEffectByName(effectNames[i]); } catch(e) {}
|
|
573
|
+
if (effect) break;
|
|
574
|
+
}
|
|
575
|
+
if (!effect) return __error("Hard Limiter effect not found.");
|
|
576
|
+
qeClip.addAudioEffect(effect);
|
|
577
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: "Hard Limiter", ceiling: ${ceiling}, applied: true, note: "Effect added. Adjust ceiling parameter in Effect Controls panel." });
|
|
578
|
+
`);
|
|
579
|
+
return sendCommand(script, bridgeOptions);
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
apply_de_esser: {
|
|
583
|
+
description: "Apply a DeEsser audio effect to reduce sibilant 's' and 'sh' sounds in speech. Targets a specific frequency.",
|
|
584
|
+
parameters: {
|
|
585
|
+
type: "object",
|
|
586
|
+
properties: {
|
|
587
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
588
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
589
|
+
frequency: { type: "number", description: "Target frequency in Hz (typically 6000-8000 for sibilance). Default: 6000." },
|
|
590
|
+
reduction: { type: "number", description: "Reduction amount in dB (negative value, e.g. -10). Default: -10." },
|
|
591
|
+
},
|
|
592
|
+
required: ["track_index", "clip_index"],
|
|
593
|
+
},
|
|
594
|
+
handler: async (args) => {
|
|
595
|
+
const frequency = args.frequency ?? 6000;
|
|
596
|
+
const reduction = args.reduction ?? -10;
|
|
597
|
+
const script = buildToolScript(`
|
|
598
|
+
var seq = app.project.activeSequence;
|
|
599
|
+
if (!seq) return __error("No active sequence");
|
|
600
|
+
var trackIdx = ${args.track_index};
|
|
601
|
+
var clipIdx = ${args.clip_index};
|
|
602
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
603
|
+
var track = seq.audioTracks[trackIdx];
|
|
604
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
605
|
+
app.enableQE();
|
|
606
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
607
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
608
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
609
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
610
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
611
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
612
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
613
|
+
var effectNames = ["DeEsser", "De-Esser", "Spectral DeEsser"];
|
|
614
|
+
var effect = null;
|
|
615
|
+
for (var i = 0; i < effectNames.length; i++) {
|
|
616
|
+
try { effect = qe.project.getAudioEffectByName(effectNames[i]); } catch(e) {}
|
|
617
|
+
if (effect) break;
|
|
618
|
+
}
|
|
619
|
+
if (!effect) return __error("DeEsser effect not found.");
|
|
620
|
+
qeClip.addAudioEffect(effect);
|
|
621
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: "DeEsser", frequency: ${frequency}, reduction: ${reduction}, applied: true, note: "Effect added. Adjust parameters in Effect Controls panel." });
|
|
622
|
+
`);
|
|
623
|
+
return sendCommand(script, bridgeOptions);
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
apply_audio_noise_reduction: {
|
|
627
|
+
description: "Apply DeNoise audio effect to reduce background noise in a clip.",
|
|
628
|
+
parameters: {
|
|
629
|
+
type: "object",
|
|
630
|
+
properties: {
|
|
631
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
632
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
633
|
+
amount: { type: "number", description: "Noise reduction amount 0-100. Default: 50." },
|
|
634
|
+
},
|
|
635
|
+
required: ["track_index", "clip_index"],
|
|
636
|
+
},
|
|
637
|
+
handler: async (args) => {
|
|
638
|
+
const amount = args.amount ?? 50;
|
|
639
|
+
const script = buildToolScript(`
|
|
640
|
+
var seq = app.project.activeSequence;
|
|
641
|
+
if (!seq) return __error("No active sequence");
|
|
642
|
+
var trackIdx = ${args.track_index};
|
|
643
|
+
var clipIdx = ${args.clip_index};
|
|
644
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
645
|
+
var track = seq.audioTracks[trackIdx];
|
|
646
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
647
|
+
app.enableQE();
|
|
648
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
649
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
650
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
651
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
652
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
653
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
654
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
655
|
+
var effectNames = ["DeNoise", "Noise Reduction", "Adaptive Noise Reduction"];
|
|
656
|
+
var effect = null;
|
|
657
|
+
for (var i = 0; i < effectNames.length; i++) {
|
|
658
|
+
try { effect = qe.project.getAudioEffectByName(effectNames[i]); } catch(e) {}
|
|
659
|
+
if (effect) break;
|
|
660
|
+
}
|
|
661
|
+
if (!effect) return __error("DeNoise effect not found.");
|
|
662
|
+
qeClip.addAudioEffect(effect);
|
|
663
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: "DeNoise", amount: ${amount}, applied: true, note: "Effect added. Adjust amount parameter in Effect Controls panel." });
|
|
664
|
+
`);
|
|
665
|
+
return sendCommand(script, bridgeOptions);
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
apply_de_reverb: {
|
|
669
|
+
description: "Apply DeReverb audio effect to reduce room reverb/echo in a clip.",
|
|
670
|
+
parameters: {
|
|
671
|
+
type: "object",
|
|
672
|
+
properties: {
|
|
673
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
674
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
675
|
+
amount: { type: "number", description: "Reverb reduction amount 0-100. Default: 50." },
|
|
676
|
+
},
|
|
677
|
+
required: ["track_index", "clip_index"],
|
|
678
|
+
},
|
|
679
|
+
handler: async (args) => {
|
|
680
|
+
const amount = args.amount ?? 50;
|
|
681
|
+
const script = buildToolScript(`
|
|
682
|
+
var seq = app.project.activeSequence;
|
|
683
|
+
if (!seq) return __error("No active sequence");
|
|
684
|
+
var trackIdx = ${args.track_index};
|
|
685
|
+
var clipIdx = ${args.clip_index};
|
|
686
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
687
|
+
var track = seq.audioTracks[trackIdx];
|
|
688
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
689
|
+
app.enableQE();
|
|
690
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
691
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
692
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
693
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
694
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
695
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
696
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
697
|
+
var effectNames = ["DeReverb", "De-Reverb", "Spectral Noise Reduction"];
|
|
698
|
+
var effect = null;
|
|
699
|
+
for (var i = 0; i < effectNames.length; i++) {
|
|
700
|
+
try { effect = qe.project.getAudioEffectByName(effectNames[i]); } catch(e) {}
|
|
701
|
+
if (effect) break;
|
|
702
|
+
}
|
|
703
|
+
if (!effect) return __error("DeReverb effect not found.");
|
|
704
|
+
qeClip.addAudioEffect(effect);
|
|
705
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: "DeReverb", amount: ${amount}, applied: true, note: "Effect added. Adjust amount in Effect Controls panel." });
|
|
706
|
+
`);
|
|
707
|
+
return sendCommand(script, bridgeOptions);
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
apply_de_hum: {
|
|
711
|
+
description: "Apply DeHum audio effect to remove electrical hum (50Hz or 60Hz) from a clip.",
|
|
712
|
+
parameters: {
|
|
713
|
+
type: "object",
|
|
714
|
+
properties: {
|
|
715
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
716
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
717
|
+
frequency: { type: "number", description: "Hum frequency in Hz. Use 50 (Europe) or 60 (Americas). Default: 60." },
|
|
718
|
+
},
|
|
719
|
+
required: ["track_index", "clip_index"],
|
|
720
|
+
},
|
|
721
|
+
handler: async (args) => {
|
|
722
|
+
const frequency = args.frequency ?? 60;
|
|
723
|
+
const script = buildToolScript(`
|
|
724
|
+
var seq = app.project.activeSequence;
|
|
725
|
+
if (!seq) return __error("No active sequence");
|
|
726
|
+
var trackIdx = ${args.track_index};
|
|
727
|
+
var clipIdx = ${args.clip_index};
|
|
728
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
729
|
+
var track = seq.audioTracks[trackIdx];
|
|
730
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
731
|
+
app.enableQE();
|
|
732
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
733
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
734
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
735
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
736
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
737
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
738
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
739
|
+
var effectNames = ["DeHummer", "DeHum", "Notch Filter"];
|
|
740
|
+
var effect = null;
|
|
741
|
+
for (var i = 0; i < effectNames.length; i++) {
|
|
742
|
+
try { effect = qe.project.getAudioEffectByName(effectNames[i]); } catch(e) {}
|
|
743
|
+
if (effect) break;
|
|
744
|
+
}
|
|
745
|
+
if (!effect) return __error("DeHummer effect not found.");
|
|
746
|
+
qeClip.addAudioEffect(effect);
|
|
747
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: "DeHummer", frequency: ${frequency}, applied: true, note: "Effect added. Set frequency to " + ${frequency} + "Hz in Effect Controls panel." });
|
|
748
|
+
`);
|
|
749
|
+
return sendCommand(script, bridgeOptions);
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
apply_audio_preset: {
|
|
753
|
+
description: "Apply a named audio effect preset to a clip. Use get_audio_effect_presets to see available presets.",
|
|
754
|
+
parameters: {
|
|
755
|
+
type: "object",
|
|
756
|
+
properties: {
|
|
757
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
758
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
759
|
+
preset_name: { type: "string", description: "Name of the audio effect (e.g. 'Parametric Equalizer', 'Dynamics', 'Hard Limiter', 'DeEsser', 'DeHummer', 'DeNoise', 'Reverb')" },
|
|
760
|
+
},
|
|
761
|
+
required: ["track_index", "clip_index", "preset_name"],
|
|
762
|
+
},
|
|
763
|
+
handler: async (args) => {
|
|
764
|
+
const presetNameEsc = args.preset_name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
765
|
+
const script = buildToolScript(`
|
|
766
|
+
var seq = app.project.activeSequence;
|
|
767
|
+
if (!seq) return __error("No active sequence");
|
|
768
|
+
var trackIdx = ${args.track_index};
|
|
769
|
+
var clipIdx = ${args.clip_index};
|
|
770
|
+
var presetName = "${presetNameEsc}";
|
|
771
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
772
|
+
var track = seq.audioTracks[trackIdx];
|
|
773
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
774
|
+
app.enableQE();
|
|
775
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
776
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
777
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
778
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
779
|
+
if (!qeTrack) return __error("Track not found in QE");
|
|
780
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
781
|
+
if (!qeClip) return __error("Clip not found in QE");
|
|
782
|
+
var effect = null;
|
|
783
|
+
try { effect = qe.project.getAudioEffectByName(presetName); } catch(e) {}
|
|
784
|
+
if (!effect) return __error("Audio effect not found: " + presetName + ". Use get_audio_effect_presets to see available effects.");
|
|
785
|
+
qeClip.addAudioEffect(effect);
|
|
786
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effect: presetName, applied: true });
|
|
787
|
+
`);
|
|
788
|
+
return sendCommand(script, bridgeOptions);
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
get_audio_effect_presets: {
|
|
792
|
+
description: "List available audio effect presets that can be applied to clips. Shows which effects are installed.",
|
|
793
|
+
parameters: {
|
|
794
|
+
type: "object",
|
|
795
|
+
properties: {},
|
|
796
|
+
required: [],
|
|
797
|
+
},
|
|
798
|
+
handler: async (_args) => {
|
|
799
|
+
const script = buildToolScript(`
|
|
800
|
+
var commonEffects = [
|
|
801
|
+
{ name: "Parametric Equalizer", category: "EQ", description: "Multi-band parametric EQ" },
|
|
802
|
+
{ name: "Studio Equalizer", category: "EQ", description: "Graphical EQ with bands" },
|
|
803
|
+
{ name: "Graphic Equalizer (10 Band)", category: "EQ", description: "10-band graphic EQ" },
|
|
804
|
+
{ name: "Graphic Equalizer (20 Band)", category: "EQ", description: "20-band graphic EQ" },
|
|
805
|
+
{ name: "Dynamics", category: "Dynamics", description: "Compressor/expander/gate/limiter" },
|
|
806
|
+
{ name: "Multiband Compressor", category: "Dynamics", description: "Multi-band compression" },
|
|
807
|
+
{ name: "Single-band Compressor", category: "Dynamics", description: "Single-band compression" },
|
|
808
|
+
{ name: "Hard Limiter", category: "Dynamics", description: "Brick wall limiter" },
|
|
809
|
+
{ name: "Loudness Maximizer", category: "Dynamics", description: "Maximize perceived loudness" },
|
|
810
|
+
{ name: "DeNoise", category: "Restoration", description: "Reduce background noise" },
|
|
811
|
+
{ name: "DeReverb", category: "Restoration", description: "Reduce room reverb" },
|
|
812
|
+
{ name: "DeHummer", category: "Restoration", description: "Remove electrical hum" },
|
|
813
|
+
{ name: "DeEsser", category: "Restoration", description: "Reduce sibilance" },
|
|
814
|
+
{ name: "Adaptive Noise Reduction", category: "Restoration", description: "Adaptive noise gate" },
|
|
815
|
+
{ name: "Click/Pop Eliminator", category: "Restoration", description: "Remove clicks and pops" },
|
|
816
|
+
{ name: "Reverb", category: "Ambience", description: "Add room reverb" },
|
|
817
|
+
{ name: "Studio Reverb", category: "Ambience", description: "Studio reverb effect" },
|
|
818
|
+
{ name: "Convolution Reverb", category: "Ambience", description: "Convolution-based reverb" },
|
|
819
|
+
{ name: "Chorus/Flanger", category: "Modulation", description: "Chorus and flanger" },
|
|
820
|
+
{ name: "Flanger", category: "Modulation", description: "Flanger modulation" },
|
|
821
|
+
{ name: "Phaser", category: "Modulation", description: "Phaser effect" },
|
|
822
|
+
{ name: "Pitch Shifter", category: "Pitch", description: "Real-time pitch shifting" },
|
|
823
|
+
{ name: "Analog Delay", category: "Delay", description: "Tape-style delay" },
|
|
824
|
+
{ name: "Delay", category: "Delay", description: "Basic digital delay" },
|
|
825
|
+
{ name: "Notch Filter", category: "Filter", description: "Notch/band-reject filter" },
|
|
826
|
+
{ name: "High Pass", category: "Filter", description: "High-pass filter" },
|
|
827
|
+
{ name: "Low Pass", category: "Filter", description: "Low-pass filter" }
|
|
828
|
+
];
|
|
829
|
+
return __result({ effects: commonEffects, note: "List shows common Premiere Pro audio effects. Available effects depend on your installation and installed audio plugins." });
|
|
830
|
+
`);
|
|
831
|
+
return sendCommand(script, bridgeOptions);
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
get_loudness_info: {
|
|
835
|
+
description: "Get loudness information for an audio clip including current level and duration.",
|
|
836
|
+
parameters: {
|
|
837
|
+
type: "object",
|
|
838
|
+
properties: {
|
|
839
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
840
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
841
|
+
},
|
|
842
|
+
required: ["track_index", "clip_index"],
|
|
843
|
+
},
|
|
844
|
+
handler: async (args) => {
|
|
845
|
+
const script = buildToolScript(`
|
|
846
|
+
var seq = app.project.activeSequence;
|
|
847
|
+
if (!seq) return __error("No active sequence");
|
|
848
|
+
var trackIdx = ${args.track_index};
|
|
849
|
+
var clipIdx = ${args.clip_index};
|
|
850
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
851
|
+
var track = seq.audioTracks[trackIdx];
|
|
852
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
853
|
+
var clip = track.clips[clipIdx];
|
|
854
|
+
var levelDb = 0;
|
|
855
|
+
var found = false;
|
|
856
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
857
|
+
var comp = clip.components[i];
|
|
858
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
859
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
860
|
+
if (comp.properties[p].displayName === "Level") {
|
|
861
|
+
try { levelDb = comp.properties[p].getValue(); found = true; } catch(e) {}
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (found) break;
|
|
867
|
+
}
|
|
868
|
+
var startSecs = clip.start ? __ticksToSeconds(clip.start.ticks) : 0;
|
|
869
|
+
var endSecs = clip.end ? __ticksToSeconds(clip.end.ticks) : 0;
|
|
870
|
+
var durationSecs = endSecs - startSecs;
|
|
871
|
+
return __result({
|
|
872
|
+
trackIndex: trackIdx,
|
|
873
|
+
clipIndex: clipIdx,
|
|
874
|
+
clip: clip.name,
|
|
875
|
+
levelDb: levelDb,
|
|
876
|
+
durationSeconds: durationSecs,
|
|
877
|
+
startSeconds: startSecs,
|
|
878
|
+
endSeconds: endSecs,
|
|
879
|
+
couldClip: levelDb > 0,
|
|
880
|
+
note: "LUFS integrated loudness requires dedicated loudness analysis. levelDb is the static clip gain/Level property."
|
|
881
|
+
});
|
|
882
|
+
`);
|
|
883
|
+
return sendCommand(script, bridgeOptions);
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
get_sequence_loudness: {
|
|
887
|
+
description: "Get loudness information for the entire active sequence including all audio tracks.",
|
|
888
|
+
parameters: {
|
|
889
|
+
type: "object",
|
|
890
|
+
properties: {},
|
|
891
|
+
required: [],
|
|
892
|
+
},
|
|
893
|
+
handler: async (_args) => {
|
|
894
|
+
const script = buildToolScript(`
|
|
895
|
+
var seq = app.project.activeSequence;
|
|
896
|
+
if (!seq) return __error("No active sequence");
|
|
897
|
+
var tracks = [];
|
|
898
|
+
var totalClips = 0;
|
|
899
|
+
var maxLevel = -96;
|
|
900
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
901
|
+
var track = seq.audioTracks[t];
|
|
902
|
+
var trackData = { index: t, name: track.name || ("Audio " + (t + 1)), clips: [] };
|
|
903
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
904
|
+
var clip = track.clips[c];
|
|
905
|
+
var levelDb = 0;
|
|
906
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
907
|
+
var comp = clip.components[i];
|
|
908
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
909
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
910
|
+
if (comp.properties[p].displayName === "Level") {
|
|
911
|
+
try { levelDb = comp.properties[p].getValue(); } catch(e) {}
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (levelDb > maxLevel) maxLevel = levelDb;
|
|
919
|
+
trackData.clips.push({ name: clip.name, levelDb: levelDb, start: __ticksToSeconds(clip.start.ticks), end: __ticksToSeconds(clip.end.ticks) });
|
|
920
|
+
totalClips++;
|
|
921
|
+
}
|
|
922
|
+
tracks.push(trackData);
|
|
923
|
+
}
|
|
924
|
+
return __result({ tracks: tracks, totalClips: totalClips, maxLevelDb: maxLevel, couldClip: maxLevel > 0 });
|
|
925
|
+
`);
|
|
926
|
+
return sendCommand(script, bridgeOptions);
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
detect_clipping: {
|
|
930
|
+
description: "Detect potential audio clipping in a clip. Returns whether the clip level could cause clipping.",
|
|
931
|
+
parameters: {
|
|
932
|
+
type: "object",
|
|
933
|
+
properties: {
|
|
934
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
935
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
936
|
+
},
|
|
937
|
+
required: ["track_index", "clip_index"],
|
|
938
|
+
},
|
|
939
|
+
handler: async (args) => {
|
|
940
|
+
const script = buildToolScript(`
|
|
941
|
+
var seq = app.project.activeSequence;
|
|
942
|
+
if (!seq) return __error("No active sequence");
|
|
943
|
+
var trackIdx = ${args.track_index};
|
|
944
|
+
var clipIdx = ${args.clip_index};
|
|
945
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
946
|
+
var track = seq.audioTracks[trackIdx];
|
|
947
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
948
|
+
var clip = track.clips[clipIdx];
|
|
949
|
+
var levelDb = 0;
|
|
950
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
951
|
+
var comp = clip.components[i];
|
|
952
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
953
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
954
|
+
if (comp.properties[p].displayName === "Level") {
|
|
955
|
+
try { levelDb = comp.properties[p].getValue(); } catch(e) {}
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
var couldClip = levelDb > 0;
|
|
963
|
+
return __result({
|
|
964
|
+
trackIndex: trackIdx,
|
|
965
|
+
clipIndex: clipIdx,
|
|
966
|
+
clip: clip.name,
|
|
967
|
+
levelDb: levelDb,
|
|
968
|
+
couldClip: couldClip,
|
|
969
|
+
message: couldClip ? "WARNING: Clip level is " + levelDb + " dB (above 0 dB) - may cause digital clipping." : "OK: Clip level " + levelDb + " dB is at or below 0 dB."
|
|
970
|
+
});
|
|
971
|
+
`);
|
|
972
|
+
return sendCommand(script, bridgeOptions);
|
|
973
|
+
},
|
|
974
|
+
},
|
|
975
|
+
normalize_all_audio: {
|
|
976
|
+
description: "Normalize all audio clips on all tracks to a target dB level.",
|
|
977
|
+
parameters: {
|
|
978
|
+
type: "object",
|
|
979
|
+
properties: {
|
|
980
|
+
target_db: { type: "number", description: "Target normalization level in dB (e.g. -6 for -6dBFS). Default: -6." },
|
|
981
|
+
},
|
|
982
|
+
required: [],
|
|
983
|
+
},
|
|
984
|
+
handler: async (args) => {
|
|
985
|
+
const targetDb = args.target_db ?? -6;
|
|
986
|
+
const script = buildToolScript(`
|
|
987
|
+
var seq = app.project.activeSequence;
|
|
988
|
+
if (!seq) return __error("No active sequence");
|
|
989
|
+
var targetDb = ${targetDb};
|
|
990
|
+
var totalSet = 0;
|
|
991
|
+
var errors = [];
|
|
992
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
993
|
+
var track = seq.audioTracks[t];
|
|
994
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
995
|
+
var clip = track.clips[c];
|
|
996
|
+
var found = false;
|
|
997
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
998
|
+
var comp = clip.components[i];
|
|
999
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
1000
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
1001
|
+
if (comp.properties[p].displayName === "Level") {
|
|
1002
|
+
try {
|
|
1003
|
+
comp.properties[p].setValue(targetDb, true);
|
|
1004
|
+
totalSet++;
|
|
1005
|
+
found = true;
|
|
1006
|
+
} catch(e) {
|
|
1007
|
+
errors.push("Track " + t + " clip " + c + ": " + e.toString());
|
|
1008
|
+
}
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (found) break;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return __result({ targetDb: targetDb, clipsNormalized: totalSet, errors: errors });
|
|
1018
|
+
`);
|
|
1019
|
+
return sendCommand(script, bridgeOptions);
|
|
1020
|
+
},
|
|
1021
|
+
},
|
|
1022
|
+
mute_all_audio_tracks: {
|
|
1023
|
+
description: "Mute all audio tracks in the active sequence.",
|
|
1024
|
+
parameters: {
|
|
1025
|
+
type: "object",
|
|
1026
|
+
properties: {},
|
|
1027
|
+
required: [],
|
|
1028
|
+
},
|
|
1029
|
+
handler: async (_args) => {
|
|
1030
|
+
const script = buildToolScript(`
|
|
1031
|
+
var seq = app.project.activeSequence;
|
|
1032
|
+
if (!seq) return __error("No active sequence");
|
|
1033
|
+
var count = 0;
|
|
1034
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
1035
|
+
var track = seq.audioTracks[t];
|
|
1036
|
+
try {
|
|
1037
|
+
if (track.setMute) { track.setMute(1); count++; }
|
|
1038
|
+
} catch(e) {}
|
|
1039
|
+
}
|
|
1040
|
+
return __result({ muted: count, totalTracks: seq.audioTracks.numTracks });
|
|
1041
|
+
`);
|
|
1042
|
+
return sendCommand(script, bridgeOptions);
|
|
1043
|
+
},
|
|
1044
|
+
},
|
|
1045
|
+
unmute_all_audio_tracks: {
|
|
1046
|
+
description: "Unmute all audio tracks in the active sequence.",
|
|
1047
|
+
parameters: {
|
|
1048
|
+
type: "object",
|
|
1049
|
+
properties: {},
|
|
1050
|
+
required: [],
|
|
1051
|
+
},
|
|
1052
|
+
handler: async (_args) => {
|
|
1053
|
+
const script = buildToolScript(`
|
|
1054
|
+
var seq = app.project.activeSequence;
|
|
1055
|
+
if (!seq) return __error("No active sequence");
|
|
1056
|
+
var count = 0;
|
|
1057
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
1058
|
+
var track = seq.audioTracks[t];
|
|
1059
|
+
try {
|
|
1060
|
+
if (track.setMute) { track.setMute(0); count++; }
|
|
1061
|
+
} catch(e) {}
|
|
1062
|
+
}
|
|
1063
|
+
return __result({ unmuted: count, totalTracks: seq.audioTracks.numTracks });
|
|
1064
|
+
`);
|
|
1065
|
+
return sendCommand(script, bridgeOptions);
|
|
1066
|
+
},
|
|
1067
|
+
},
|
|
1068
|
+
create_submix: {
|
|
1069
|
+
description: "Create a submix (bus) track in the active sequence for routing multiple tracks.",
|
|
1070
|
+
parameters: {
|
|
1071
|
+
type: "object",
|
|
1072
|
+
properties: {
|
|
1073
|
+
name: { type: "string", description: "Name for the submix track. Default: 'Submix'." },
|
|
1074
|
+
channel_type: { type: "string", description: "Channel type: 'mono', 'stereo', '5.1', or 'adaptive'. Default: 'stereo'." },
|
|
1075
|
+
},
|
|
1076
|
+
required: [],
|
|
1077
|
+
},
|
|
1078
|
+
handler: async (args) => {
|
|
1079
|
+
const name = (args.name || "Submix").replace(/"/g, '\\"');
|
|
1080
|
+
const channelType = (args.channel_type || "stereo").replace(/"/g, '\\"');
|
|
1081
|
+
const script = buildToolScript(`
|
|
1082
|
+
var seq = app.project.activeSequence;
|
|
1083
|
+
if (!seq) return __error("No active sequence");
|
|
1084
|
+
var trackName = "${name}";
|
|
1085
|
+
var channelType = "${channelType}";
|
|
1086
|
+
try {
|
|
1087
|
+
app.enableQE();
|
|
1088
|
+
if (qe && qe.project) {
|
|
1089
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
1090
|
+
if (qeSeq && qeSeq.addAudioSubmixTrack) {
|
|
1091
|
+
var channelTypeMap = { "mono": 1, "stereo": 2, "5.1": 6, "adaptive": 0 };
|
|
1092
|
+
var chType = channelTypeMap[channelType] !== undefined ? channelTypeMap[channelType] : 2;
|
|
1093
|
+
qeSeq.addAudioSubmixTrack(chType);
|
|
1094
|
+
return __result({ name: trackName, channelType: channelType, created: true });
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
} catch(e) {
|
|
1098
|
+
return __error("Could not create submix track via QE DOM: " + e.toString());
|
|
1099
|
+
}
|
|
1100
|
+
return __result({ note: "Submix track creation not available via this API in the current Premiere version. Add via Sequence > Add Tracks in the Premiere Pro UI.", requestedName: trackName, requestedChannelType: channelType });
|
|
1101
|
+
`);
|
|
1102
|
+
return sendCommand(script, bridgeOptions);
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
prepare_voiceover_track: {
|
|
1106
|
+
description: "Prepare an audio track for voiceover recording by muting all other tracks and targeting this one.",
|
|
1107
|
+
parameters: {
|
|
1108
|
+
type: "object",
|
|
1109
|
+
properties: {
|
|
1110
|
+
track_index: { type: "number", description: "Audio track index to set up for voiceover (0-based)" },
|
|
1111
|
+
},
|
|
1112
|
+
required: ["track_index"],
|
|
1113
|
+
},
|
|
1114
|
+
handler: async (args) => {
|
|
1115
|
+
const script = buildToolScript(`
|
|
1116
|
+
var seq = app.project.activeSequence;
|
|
1117
|
+
if (!seq) return __error("No active sequence");
|
|
1118
|
+
var voTrackIdx = ${args.track_index};
|
|
1119
|
+
if (voTrackIdx >= seq.audioTracks.numTracks) return __error("Track index " + voTrackIdx + " out of range");
|
|
1120
|
+
var mutedCount = 0;
|
|
1121
|
+
var unmutedCount = 0;
|
|
1122
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
1123
|
+
var track = seq.audioTracks[t];
|
|
1124
|
+
if (t !== voTrackIdx) {
|
|
1125
|
+
try { if (track.setMute) { track.setMute(1); mutedCount++; } } catch(e) {}
|
|
1126
|
+
} else {
|
|
1127
|
+
try { if (track.setMute) { track.setMute(0); unmutedCount++; } } catch(e) {}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return __result({ voTrackIndex: voTrackIdx, voTrackName: seq.audioTracks[voTrackIdx].name || ("Audio " + (voTrackIdx + 1)), otherTracksMuted: mutedCount, voTrackActive: true });
|
|
1131
|
+
`);
|
|
1132
|
+
return sendCommand(script, bridgeOptions);
|
|
1133
|
+
},
|
|
1134
|
+
},
|
|
1135
|
+
set_voiceover_ducking: {
|
|
1136
|
+
description: "Set up automatic audio ducking so music lowers when voiceover is present. Adds volume keyframes on music track around VO clips.",
|
|
1137
|
+
parameters: {
|
|
1138
|
+
type: "object",
|
|
1139
|
+
properties: {
|
|
1140
|
+
vo_track_index: { type: "number", description: "Voiceover track index (0-based)" },
|
|
1141
|
+
music_track_index: { type: "number", description: "Music track index to duck (0-based)" },
|
|
1142
|
+
duck_amount: { type: "number", description: "How much to lower the music in dB (e.g. -15). Default: -15." },
|
|
1143
|
+
sensitivity: { type: "number", description: "Fade duration in seconds before/after VO clip. Default: 0.5." },
|
|
1144
|
+
},
|
|
1145
|
+
required: ["vo_track_index", "music_track_index"],
|
|
1146
|
+
},
|
|
1147
|
+
handler: async (args) => {
|
|
1148
|
+
const duckAmount = args.duck_amount ?? -15;
|
|
1149
|
+
const sensitivity = args.sensitivity ?? 0.5;
|
|
1150
|
+
const script = buildToolScript(`
|
|
1151
|
+
var seq = app.project.activeSequence;
|
|
1152
|
+
if (!seq) return __error("No active sequence");
|
|
1153
|
+
var voTrackIdx = ${args.vo_track_index};
|
|
1154
|
+
var musicTrackIdx = ${args.music_track_index};
|
|
1155
|
+
var duckAmount = ${duckAmount};
|
|
1156
|
+
var fadeDuration = ${sensitivity};
|
|
1157
|
+
if (voTrackIdx >= seq.audioTracks.numTracks) return __error("VO track index " + voTrackIdx + " out of range");
|
|
1158
|
+
if (musicTrackIdx >= seq.audioTracks.numTracks) return __error("Music track index " + musicTrackIdx + " out of range");
|
|
1159
|
+
var voTrack = seq.audioTracks[voTrackIdx];
|
|
1160
|
+
var musicTrack = seq.audioTracks[musicTrackIdx];
|
|
1161
|
+
var processedClips = 0;
|
|
1162
|
+
var errors = [];
|
|
1163
|
+
for (var mc = 0; mc < musicTrack.clips.numItems; mc++) {
|
|
1164
|
+
var musicClip = musicTrack.clips[mc];
|
|
1165
|
+
var levelProp = null;
|
|
1166
|
+
for (var ci = 0; ci < musicClip.components.numItems; ci++) {
|
|
1167
|
+
var comp = musicClip.components[ci];
|
|
1168
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
1169
|
+
for (var pi = 0; pi < comp.properties.numItems; pi++) {
|
|
1170
|
+
if (comp.properties[pi].displayName === "Level") {
|
|
1171
|
+
levelProp = comp.properties[pi];
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (levelProp) break;
|
|
1177
|
+
}
|
|
1178
|
+
if (!levelProp) continue;
|
|
1179
|
+
levelProp.setTimeVarying(true);
|
|
1180
|
+
var musicStart = __ticksToSeconds(musicClip.start.ticks);
|
|
1181
|
+
var musicEnd = __ticksToSeconds(musicClip.end.ticks);
|
|
1182
|
+
for (var vc = 0; vc < voTrack.clips.numItems; vc++) {
|
|
1183
|
+
var voClip = voTrack.clips[vc];
|
|
1184
|
+
var voStart = __ticksToSeconds(voClip.start.ticks);
|
|
1185
|
+
var voEnd = __ticksToSeconds(voClip.end.ticks);
|
|
1186
|
+
if (voStart >= musicEnd || voEnd <= musicStart) continue;
|
|
1187
|
+
var fadeInTime = Math.max(musicStart, voStart - fadeDuration);
|
|
1188
|
+
var fadeInEnd = Math.min(musicEnd, voStart);
|
|
1189
|
+
var fadeOutStart = Math.max(musicStart, voEnd);
|
|
1190
|
+
var fadeOutEnd = Math.min(musicEnd, voEnd + fadeDuration);
|
|
1191
|
+
try {
|
|
1192
|
+
var t1 = new Time(); t1.ticks = String(__secondsToTicks(fadeInTime));
|
|
1193
|
+
levelProp.addKey(t1); levelProp.setValueAtKey(t1, 0);
|
|
1194
|
+
var t2 = new Time(); t2.ticks = String(__secondsToTicks(fadeInEnd));
|
|
1195
|
+
levelProp.addKey(t2); levelProp.setValueAtKey(t2, duckAmount);
|
|
1196
|
+
var t3 = new Time(); t3.ticks = String(__secondsToTicks(fadeOutStart));
|
|
1197
|
+
levelProp.addKey(t3); levelProp.setValueAtKey(t3, duckAmount);
|
|
1198
|
+
var t4 = new Time(); t4.ticks = String(__secondsToTicks(fadeOutEnd));
|
|
1199
|
+
levelProp.addKey(t4); levelProp.setValueAtKey(t4, 0);
|
|
1200
|
+
processedClips++;
|
|
1201
|
+
} catch(e) {
|
|
1202
|
+
errors.push("VO clip " + vc + ": " + e.toString());
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return __result({ voTrackIndex: voTrackIdx, musicTrackIndex: musicTrackIdx, duckAmount: duckAmount, fadeDuration: fadeDuration, duckingRegionsAdded: processedClips, errors: errors });
|
|
1207
|
+
`);
|
|
1208
|
+
return sendCommand(script, bridgeOptions);
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
set_audio_track_volume: {
|
|
1212
|
+
description: "Set the volume level for an entire audio track (track-level fader, not clip-level).",
|
|
1213
|
+
parameters: {
|
|
1214
|
+
type: "object",
|
|
1215
|
+
properties: {
|
|
1216
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
1217
|
+
level_db: { type: "number", description: "Track volume in dB. Range: -96 to +15. 0 = unity." },
|
|
1218
|
+
},
|
|
1219
|
+
required: ["track_index", "level_db"],
|
|
1220
|
+
},
|
|
1221
|
+
handler: async (args) => {
|
|
1222
|
+
const script = buildToolScript(`
|
|
1223
|
+
var seq = app.project.activeSequence;
|
|
1224
|
+
if (!seq) return __error("No active sequence");
|
|
1225
|
+
var trackIdx = ${args.track_index};
|
|
1226
|
+
var levelDb = ${args.level_db};
|
|
1227
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
1228
|
+
var track = seq.audioTracks[trackIdx];
|
|
1229
|
+
var found = false;
|
|
1230
|
+
if (track.components) {
|
|
1231
|
+
for (var ci = 0; ci < track.components.numItems; ci++) {
|
|
1232
|
+
var comp = track.components[ci];
|
|
1233
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
1234
|
+
for (var pi = 0; pi < comp.properties.numItems; pi++) {
|
|
1235
|
+
if (comp.properties[pi].displayName === "Level") {
|
|
1236
|
+
comp.properties[pi].setValue(levelDb, true);
|
|
1237
|
+
found = true;
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (found) break;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
if (!found) return __result({ note: "Track-level Volume component not found on track " + trackIdx + ". Track fader may not be accessible via ExtendScript.", trackIndex: trackIdx, requestedLevel: levelDb });
|
|
1246
|
+
return __result({ trackIndex: trackIdx, levelDb: levelDb, applied: true });
|
|
1247
|
+
`);
|
|
1248
|
+
return sendCommand(script, bridgeOptions);
|
|
1249
|
+
},
|
|
1250
|
+
},
|
|
1251
|
+
solo_audio_track: {
|
|
1252
|
+
description: "Solo or unsolo an audio track (only this track plays when soloed).",
|
|
1253
|
+
parameters: {
|
|
1254
|
+
type: "object",
|
|
1255
|
+
properties: {
|
|
1256
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
1257
|
+
soloed: { type: "boolean", description: "True to solo, false to unsolo" },
|
|
1258
|
+
},
|
|
1259
|
+
required: ["track_index", "soloed"],
|
|
1260
|
+
},
|
|
1261
|
+
handler: async (args) => {
|
|
1262
|
+
const script = buildToolScript(`
|
|
1263
|
+
var seq = app.project.activeSequence;
|
|
1264
|
+
if (!seq) return __error("No active sequence");
|
|
1265
|
+
var trackIdx = ${args.track_index};
|
|
1266
|
+
var soloed = ${args.soloed};
|
|
1267
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
1268
|
+
var track = seq.audioTracks[trackIdx];
|
|
1269
|
+
if (track.setSolo) {
|
|
1270
|
+
track.setSolo(soloed ? 1 : 0);
|
|
1271
|
+
return __result({ trackIndex: trackIdx, soloed: soloed });
|
|
1272
|
+
}
|
|
1273
|
+
return __result({ note: "setSolo not supported on this track type in the current Premiere version.", trackIndex: trackIdx, requestedSolo: soloed });
|
|
1274
|
+
`);
|
|
1275
|
+
return sendCommand(script, bridgeOptions);
|
|
1276
|
+
},
|
|
1277
|
+
},
|
|
1278
|
+
convert_stereo_to_mono: {
|
|
1279
|
+
description: "Convert a stereo project item to mono by duplicating it as mono channels.",
|
|
1280
|
+
parameters: {
|
|
1281
|
+
type: "object",
|
|
1282
|
+
properties: {
|
|
1283
|
+
project_item_index: { type: "number", description: "Project item index (0-based, from root item children)" },
|
|
1284
|
+
},
|
|
1285
|
+
required: ["project_item_index"],
|
|
1286
|
+
},
|
|
1287
|
+
handler: async (args) => {
|
|
1288
|
+
const script = buildToolScript(`
|
|
1289
|
+
var itemIndex = ${args.project_item_index};
|
|
1290
|
+
if (!app.project || !app.project.rootItem) return __error("No project open");
|
|
1291
|
+
if (itemIndex >= app.project.rootItem.children.numItems) return __error("Item index " + itemIndex + " out of range");
|
|
1292
|
+
var item = app.project.rootItem.children[itemIndex];
|
|
1293
|
+
try {
|
|
1294
|
+
var interp = item.getFootageInterpretation();
|
|
1295
|
+
if (!interp) return __error("Cannot get footage interpretation for: " + item.name);
|
|
1296
|
+
// audioChannelType: 0=Stereo, 1=Mono, 2=5.1, 4=Adaptive
|
|
1297
|
+
var prevType = interp.audioChannelType;
|
|
1298
|
+
interp.audioChannelType = 1; // Mono
|
|
1299
|
+
item.setFootageInterpretation(interp);
|
|
1300
|
+
return __result({ item: item.name, index: itemIndex, converted: true,
|
|
1301
|
+
previousChannelType: prevType, newChannelType: 1,
|
|
1302
|
+
note: "Audio channel type set to Mono (1). This affects how PP treats the audio channels of this item." });
|
|
1303
|
+
} catch(e) {
|
|
1304
|
+
return __error("Could not set audio channel type: " + e.toString());
|
|
1305
|
+
}
|
|
1306
|
+
`);
|
|
1307
|
+
return sendCommand(script, bridgeOptions);
|
|
1308
|
+
},
|
|
1309
|
+
},
|
|
1310
|
+
extract_audio_from_video: {
|
|
1311
|
+
description: "Extract the audio from a video project item as a separate audio clip in the project.",
|
|
1312
|
+
parameters: {
|
|
1313
|
+
type: "object",
|
|
1314
|
+
properties: {
|
|
1315
|
+
project_item_index: { type: "number", description: "Project item index (0-based, from root item children)" },
|
|
1316
|
+
},
|
|
1317
|
+
required: ["project_item_index"],
|
|
1318
|
+
},
|
|
1319
|
+
handler: async (args) => {
|
|
1320
|
+
const script = buildToolScript(`
|
|
1321
|
+
var itemIndex = ${args.project_item_index};
|
|
1322
|
+
if (!app.project || !app.project.rootItem) return __error("No project open");
|
|
1323
|
+
if (itemIndex >= app.project.rootItem.children.numItems) return __error("Item index " + itemIndex + " out of range");
|
|
1324
|
+
var item = app.project.rootItem.children[itemIndex];
|
|
1325
|
+
var mediaPath = "";
|
|
1326
|
+
try { mediaPath = item.getMediaPath(); } catch(e) {}
|
|
1327
|
+
// Gather audio interpretation info
|
|
1328
|
+
var audioInfo = {};
|
|
1329
|
+
try {
|
|
1330
|
+
var interp = item.getFootageInterpretation();
|
|
1331
|
+
if (interp) {
|
|
1332
|
+
audioInfo.channelType = interp.audioChannelType;
|
|
1333
|
+
audioInfo.numChannels = interp.audioNumChannels;
|
|
1334
|
+
}
|
|
1335
|
+
} catch(e) {}
|
|
1336
|
+
return __result({ item: item.name, index: itemIndex, mediaPath: mediaPath,
|
|
1337
|
+
audioInfo: audioInfo,
|
|
1338
|
+
note: "To extract audio as a separate file, use export_audio_only tool with this item's media path as source. Audio interpretation info shown above." });
|
|
1339
|
+
`);
|
|
1340
|
+
return sendCommand(script, bridgeOptions);
|
|
1341
|
+
},
|
|
1342
|
+
},
|
|
1343
|
+
// NEW ENHANCED AUDIO TOOLS
|
|
1344
|
+
apply_audio_effect: {
|
|
1345
|
+
description: "Apply any audio effect to a clip by exact name using the QE DOM.",
|
|
1346
|
+
parameters: {
|
|
1347
|
+
type: "object",
|
|
1348
|
+
properties: {
|
|
1349
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
1350
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
1351
|
+
effect_name: { type: "string", description: "Exact name of the audio effect to apply (e.g. 'Reverb', 'Chorus/Flanger', 'Pitch Shifter')" },
|
|
1352
|
+
},
|
|
1353
|
+
required: ["track_index", "clip_index", "effect_name"],
|
|
1354
|
+
},
|
|
1355
|
+
handler: async (args) => {
|
|
1356
|
+
const effectNameEsc = args.effect_name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1357
|
+
const script = buildToolScript(`
|
|
1358
|
+
var seq = app.project.activeSequence;
|
|
1359
|
+
if (!seq) return __error("No active sequence");
|
|
1360
|
+
var trackIdx = ${args.track_index};
|
|
1361
|
+
var clipIdx = ${args.clip_index};
|
|
1362
|
+
var effectName = "${effectNameEsc}";
|
|
1363
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
1364
|
+
var track = seq.audioTracks[trackIdx];
|
|
1365
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
1366
|
+
app.enableQE();
|
|
1367
|
+
if (!qe || !qe.project) return __error("QE DOM not available");
|
|
1368
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
1369
|
+
if (!qeSeq) return __error("No active sequence in QE DOM");
|
|
1370
|
+
var qeTrack = qeSeq.getAudioTrackAt(trackIdx);
|
|
1371
|
+
if (!qeTrack) return __error("Track " + trackIdx + " not found in QE");
|
|
1372
|
+
var qeClip = qeTrack.getItemAt(clipIdx);
|
|
1373
|
+
if (!qeClip) return __error("Clip " + clipIdx + " not found in QE");
|
|
1374
|
+
var effect = null;
|
|
1375
|
+
try { effect = qe.project.getAudioEffectByName(effectName); } catch(e) {}
|
|
1376
|
+
if (!effect) return __error("Audio effect not found: " + effectName + ". Use get_audio_effect_presets for available effects.");
|
|
1377
|
+
qeClip.addAudioEffect(effect);
|
|
1378
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, effectName: effectName, applied: true });
|
|
1379
|
+
`);
|
|
1380
|
+
return sendCommand(script, bridgeOptions);
|
|
1381
|
+
},
|
|
1382
|
+
},
|
|
1383
|
+
fade_in_audio: {
|
|
1384
|
+
description: "Add volume keyframes for a fade-in at the start of an audio clip.",
|
|
1385
|
+
parameters: {
|
|
1386
|
+
type: "object",
|
|
1387
|
+
properties: {
|
|
1388
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
1389
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
1390
|
+
duration_seconds: { type: "number", description: "Fade-in duration in seconds" },
|
|
1391
|
+
target_db: { type: "number", description: "Target level in dB at end of fade-in. Default: 0 dB." },
|
|
1392
|
+
},
|
|
1393
|
+
required: ["track_index", "clip_index", "duration_seconds"],
|
|
1394
|
+
},
|
|
1395
|
+
handler: async (args) => {
|
|
1396
|
+
const targetDb = args.target_db ?? 0;
|
|
1397
|
+
const script = buildToolScript(`
|
|
1398
|
+
var seq = app.project.activeSequence;
|
|
1399
|
+
if (!seq) return __error("No active sequence");
|
|
1400
|
+
var trackIdx = ${args.track_index};
|
|
1401
|
+
var clipIdx = ${args.clip_index};
|
|
1402
|
+
var fadeDuration = ${args.duration_seconds};
|
|
1403
|
+
var targetDb = ${targetDb};
|
|
1404
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
1405
|
+
var track = seq.audioTracks[trackIdx];
|
|
1406
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
1407
|
+
var clip = track.clips[clipIdx];
|
|
1408
|
+
var volComp = __findIntrinsic(clip, "volume");
|
|
1409
|
+
if (!volComp) return __error("Volume component not found on clip");
|
|
1410
|
+
var levelProp = __findProp(volComp, ["Level", "Nivel", "Pegel", "Volume"]);
|
|
1411
|
+
if (!levelProp) return __error("Volume Level property not found on clip");
|
|
1412
|
+
var clipStart = __ticksToSeconds(clip.start.ticks);
|
|
1413
|
+
var clipEnd = __ticksToSeconds(clip.end.ticks);
|
|
1414
|
+
var fadeEnd = Math.min(clipStart + fadeDuration, clipEnd);
|
|
1415
|
+
levelProp.setTimeVarying(true);
|
|
1416
|
+
var t1 = new Time(); t1.ticks = String(__secondsToTicks(clipStart));
|
|
1417
|
+
levelProp.addKey(t1); levelProp.setValueAtKey(t1, -96);
|
|
1418
|
+
var t2 = new Time(); t2.ticks = String(__secondsToTicks(fadeEnd));
|
|
1419
|
+
levelProp.addKey(t2); levelProp.setValueAtKey(t2, targetDb);
|
|
1420
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, clip: clip.name, fadeStart: clipStart, fadeEnd: fadeEnd, startDb: -96, endDb: targetDb });
|
|
1421
|
+
`);
|
|
1422
|
+
return sendCommand(script, bridgeOptions);
|
|
1423
|
+
},
|
|
1424
|
+
},
|
|
1425
|
+
fade_out_audio: {
|
|
1426
|
+
description: "Add volume keyframes for a fade-out at the end of an audio clip.",
|
|
1427
|
+
parameters: {
|
|
1428
|
+
type: "object",
|
|
1429
|
+
properties: {
|
|
1430
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
1431
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
1432
|
+
duration_seconds: { type: "number", description: "Fade-out duration in seconds" },
|
|
1433
|
+
start_db: { type: "number", description: "Starting level in dB at beginning of fade-out. Default: 0 dB." },
|
|
1434
|
+
},
|
|
1435
|
+
required: ["track_index", "clip_index", "duration_seconds"],
|
|
1436
|
+
},
|
|
1437
|
+
handler: async (args) => {
|
|
1438
|
+
const startDb = args.start_db ?? 0;
|
|
1439
|
+
const script = buildToolScript(`
|
|
1440
|
+
var seq = app.project.activeSequence;
|
|
1441
|
+
if (!seq) return __error("No active sequence");
|
|
1442
|
+
var trackIdx = ${args.track_index};
|
|
1443
|
+
var clipIdx = ${args.clip_index};
|
|
1444
|
+
var fadeDuration = ${args.duration_seconds};
|
|
1445
|
+
var startDb = ${startDb};
|
|
1446
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
1447
|
+
var track = seq.audioTracks[trackIdx];
|
|
1448
|
+
if (clipIdx >= track.clips.numItems) return __error("Clip index " + clipIdx + " out of range");
|
|
1449
|
+
var clip = track.clips[clipIdx];
|
|
1450
|
+
var volComp = __findIntrinsic(clip, "volume");
|
|
1451
|
+
if (!volComp) return __error("Volume component not found on clip");
|
|
1452
|
+
var levelProp = __findProp(volComp, ["Level", "Nivel", "Pegel", "Volume"]);
|
|
1453
|
+
if (!levelProp) return __error("Volume Level property not found on clip");
|
|
1454
|
+
var clipStart = __ticksToSeconds(clip.start.ticks);
|
|
1455
|
+
var clipEnd = __ticksToSeconds(clip.end.ticks);
|
|
1456
|
+
var fadeStart = Math.max(clipEnd - fadeDuration, clipStart);
|
|
1457
|
+
levelProp.setTimeVarying(true);
|
|
1458
|
+
var t1 = new Time(); t1.ticks = String(__secondsToTicks(fadeStart));
|
|
1459
|
+
levelProp.addKey(t1); levelProp.setValueAtKey(t1, startDb);
|
|
1460
|
+
var t2 = new Time(); t2.ticks = String(__secondsToTicks(clipEnd));
|
|
1461
|
+
levelProp.addKey(t2); levelProp.setValueAtKey(t2, -96);
|
|
1462
|
+
return __result({ trackIndex: trackIdx, clipIndex: clipIdx, clip: clip.name, fadeStart: fadeStart, fadeEnd: clipEnd, startDb: startDb, endDb: -96 });
|
|
1463
|
+
`);
|
|
1464
|
+
return sendCommand(script, bridgeOptions);
|
|
1465
|
+
},
|
|
1466
|
+
},
|
|
1467
|
+
cross_fade_clips: {
|
|
1468
|
+
description: "Add audio crossfade between two adjacent clips on the same track (fade out clip N, fade in clip N+1).",
|
|
1469
|
+
parameters: {
|
|
1470
|
+
type: "object",
|
|
1471
|
+
properties: {
|
|
1472
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
1473
|
+
clip_index: { type: "number", description: "Index of the first clip (0-based); the crossfade is between this clip and the next one" },
|
|
1474
|
+
duration_seconds: { type: "number", description: "Total crossfade duration in seconds (split evenly between fade-out and fade-in)" },
|
|
1475
|
+
},
|
|
1476
|
+
required: ["track_index", "clip_index", "duration_seconds"],
|
|
1477
|
+
},
|
|
1478
|
+
handler: async (args) => {
|
|
1479
|
+
const script = buildToolScript(`
|
|
1480
|
+
var seq = app.project.activeSequence;
|
|
1481
|
+
if (!seq) return __error("No active sequence");
|
|
1482
|
+
var trackIdx = ${args.track_index};
|
|
1483
|
+
var clipIdx = ${args.clip_index};
|
|
1484
|
+
var totalDuration = ${args.duration_seconds};
|
|
1485
|
+
var halfDuration = totalDuration / 2;
|
|
1486
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
1487
|
+
var track = seq.audioTracks[trackIdx];
|
|
1488
|
+
if (clipIdx >= track.clips.numItems - 1) return __error("Clip index " + clipIdx + " must have a next clip for crossfade");
|
|
1489
|
+
var clip1 = track.clips[clipIdx];
|
|
1490
|
+
var clip2 = track.clips[clipIdx + 1];
|
|
1491
|
+
var getLevel = function(clip) {
|
|
1492
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
1493
|
+
var comp = clip.components[i];
|
|
1494
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
1495
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
1496
|
+
if (comp.properties[p].displayName === "Level") return comp.properties[p];
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
return null;
|
|
1501
|
+
};
|
|
1502
|
+
var level1 = getLevel(clip1);
|
|
1503
|
+
var level2 = getLevel(clip2);
|
|
1504
|
+
if (!level1) return __error("Could not find Volume Level on clip " + clipIdx);
|
|
1505
|
+
if (!level2) return __error("Could not find Volume Level on clip " + (clipIdx + 1));
|
|
1506
|
+
var clip1End = __ticksToSeconds(clip1.end.ticks);
|
|
1507
|
+
var clip1Start = __ticksToSeconds(clip1.start.ticks);
|
|
1508
|
+
var clip2Start = __ticksToSeconds(clip2.start.ticks);
|
|
1509
|
+
var clip2End = __ticksToSeconds(clip2.end.ticks);
|
|
1510
|
+
var fadeOutStart = Math.max(clip1Start, clip1End - halfDuration);
|
|
1511
|
+
level1.setTimeVarying(true);
|
|
1512
|
+
var t1a = new Time(); t1a.ticks = String(__secondsToTicks(fadeOutStart));
|
|
1513
|
+
level1.addKey(t1a); level1.setValueAtKey(t1a, 0);
|
|
1514
|
+
var t1b = new Time(); t1b.ticks = String(__secondsToTicks(clip1End));
|
|
1515
|
+
level1.addKey(t1b); level1.setValueAtKey(t1b, -96);
|
|
1516
|
+
var fadeInEnd = Math.min(clip2End, clip2Start + halfDuration);
|
|
1517
|
+
level2.setTimeVarying(true);
|
|
1518
|
+
var t2a = new Time(); t2a.ticks = String(__secondsToTicks(clip2Start));
|
|
1519
|
+
level2.addKey(t2a); level2.setValueAtKey(t2a, -96);
|
|
1520
|
+
var t2b = new Time(); t2b.ticks = String(__secondsToTicks(fadeInEnd));
|
|
1521
|
+
level2.addKey(t2b); level2.setValueAtKey(t2b, 0);
|
|
1522
|
+
return __result({ trackIndex: trackIdx, clip1Index: clipIdx, clip2Index: clipIdx + 1, clip1: clip1.name, clip2: clip2.name, fadeOutStart: fadeOutStart, clip1End: clip1End, clip2Start: clip2Start, fadeInEnd: fadeInEnd });
|
|
1523
|
+
`);
|
|
1524
|
+
return sendCommand(script, bridgeOptions);
|
|
1525
|
+
},
|
|
1526
|
+
},
|
|
1527
|
+
get_audio_track_info: {
|
|
1528
|
+
description: "Get detailed info for one audio track including name, mute state, level, panning, and all clips.",
|
|
1529
|
+
parameters: {
|
|
1530
|
+
type: "object",
|
|
1531
|
+
properties: {
|
|
1532
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
1533
|
+
},
|
|
1534
|
+
required: ["track_index"],
|
|
1535
|
+
},
|
|
1536
|
+
handler: async (args) => {
|
|
1537
|
+
const script = buildToolScript(`
|
|
1538
|
+
var seq = app.project.activeSequence;
|
|
1539
|
+
if (!seq) return __error("No active sequence");
|
|
1540
|
+
var trackIdx = ${args.track_index};
|
|
1541
|
+
if (trackIdx >= seq.audioTracks.numTracks) return __error("Track index " + trackIdx + " out of range");
|
|
1542
|
+
var track = seq.audioTracks[trackIdx];
|
|
1543
|
+
var trackLevel = 0;
|
|
1544
|
+
var trackPanning = 0;
|
|
1545
|
+
if (track.components) {
|
|
1546
|
+
for (var ci = 0; ci < track.components.numItems; ci++) {
|
|
1547
|
+
var comp = track.components[ci];
|
|
1548
|
+
if (comp.matchName === "audioVolume" || comp.displayName === "Volume") {
|
|
1549
|
+
for (var pi = 0; pi < comp.properties.numItems; pi++) {
|
|
1550
|
+
if (comp.properties[pi].displayName === "Level") {
|
|
1551
|
+
try { trackLevel = comp.properties[pi].getValue(); } catch(e) {}
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
if (comp.matchName === "audioPanner" || comp.displayName === "Panner") {
|
|
1557
|
+
for (var pi2 = 0; pi2 < comp.properties.numItems; pi2++) {
|
|
1558
|
+
var prop2 = comp.properties[pi2];
|
|
1559
|
+
if (prop2.displayName === "Balance" || prop2.displayName === "Panner" || prop2.displayName === "Left/Right Balance") {
|
|
1560
|
+
try { trackPanning = prop2.getValue(); } catch(e2) {}
|
|
1561
|
+
break;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
var clips = [];
|
|
1568
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
1569
|
+
var clip = track.clips[c];
|
|
1570
|
+
var clipLevel = 0;
|
|
1571
|
+
for (var ci2 = 0; ci2 < clip.components.numItems; ci2++) {
|
|
1572
|
+
var comp2 = clip.components[ci2];
|
|
1573
|
+
if (comp2.matchName === "audioVolume" || comp2.displayName === "Volume") {
|
|
1574
|
+
for (var pi3 = 0; pi3 < comp2.properties.numItems; pi3++) {
|
|
1575
|
+
if (comp2.properties[pi3].displayName === "Level") {
|
|
1576
|
+
try { clipLevel = comp2.properties[pi3].getValue(); } catch(e3) {}
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
break;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
clips.push({
|
|
1584
|
+
index: c,
|
|
1585
|
+
nodeId: clip.nodeId,
|
|
1586
|
+
name: clip.name,
|
|
1587
|
+
start: __ticksToSeconds(clip.start.ticks),
|
|
1588
|
+
end: __ticksToSeconds(clip.end.ticks),
|
|
1589
|
+
level: clipLevel
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
return __result({
|
|
1593
|
+
index: trackIdx,
|
|
1594
|
+
name: track.name || ("Audio " + (trackIdx + 1)),
|
|
1595
|
+
numClips: track.clips.numItems,
|
|
1596
|
+
isMuted: __readMaybe(track, "isMuted", false),
|
|
1597
|
+
level: trackLevel,
|
|
1598
|
+
panning: trackPanning,
|
|
1599
|
+
clips: clips
|
|
1600
|
+
});
|
|
1601
|
+
`);
|
|
1602
|
+
return sendCommand(script, bridgeOptions);
|
|
1603
|
+
},
|
|
1604
|
+
},
|
|
1605
|
+
set_clip_audio_volume: {
|
|
1606
|
+
description: "Set the volume level of an audio clip found by its node ID.",
|
|
1607
|
+
parameters: {
|
|
1608
|
+
type: "object",
|
|
1609
|
+
properties: {
|
|
1610
|
+
node_id: { type: "string", description: "The nodeId of the audio clip (from get_all_clips or get_audio_track_info)" },
|
|
1611
|
+
level_db: { type: "number", description: "Audio level in dB. Range: -96 to +15. 0 = unity gain." },
|
|
1612
|
+
},
|
|
1613
|
+
required: ["node_id", "level_db"],
|
|
1614
|
+
},
|
|
1615
|
+
handler: async (args) => {
|
|
1616
|
+
const nodeIdEsc = args.node_id.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1617
|
+
const script = buildToolScript(`
|
|
1618
|
+
var seq = app.project.activeSequence;
|
|
1619
|
+
if (!seq) return __error("No active sequence");
|
|
1620
|
+
var nodeId = "${nodeIdEsc}";
|
|
1621
|
+
var levelDb = ${args.level_db};
|
|
1622
|
+
var foundClip = null;
|
|
1623
|
+
var foundTrack = -1;
|
|
1624
|
+
var foundClipIdx = -1;
|
|
1625
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
1626
|
+
var track = seq.audioTracks[t];
|
|
1627
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
1628
|
+
var clip = track.clips[c];
|
|
1629
|
+
if (clip.nodeId === nodeId) {
|
|
1630
|
+
foundClip = clip;
|
|
1631
|
+
foundTrack = t;
|
|
1632
|
+
foundClipIdx = c;
|
|
1633
|
+
break;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
if (foundClip) break;
|
|
1637
|
+
}
|
|
1638
|
+
if (!foundClip) return __error("Clip with nodeId '" + nodeId + "' not found in any audio track");
|
|
1639
|
+
// Locale-resilient: __findIntrinsic uses matchName candidates first
|
|
1640
|
+
// (incl. AE.ADBE Audio Levels for PP 2026), then localized displayName.
|
|
1641
|
+
var volComp = __findIntrinsic(foundClip, "volume");
|
|
1642
|
+
if (!volComp) return __error("Volume component not found on clip");
|
|
1643
|
+
var levelProp = __findProp(volComp, ["Level", "Nivel", "Volume", "Volumen", "Pegel"]);
|
|
1644
|
+
if (!levelProp) return __error("Volume Level property not found in Volume component");
|
|
1645
|
+
levelProp.setValue(levelDb, true);
|
|
1646
|
+
return __result({ nodeId: nodeId, clip: foundClip.name, trackIndex: foundTrack, clipIndex: foundClipIdx, levelDb: levelDb });
|
|
1647
|
+
`);
|
|
1648
|
+
return sendCommand(script, bridgeOptions);
|
|
1649
|
+
},
|
|
1650
|
+
},
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
//# sourceMappingURL=audio-advanced.js.map
|