@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,787 @@
|
|
|
1
|
+
import { buildToolScript, escapeForExtendScript } from "../bridge/script-builder.js";
|
|
2
|
+
import { sendCommand } from "../bridge/file-bridge.js";
|
|
3
|
+
export function getTrackManagementTools(bridgeOptions) {
|
|
4
|
+
return {
|
|
5
|
+
rename_audio_track: {
|
|
6
|
+
description: "Rename an audio track in the active sequence.",
|
|
7
|
+
parameters: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
11
|
+
name: { type: "string", description: "New name for the track" },
|
|
12
|
+
},
|
|
13
|
+
required: ["track_index", "name"],
|
|
14
|
+
},
|
|
15
|
+
handler: async (args) => {
|
|
16
|
+
const script = buildToolScript(`
|
|
17
|
+
var seq = app.project.activeSequence;
|
|
18
|
+
if (!seq) return __error("No active sequence");
|
|
19
|
+
var track = seq.audioTracks[${args.track_index}];
|
|
20
|
+
if (!track) return __error("Audio track not found at index ${args.track_index}");
|
|
21
|
+
track.name = "${escapeForExtendScript(args.name)}";
|
|
22
|
+
return __result({ renamed: true, trackIndex: ${args.track_index}, newName: "${escapeForExtendScript(args.name)}" });
|
|
23
|
+
`);
|
|
24
|
+
return sendCommand(script, bridgeOptions);
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
mute_audio_track: {
|
|
28
|
+
description: "Mute or unmute an audio track.",
|
|
29
|
+
parameters: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
33
|
+
muted: { type: "boolean", description: "True to mute, false to unmute" },
|
|
34
|
+
},
|
|
35
|
+
required: ["track_index", "muted"],
|
|
36
|
+
},
|
|
37
|
+
handler: async (args) => {
|
|
38
|
+
const script = buildToolScript(`
|
|
39
|
+
var seq = app.project.activeSequence;
|
|
40
|
+
if (!seq) return __error("No active sequence");
|
|
41
|
+
var track = seq.audioTracks[${args.track_index}];
|
|
42
|
+
if (!track) return __error("Audio track not found at index ${args.track_index}");
|
|
43
|
+
track.setMute(${args.muted ? 1 : 0});
|
|
44
|
+
return __result({ muted: ${args.muted}, trackIndex: ${args.track_index}, trackName: track.name });
|
|
45
|
+
`);
|
|
46
|
+
return sendCommand(script, bridgeOptions);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
lock_audio_track: {
|
|
50
|
+
description: "Lock or unlock an audio track to prevent accidental edits.",
|
|
51
|
+
parameters: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
55
|
+
locked: { type: "boolean", description: "True to lock, false to unlock" },
|
|
56
|
+
},
|
|
57
|
+
required: ["track_index", "locked"],
|
|
58
|
+
},
|
|
59
|
+
handler: async (args) => {
|
|
60
|
+
const script = buildToolScript(`
|
|
61
|
+
var seq = app.project.activeSequence;
|
|
62
|
+
if (!seq) return __error("No active sequence");
|
|
63
|
+
var track = seq.audioTracks[${args.track_index}];
|
|
64
|
+
if (!track) return __error("Audio track not found at index ${args.track_index}");
|
|
65
|
+
if (typeof track.setLock === "function") { track.setLock(${args.locked}); } else { track.locked = ${args.locked}; }
|
|
66
|
+
return __result({ locked: ${args.locked}, trackIndex: ${args.track_index}, trackName: track.name });
|
|
67
|
+
`);
|
|
68
|
+
return sendCommand(script, bridgeOptions);
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
set_audio_track_target: {
|
|
72
|
+
description: "Set whether an audio track is targeted for recording or editing operations.",
|
|
73
|
+
parameters: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
77
|
+
targeted: { type: "boolean", description: "True to target this track, false to untarget" },
|
|
78
|
+
},
|
|
79
|
+
required: ["track_index", "targeted"],
|
|
80
|
+
},
|
|
81
|
+
handler: async (args) => {
|
|
82
|
+
const script = buildToolScript(`
|
|
83
|
+
var seq = app.project.activeSequence;
|
|
84
|
+
if (!seq) return __error("No active sequence");
|
|
85
|
+
var track = seq.audioTracks[${args.track_index}];
|
|
86
|
+
if (!track) return __error("Audio track not found at index ${args.track_index}");
|
|
87
|
+
if (typeof track.setTargeted === "function") { track.setTargeted(${args.targeted}, false); } else { track.targeted = ${args.targeted}; }
|
|
88
|
+
return __result({ targeted: ${args.targeted}, trackIndex: ${args.track_index}, trackName: track.name });
|
|
89
|
+
`);
|
|
90
|
+
return sendCommand(script, bridgeOptions);
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
rename_video_track: {
|
|
94
|
+
description: "Rename a video track in the active sequence.",
|
|
95
|
+
parameters: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
track_index: { type: "number", description: "Video track index (0-based)" },
|
|
99
|
+
name: { type: "string", description: "New name for the track" },
|
|
100
|
+
},
|
|
101
|
+
required: ["track_index", "name"],
|
|
102
|
+
},
|
|
103
|
+
handler: async (args) => {
|
|
104
|
+
const script = buildToolScript(`
|
|
105
|
+
var seq = app.project.activeSequence;
|
|
106
|
+
if (!seq) return __error("No active sequence");
|
|
107
|
+
var track = seq.videoTracks[${args.track_index}];
|
|
108
|
+
if (!track) return __error("Video track not found at index ${args.track_index}");
|
|
109
|
+
track.name = "${escapeForExtendScript(args.name)}";
|
|
110
|
+
return __result({ renamed: true, trackIndex: ${args.track_index}, newName: "${escapeForExtendScript(args.name)}" });
|
|
111
|
+
`);
|
|
112
|
+
return sendCommand(script, bridgeOptions);
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
mute_video_track: {
|
|
116
|
+
description: "Mute or unmute a video track (hide/show its output).",
|
|
117
|
+
parameters: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
track_index: { type: "number", description: "Video track index (0-based)" },
|
|
121
|
+
muted: { type: "boolean", description: "True to mute (hide), false to unmute (show)" },
|
|
122
|
+
},
|
|
123
|
+
required: ["track_index", "muted"],
|
|
124
|
+
},
|
|
125
|
+
handler: async (args) => {
|
|
126
|
+
const script = buildToolScript(`
|
|
127
|
+
var seq = app.project.activeSequence;
|
|
128
|
+
if (!seq) return __error("No active sequence");
|
|
129
|
+
var track = seq.videoTracks[${args.track_index}];
|
|
130
|
+
if (!track) return __error("Video track not found at index ${args.track_index}");
|
|
131
|
+
track.setMute(${args.muted ? 1 : 0});
|
|
132
|
+
return __result({ muted: ${args.muted}, trackIndex: ${args.track_index}, trackName: track.name });
|
|
133
|
+
`);
|
|
134
|
+
return sendCommand(script, bridgeOptions);
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
lock_video_track: {
|
|
138
|
+
description: "Lock or unlock a video track to prevent accidental edits.",
|
|
139
|
+
parameters: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
track_index: { type: "number", description: "Video track index (0-based)" },
|
|
143
|
+
locked: { type: "boolean", description: "True to lock, false to unlock" },
|
|
144
|
+
},
|
|
145
|
+
required: ["track_index", "locked"],
|
|
146
|
+
},
|
|
147
|
+
handler: async (args) => {
|
|
148
|
+
const script = buildToolScript(`
|
|
149
|
+
var seq = app.project.activeSequence;
|
|
150
|
+
if (!seq) return __error("No active sequence");
|
|
151
|
+
var track = seq.videoTracks[${args.track_index}];
|
|
152
|
+
if (!track) return __error("Video track not found at index ${args.track_index}");
|
|
153
|
+
if (typeof track.setLock === "function") { track.setLock(${args.locked}); } else { track.locked = ${args.locked}; }
|
|
154
|
+
return __result({ locked: ${args.locked}, trackIndex: ${args.track_index}, trackName: track.name });
|
|
155
|
+
`);
|
|
156
|
+
return sendCommand(script, bridgeOptions);
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
set_video_track_target: {
|
|
160
|
+
description: "Set whether a video track is targeted for editing operations.",
|
|
161
|
+
parameters: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
track_index: { type: "number", description: "Video track index (0-based)" },
|
|
165
|
+
targeted: { type: "boolean", description: "True to target this track, false to untarget" },
|
|
166
|
+
},
|
|
167
|
+
required: ["track_index", "targeted"],
|
|
168
|
+
},
|
|
169
|
+
handler: async (args) => {
|
|
170
|
+
const script = buildToolScript(`
|
|
171
|
+
var seq = app.project.activeSequence;
|
|
172
|
+
if (!seq) return __error("No active sequence");
|
|
173
|
+
var track = seq.videoTracks[${args.track_index}];
|
|
174
|
+
if (!track) return __error("Video track not found at index ${args.track_index}");
|
|
175
|
+
if (typeof track.setTargeted === "function") { track.setTargeted(${args.targeted}, false); } else { track.targeted = ${args.targeted}; }
|
|
176
|
+
return __result({ targeted: ${args.targeted}, trackIndex: ${args.track_index}, trackName: track.name });
|
|
177
|
+
`);
|
|
178
|
+
return sendCommand(script, bridgeOptions);
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
set_track_output: {
|
|
182
|
+
description: "Set the output channel assignment for an audio track.",
|
|
183
|
+
parameters: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
187
|
+
output_channels: { type: "number", description: "Output channel assignment index" },
|
|
188
|
+
},
|
|
189
|
+
required: ["track_index", "output_channels"],
|
|
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 track = seq.audioTracks[${args.track_index}];
|
|
196
|
+
if (!track) return __error("Audio track not found at index ${args.track_index}");
|
|
197
|
+
var outputSet = false;
|
|
198
|
+
if (track.components) {
|
|
199
|
+
for (var ci = 0; ci < track.components.numItems; ci++) {
|
|
200
|
+
var comp = track.components[ci];
|
|
201
|
+
if (comp.matchName === "audioOutputRouting" || comp.displayName === "Output Routing") {
|
|
202
|
+
for (var pi = 0; pi < comp.properties.numItems; pi++) {
|
|
203
|
+
try { comp.properties[pi].setValue(${args.output_channels}, true); outputSet = true; break; } catch(e) {}
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return __result({ trackIndex: ${args.track_index}, trackName: track.name, requestedOutput: ${args.output_channels},
|
|
210
|
+
outputSet: outputSet, note: outputSet ? "Output routing set." : "Output routing component not accessible via ExtendScript in this PP version. Use the Audio Track Mixer to set output routing manually." });
|
|
211
|
+
`);
|
|
212
|
+
return sendCommand(script, bridgeOptions);
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
get_track_output: {
|
|
216
|
+
description: "Get the output channel assignment for an audio track.",
|
|
217
|
+
parameters: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
track_index: { type: "number", description: "Audio track index (0-based)" },
|
|
221
|
+
},
|
|
222
|
+
required: ["track_index"],
|
|
223
|
+
},
|
|
224
|
+
handler: async (args) => {
|
|
225
|
+
const script = buildToolScript(`
|
|
226
|
+
var seq = app.project.activeSequence;
|
|
227
|
+
if (!seq) return __error("No active sequence");
|
|
228
|
+
var track = seq.audioTracks[${args.track_index}];
|
|
229
|
+
if (!track) return __error("Audio track not found at index ${args.track_index}");
|
|
230
|
+
var outputInfo = { trackIndex: ${args.track_index}, trackName: track.name, clipCount: track.clips.numItems };
|
|
231
|
+
if (track.components) {
|
|
232
|
+
for (var ci = 0; ci < track.components.numItems; ci++) {
|
|
233
|
+
var comp = track.components[ci];
|
|
234
|
+
if (comp.matchName === "audioOutputRouting" || comp.displayName === "Output Routing") {
|
|
235
|
+
outputInfo.outputRoutingComponent = comp.displayName;
|
|
236
|
+
var vals = [];
|
|
237
|
+
for (var pi = 0; pi < comp.properties.numItems; pi++) {
|
|
238
|
+
try { vals.push({ name: comp.properties[pi].displayName, value: comp.properties[pi].getValue() }); } catch(e) {}
|
|
239
|
+
}
|
|
240
|
+
outputInfo.properties = vals;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return __result(outputInfo);
|
|
246
|
+
`);
|
|
247
|
+
return sendCommand(script, bridgeOptions);
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
select_all_on_track: {
|
|
251
|
+
description: "Select all clips on a specific track.",
|
|
252
|
+
parameters: {
|
|
253
|
+
type: "object",
|
|
254
|
+
properties: {
|
|
255
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
256
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
257
|
+
},
|
|
258
|
+
required: ["track_type", "track_index"],
|
|
259
|
+
},
|
|
260
|
+
handler: async (args) => {
|
|
261
|
+
const script = buildToolScript(`
|
|
262
|
+
var seq = app.project.activeSequence;
|
|
263
|
+
if (!seq) return __error("No active sequence");
|
|
264
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
265
|
+
var track = trackType === "video" ? seq.videoTracks[${args.track_index}] : seq.audioTracks[${args.track_index}];
|
|
266
|
+
if (!track) return __error("Track not found at index ${args.track_index}");
|
|
267
|
+
var count = 0;
|
|
268
|
+
for (var i = 0; i < track.clips.numItems; i++) {
|
|
269
|
+
track.clips[i].setSelected(true, true);
|
|
270
|
+
count++;
|
|
271
|
+
}
|
|
272
|
+
return __result({ selected: count, trackIndex: ${args.track_index}, trackType: trackType, trackName: track.name });
|
|
273
|
+
`);
|
|
274
|
+
return sendCommand(script, bridgeOptions);
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
get_clips_on_track: {
|
|
278
|
+
description: "Get information about all clips on a specific track.",
|
|
279
|
+
parameters: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
283
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
284
|
+
},
|
|
285
|
+
required: ["track_type", "track_index"],
|
|
286
|
+
},
|
|
287
|
+
handler: async (args) => {
|
|
288
|
+
const script = buildToolScript(`
|
|
289
|
+
var seq = app.project.activeSequence;
|
|
290
|
+
if (!seq) return __error("No active sequence");
|
|
291
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
292
|
+
var track = trackType === "video" ? seq.videoTracks[${args.track_index}] : seq.audioTracks[${args.track_index}];
|
|
293
|
+
if (!track) return __error("Track not found at index ${args.track_index}");
|
|
294
|
+
var clips = [];
|
|
295
|
+
for (var i = 0; i < track.clips.numItems; i++) {
|
|
296
|
+
var c = track.clips[i];
|
|
297
|
+
clips.push({
|
|
298
|
+
nodeId: c.nodeId,
|
|
299
|
+
name: c.name,
|
|
300
|
+
clipIndex: i,
|
|
301
|
+
startSeconds: __ticksToSeconds(c.start.ticks),
|
|
302
|
+
endSeconds: __ticksToSeconds(c.end.ticks),
|
|
303
|
+
durationSeconds: __ticksToSeconds(c.duration.ticks),
|
|
304
|
+
inPointSeconds: __ticksToSeconds(c.inPoint.ticks),
|
|
305
|
+
outPointSeconds: __ticksToSeconds(c.outPoint.ticks),
|
|
306
|
+
mediaType: c.mediaType
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return __result({
|
|
310
|
+
trackType: trackType,
|
|
311
|
+
trackIndex: ${args.track_index},
|
|
312
|
+
trackName: track.name,
|
|
313
|
+
clipCount: clips.length,
|
|
314
|
+
clips: clips
|
|
315
|
+
});
|
|
316
|
+
`);
|
|
317
|
+
return sendCommand(script, bridgeOptions);
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
find_gaps: {
|
|
321
|
+
description: "Find gaps (empty spaces) on a track in the timeline.",
|
|
322
|
+
parameters: {
|
|
323
|
+
type: "object",
|
|
324
|
+
properties: {
|
|
325
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
326
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
327
|
+
},
|
|
328
|
+
required: ["track_type", "track_index"],
|
|
329
|
+
},
|
|
330
|
+
handler: async (args) => {
|
|
331
|
+
const script = buildToolScript(`
|
|
332
|
+
var seq = app.project.activeSequence;
|
|
333
|
+
if (!seq) return __error("No active sequence");
|
|
334
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
335
|
+
var track = trackType === "video" ? seq.videoTracks[${args.track_index}] : seq.audioTracks[${args.track_index}];
|
|
336
|
+
if (!track) return __error("Track not found at index ${args.track_index}");
|
|
337
|
+
var clipCount = track.clips.numItems;
|
|
338
|
+
var clipData = [];
|
|
339
|
+
for (var i = 0; i < clipCount; i++) {
|
|
340
|
+
var c = track.clips[i];
|
|
341
|
+
clipData.push({ startTicks: parseFloat(c.start.ticks), endTicks: parseFloat(c.end.ticks) });
|
|
342
|
+
}
|
|
343
|
+
clipData.sort(function(a, b) { return a.startTicks - b.startTicks; });
|
|
344
|
+
var gaps = [];
|
|
345
|
+
for (var i = 0; i < clipData.length - 1; i++) {
|
|
346
|
+
var gapStartTicks = clipData[i].endTicks;
|
|
347
|
+
var gapEndTicks = clipData[i + 1].startTicks;
|
|
348
|
+
if (gapEndTicks > gapStartTicks + 1000) {
|
|
349
|
+
gaps.push({
|
|
350
|
+
index: gaps.length,
|
|
351
|
+
startSeconds: __ticksToSeconds(gapStartTicks.toString()),
|
|
352
|
+
endSeconds: __ticksToSeconds(gapEndTicks.toString()),
|
|
353
|
+
durationSeconds: __ticksToSeconds((gapEndTicks - gapStartTicks).toString()),
|
|
354
|
+
afterClipIndex: i,
|
|
355
|
+
beforeClipIndex: i + 1
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return __result({
|
|
360
|
+
trackType: trackType,
|
|
361
|
+
trackIndex: ${args.track_index},
|
|
362
|
+
trackName: track.name,
|
|
363
|
+
clipCount: clipCount,
|
|
364
|
+
gapCount: gaps.length,
|
|
365
|
+
gaps: gaps
|
|
366
|
+
});
|
|
367
|
+
`);
|
|
368
|
+
return sendCommand(script, bridgeOptions);
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
close_gap: {
|
|
372
|
+
description: "Close a specific gap on a track. Uses QE DOM extract to ripple-delete the gap range.",
|
|
373
|
+
parameters: {
|
|
374
|
+
type: "object",
|
|
375
|
+
properties: {
|
|
376
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
377
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
378
|
+
gap_index: { type: "number", description: "Gap index as returned by find_gaps (0-based)" },
|
|
379
|
+
},
|
|
380
|
+
required: ["track_type", "track_index", "gap_index"],
|
|
381
|
+
},
|
|
382
|
+
handler: async (args) => {
|
|
383
|
+
const script = buildToolScript(`
|
|
384
|
+
var seq = app.project.activeSequence;
|
|
385
|
+
if (!seq) return __error("No active sequence");
|
|
386
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
387
|
+
var track = trackType === "video" ? seq.videoTracks[${args.track_index}] : seq.audioTracks[${args.track_index}];
|
|
388
|
+
if (!track) return __error("Track not found at index ${args.track_index}");
|
|
389
|
+
var clipData = [];
|
|
390
|
+
for (var i = 0; i < track.clips.numItems; i++) {
|
|
391
|
+
var c = track.clips[i];
|
|
392
|
+
clipData.push({ startTicks: parseFloat(c.start.ticks), endTicks: parseFloat(c.end.ticks) });
|
|
393
|
+
}
|
|
394
|
+
clipData.sort(function(a, b) { return a.startTicks - b.startTicks; });
|
|
395
|
+
var gaps = [];
|
|
396
|
+
for (var i = 0; i < clipData.length - 1; i++) {
|
|
397
|
+
var gapStartTicks = clipData[i].endTicks;
|
|
398
|
+
var gapEndTicks = clipData[i + 1].startTicks;
|
|
399
|
+
if (gapEndTicks > gapStartTicks + 1000) {
|
|
400
|
+
gaps.push({ startTicks: gapStartTicks, endTicks: gapEndTicks });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
var gapIdx = ${args.gap_index};
|
|
404
|
+
if (gapIdx >= gaps.length) return __error("Gap index " + gapIdx + " out of range (found " + gaps.length + " gaps)");
|
|
405
|
+
var gap = gaps[gapIdx];
|
|
406
|
+
var gapDuration = gap.endTicks - gap.startTicks;
|
|
407
|
+
// Use QE DOM to perform ripple delete (extract) on the gap range
|
|
408
|
+
app.enableQE();
|
|
409
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
410
|
+
if (!qeSeq) return __error("No active sequence (QE)");
|
|
411
|
+
seq.setInPoint(gap.startTicks.toString());
|
|
412
|
+
seq.setOutPoint(gap.endTicks.toString());
|
|
413
|
+
qeSeq.extract();
|
|
414
|
+
return __result({
|
|
415
|
+
closed: true,
|
|
416
|
+
gapIndex: gapIdx,
|
|
417
|
+
gapDurationSeconds: __ticksToSeconds(gapDuration.toString()),
|
|
418
|
+
startSeconds: __ticksToSeconds(gap.startTicks.toString()),
|
|
419
|
+
endSeconds: __ticksToSeconds(gap.endTicks.toString())
|
|
420
|
+
});
|
|
421
|
+
`);
|
|
422
|
+
return sendCommand(script, bridgeOptions);
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
close_all_gaps: {
|
|
426
|
+
description: "Close all gaps on a track.",
|
|
427
|
+
parameters: {
|
|
428
|
+
type: "object",
|
|
429
|
+
properties: {
|
|
430
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
431
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
432
|
+
},
|
|
433
|
+
required: ["track_type", "track_index"],
|
|
434
|
+
},
|
|
435
|
+
handler: async (args) => {
|
|
436
|
+
const script = buildToolScript(`
|
|
437
|
+
var seq = app.project.activeSequence;
|
|
438
|
+
if (!seq) return __error("No active sequence");
|
|
439
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
440
|
+
var track = trackType === "video" ? seq.videoTracks[${args.track_index}] : seq.audioTracks[${args.track_index}];
|
|
441
|
+
if (!track) return __error("Track not found at index ${args.track_index}");
|
|
442
|
+
app.enableQE();
|
|
443
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
444
|
+
if (!qeSeq) return __error("No active sequence (QE)");
|
|
445
|
+
var closedCount = 0;
|
|
446
|
+
// Find and close gaps from right to left to preserve positions
|
|
447
|
+
var found = true;
|
|
448
|
+
while (found) {
|
|
449
|
+
found = false;
|
|
450
|
+
var clipData = [];
|
|
451
|
+
for (var i = 0; i < track.clips.numItems; i++) {
|
|
452
|
+
var c = track.clips[i];
|
|
453
|
+
clipData.push({ startTicks: parseFloat(c.start.ticks), endTicks: parseFloat(c.end.ticks) });
|
|
454
|
+
}
|
|
455
|
+
clipData.sort(function(a, b) { return a.startTicks - b.startTicks; });
|
|
456
|
+
for (var i = clipData.length - 2; i >= 0; i--) {
|
|
457
|
+
var gapStartTicks = clipData[i].endTicks;
|
|
458
|
+
var gapEndTicks = clipData[i + 1].startTicks;
|
|
459
|
+
if (gapEndTicks > gapStartTicks + 1000) {
|
|
460
|
+
seq.setInPoint(gapStartTicks.toString());
|
|
461
|
+
seq.setOutPoint(gapEndTicks.toString());
|
|
462
|
+
qeSeq.extract();
|
|
463
|
+
closedCount++;
|
|
464
|
+
found = true;
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return __result({ closedGaps: closedCount, trackIndex: ${args.track_index}, trackType: trackType });
|
|
470
|
+
`);
|
|
471
|
+
return sendCommand(script, bridgeOptions);
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
ripple_delete_gap: {
|
|
475
|
+
description: "Ripple delete the gap between two time points on a track.",
|
|
476
|
+
parameters: {
|
|
477
|
+
type: "object",
|
|
478
|
+
properties: {
|
|
479
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
480
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
481
|
+
start_time: { type: "number", description: "Start of the gap in seconds" },
|
|
482
|
+
end_time: { type: "number", description: "End of the gap in seconds" },
|
|
483
|
+
},
|
|
484
|
+
required: ["track_type", "track_index", "start_time", "end_time"],
|
|
485
|
+
},
|
|
486
|
+
handler: async (args) => {
|
|
487
|
+
const script = buildToolScript(`
|
|
488
|
+
var seq = app.project.activeSequence;
|
|
489
|
+
if (!seq) return __error("No active sequence");
|
|
490
|
+
app.enableQE();
|
|
491
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
492
|
+
if (!qeSeq) return __error("No active sequence (QE)");
|
|
493
|
+
var startTicks = __secondsToTicks(${args.start_time}).toString();
|
|
494
|
+
var endTicks = __secondsToTicks(${args.end_time}).toString();
|
|
495
|
+
seq.setInPoint(startTicks);
|
|
496
|
+
seq.setOutPoint(endTicks);
|
|
497
|
+
qeSeq.extract();
|
|
498
|
+
return __result({
|
|
499
|
+
deleted: true,
|
|
500
|
+
startSeconds: ${args.start_time},
|
|
501
|
+
endSeconds: ${args.end_time},
|
|
502
|
+
durationSeconds: ${args.end_time - args.start_time}
|
|
503
|
+
});
|
|
504
|
+
`);
|
|
505
|
+
return sendCommand(script, bridgeOptions);
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
ripple_delete_all_gaps: {
|
|
509
|
+
description: "Ripple delete all gaps on all tracks in the active sequence.",
|
|
510
|
+
parameters: {
|
|
511
|
+
type: "object",
|
|
512
|
+
properties: {},
|
|
513
|
+
required: [],
|
|
514
|
+
},
|
|
515
|
+
handler: async (_args) => {
|
|
516
|
+
const script = buildToolScript(`
|
|
517
|
+
var seq = app.project.activeSequence;
|
|
518
|
+
if (!seq) return __error("No active sequence");
|
|
519
|
+
app.enableQE();
|
|
520
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
521
|
+
if (!qeSeq) return __error("No active sequence (QE)");
|
|
522
|
+
var totalClosed = 0;
|
|
523
|
+
var found = true;
|
|
524
|
+
while (found) {
|
|
525
|
+
found = false;
|
|
526
|
+
// Collect all clips from all video tracks
|
|
527
|
+
var allClipData = [];
|
|
528
|
+
for (var t = 0; t < seq.videoTracks.numTracks; t++) {
|
|
529
|
+
var track = seq.videoTracks[t];
|
|
530
|
+
for (var i = 0; i < track.clips.numItems; i++) {
|
|
531
|
+
var c = track.clips[i];
|
|
532
|
+
allClipData.push({ startTicks: parseFloat(c.start.ticks), endTicks: parseFloat(c.end.ticks) });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
allClipData.sort(function(a, b) { return a.startTicks - b.startTicks; });
|
|
536
|
+
for (var i = allClipData.length - 2; i >= 0; i--) {
|
|
537
|
+
var gapStart = allClipData[i].endTicks;
|
|
538
|
+
var gapEnd = allClipData[i + 1].startTicks;
|
|
539
|
+
if (gapEnd > gapStart + 1000) {
|
|
540
|
+
seq.setInPoint(gapStart.toString());
|
|
541
|
+
seq.setOutPoint(gapEnd.toString());
|
|
542
|
+
qeSeq.extract();
|
|
543
|
+
totalClosed++;
|
|
544
|
+
found = true;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return __result({ closedGaps: totalClosed });
|
|
550
|
+
`);
|
|
551
|
+
return sendCommand(script, bridgeOptions);
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
get_all_clips: {
|
|
555
|
+
description: "Get all clips from all tracks in the active sequence.",
|
|
556
|
+
parameters: {
|
|
557
|
+
type: "object",
|
|
558
|
+
properties: {},
|
|
559
|
+
required: [],
|
|
560
|
+
},
|
|
561
|
+
handler: async (_args) => {
|
|
562
|
+
const script = buildToolScript(`
|
|
563
|
+
var seq = app.project.activeSequence;
|
|
564
|
+
if (!seq) return __error("No active sequence");
|
|
565
|
+
var clips = __getAllClips(seq);
|
|
566
|
+
return __result({ clipCount: clips.length, clips: clips });
|
|
567
|
+
`);
|
|
568
|
+
return sendCommand(script, bridgeOptions);
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
apply_effect_to_multiple_clips: {
|
|
572
|
+
description: "Apply the same video effect to multiple clips on a track using QE DOM.",
|
|
573
|
+
parameters: {
|
|
574
|
+
type: "object",
|
|
575
|
+
properties: {
|
|
576
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
577
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
578
|
+
clip_indices: { type: "array", description: "Array of clip indices to apply the effect to" },
|
|
579
|
+
effect_name: { type: "string", description: "Name of the effect to apply (e.g. 'Gaussian Blur', 'Lumetri Color')" },
|
|
580
|
+
},
|
|
581
|
+
required: ["track_type", "track_index", "clip_indices", "effect_name"],
|
|
582
|
+
},
|
|
583
|
+
handler: async (args) => {
|
|
584
|
+
const clipIndicesStr = JSON.stringify(args.clip_indices);
|
|
585
|
+
const script = buildToolScript(`
|
|
586
|
+
var seq = app.project.activeSequence;
|
|
587
|
+
if (!seq) return __error("No active sequence");
|
|
588
|
+
app.enableQE();
|
|
589
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
590
|
+
if (!qeSeq) return __error("No active sequence (QE)");
|
|
591
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
592
|
+
var trackIndex = ${args.track_index};
|
|
593
|
+
var effectName = "${escapeForExtendScript(args.effect_name)}";
|
|
594
|
+
var clipIndices = ${clipIndicesStr};
|
|
595
|
+
|
|
596
|
+
// Find effect in list
|
|
597
|
+
var effectList = trackType === "video" ? qe.project.getVideoEffectList() : qe.project.getAudioEffectList();
|
|
598
|
+
var effectToApply = null;
|
|
599
|
+
for (var i = 0; i < effectList.numItems; i++) {
|
|
600
|
+
if (effectList[i].name === effectName) {
|
|
601
|
+
effectToApply = effectList[i];
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (!effectToApply) return __error("Effect not found: " + effectName);
|
|
606
|
+
|
|
607
|
+
var qeTrack = trackType === "video" ? qeSeq.getVideoTrackAt(trackIndex) : qeSeq.getAudioTrackAt(trackIndex);
|
|
608
|
+
if (!qeTrack) return __error("QE track not found");
|
|
609
|
+
|
|
610
|
+
var applied = 0;
|
|
611
|
+
for (var j = 0; j < clipIndices.length; j++) {
|
|
612
|
+
var qeClip = qeTrack.getItemAt(clipIndices[j]);
|
|
613
|
+
if (qeClip) {
|
|
614
|
+
if (trackType === "video") {
|
|
615
|
+
qeClip.addVideoEffect(effectToApply);
|
|
616
|
+
} else {
|
|
617
|
+
qeClip.addAudioEffect(effectToApply);
|
|
618
|
+
}
|
|
619
|
+
applied++;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return __result({ applied: applied, effect: effectName, trackType: trackType, trackIndex: trackIndex });
|
|
623
|
+
`);
|
|
624
|
+
return sendCommand(script, bridgeOptions);
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
remove_all_effects: {
|
|
628
|
+
description: "Remove all applied effects from a clip (keeps intrinsic Motion and Opacity).",
|
|
629
|
+
parameters: {
|
|
630
|
+
type: "object",
|
|
631
|
+
properties: {
|
|
632
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
633
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
634
|
+
clip_index: { type: "number", description: "Clip index on the track (0-based)" },
|
|
635
|
+
},
|
|
636
|
+
required: ["track_type", "track_index", "clip_index"],
|
|
637
|
+
},
|
|
638
|
+
handler: async (args) => {
|
|
639
|
+
const script = buildToolScript(`
|
|
640
|
+
var seq = app.project.activeSequence;
|
|
641
|
+
if (!seq) return __error("No active sequence");
|
|
642
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
643
|
+
var track = trackType === "video" ? seq.videoTracks[${args.track_index}] : seq.audioTracks[${args.track_index}];
|
|
644
|
+
if (!track) return __error("Track not found");
|
|
645
|
+
var clip = track.clips[${args.clip_index}];
|
|
646
|
+
if (!clip) return __error("Clip not found at index ${args.clip_index}");
|
|
647
|
+
// Intrinsic components to preserve. matchName is locale-INDEPENDENT, so build the
|
|
648
|
+
// preserve set from __INTRINSIC matchNames first; keep English displayNames only
|
|
649
|
+
// as a secondary fallback (otherwise non-English installs delete Motion/Opacity).
|
|
650
|
+
var intrinsicMatch = [];
|
|
651
|
+
for (var key in __INTRINSIC) {
|
|
652
|
+
if (__INTRINSIC.hasOwnProperty(key)) {
|
|
653
|
+
var m = __INTRINSIC[key].match;
|
|
654
|
+
for (var mi = 0; mi < m.length; mi++) { intrinsicMatch.push(m[mi]); }
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
var preservedNames = ["Motion", "Opacity", "Volume", "Channel Volume", "Panner", "Time Remapping"];
|
|
658
|
+
var removed = [];
|
|
659
|
+
// Remove from end to start to avoid index shifting
|
|
660
|
+
for (var i = clip.components.numItems - 1; i >= 0; i--) {
|
|
661
|
+
var comp = clip.components[i];
|
|
662
|
+
var isIntrinsic = false;
|
|
663
|
+
for (var j = 0; j < intrinsicMatch.length; j++) {
|
|
664
|
+
if (comp.matchName === intrinsicMatch[j]) { isIntrinsic = true; break; }
|
|
665
|
+
}
|
|
666
|
+
if (!isIntrinsic) {
|
|
667
|
+
for (var k = 0; k < preservedNames.length; k++) {
|
|
668
|
+
if (comp.displayName === preservedNames[k]) { isIntrinsic = true; break; }
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (!isIntrinsic) {
|
|
672
|
+
removed.push(comp.displayName);
|
|
673
|
+
comp.remove();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return __result({ removed: removed, removedCount: removed.length, clipName: clip.name });
|
|
677
|
+
`);
|
|
678
|
+
return sendCommand(script, bridgeOptions);
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
apply_transition_to_all_cuts: {
|
|
682
|
+
description: "Apply a video transition to all cut points on a video track.",
|
|
683
|
+
parameters: {
|
|
684
|
+
type: "object",
|
|
685
|
+
properties: {
|
|
686
|
+
track_index: { type: "number", description: "Video track index (0-based)" },
|
|
687
|
+
transition_name: { type: "string", description: "Name of transition to apply (e.g. 'Cross Dissolve')" },
|
|
688
|
+
duration: { type: "number", description: "Transition duration in seconds. Default: 1.0." },
|
|
689
|
+
},
|
|
690
|
+
required: ["track_index", "transition_name"],
|
|
691
|
+
},
|
|
692
|
+
handler: async (args) => {
|
|
693
|
+
const duration = args.duration ?? 1.0;
|
|
694
|
+
const script = buildToolScript(`
|
|
695
|
+
app.enableQE();
|
|
696
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
697
|
+
if (!qeSeq) return __error("No active sequence (QE)");
|
|
698
|
+
var seq = app.project.activeSequence;
|
|
699
|
+
if (!seq) return __error("No active sequence");
|
|
700
|
+
var trackIdx = ${args.track_index};
|
|
701
|
+
var transitionName = "${escapeForExtendScript(args.transition_name)}";
|
|
702
|
+
var durationSecs = ${duration};
|
|
703
|
+
var durationTicks = __secondsToTicks(durationSecs).toString();
|
|
704
|
+
|
|
705
|
+
var transitions = qe.project.getVideoTransitionList();
|
|
706
|
+
var transitionQE = null;
|
|
707
|
+
for (var i = 0; i < transitions.numItems; i++) {
|
|
708
|
+
if (transitions[i].name === transitionName) {
|
|
709
|
+
transitionQE = transitions[i];
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (!transitionQE) return __error("Transition not found: " + transitionName);
|
|
714
|
+
|
|
715
|
+
var track = seq.videoTracks[trackIdx];
|
|
716
|
+
if (!track) return __error("Video track not found at index " + trackIdx);
|
|
717
|
+
var qeTrack = qeSeq.getVideoTrackAt(trackIdx);
|
|
718
|
+
if (!qeTrack) return __error("QE track not found");
|
|
719
|
+
|
|
720
|
+
var count = 0;
|
|
721
|
+
for (var c = 0; c < track.clips.numItems - 1; c++) {
|
|
722
|
+
var cutTicks = track.clips[c].end.ticks;
|
|
723
|
+
try {
|
|
724
|
+
qeTrack.addTransition(transitionQE, true, cutTicks, durationTicks, "0", false);
|
|
725
|
+
count++;
|
|
726
|
+
} catch(e) {}
|
|
727
|
+
}
|
|
728
|
+
return __result({
|
|
729
|
+
added: count,
|
|
730
|
+
transition: transitionName,
|
|
731
|
+
trackIndex: trackIdx,
|
|
732
|
+
durationSeconds: durationSecs
|
|
733
|
+
});
|
|
734
|
+
`);
|
|
735
|
+
return sendCommand(script, bridgeOptions);
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
delete_all_clips_between: {
|
|
739
|
+
description: "Delete all clips on a track between two time points.",
|
|
740
|
+
parameters: {
|
|
741
|
+
type: "object",
|
|
742
|
+
properties: {
|
|
743
|
+
track_type: { type: "string", description: "Track type: 'video' or 'audio'" },
|
|
744
|
+
track_index: { type: "number", description: "Track index (0-based)" },
|
|
745
|
+
start_seconds: { type: "number", description: "Start time in seconds" },
|
|
746
|
+
end_seconds: { type: "number", description: "End time in seconds" },
|
|
747
|
+
},
|
|
748
|
+
required: ["track_type", "track_index", "start_seconds", "end_seconds"],
|
|
749
|
+
},
|
|
750
|
+
handler: async (args) => {
|
|
751
|
+
const script = buildToolScript(`
|
|
752
|
+
var seq = app.project.activeSequence;
|
|
753
|
+
if (!seq) return __error("No active sequence");
|
|
754
|
+
var trackType = "${escapeForExtendScript(args.track_type)}";
|
|
755
|
+
var track = trackType === "video" ? seq.videoTracks[${args.track_index}] : seq.audioTracks[${args.track_index}];
|
|
756
|
+
if (!track) return __error("Track not found at index ${args.track_index}");
|
|
757
|
+
var startTicks = __secondsToTicks(${args.start_seconds});
|
|
758
|
+
var endTicks = __secondsToTicks(${args.end_seconds});
|
|
759
|
+
var toRemove = [];
|
|
760
|
+
for (var i = 0; i < track.clips.numItems; i++) {
|
|
761
|
+
var c = track.clips[i];
|
|
762
|
+
var clipStart = parseFloat(c.start.ticks);
|
|
763
|
+
var clipEnd = parseFloat(c.end.ticks);
|
|
764
|
+
if (clipStart >= startTicks && clipEnd <= endTicks) {
|
|
765
|
+
toRemove.push(i);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
var removed = [];
|
|
769
|
+
// Remove from end to preserve indices
|
|
770
|
+
for (var j = toRemove.length - 1; j >= 0; j--) {
|
|
771
|
+
var clip = track.clips[toRemove[j]];
|
|
772
|
+
removed.push(clip.name);
|
|
773
|
+
clip.remove(false, false);
|
|
774
|
+
}
|
|
775
|
+
return __result({
|
|
776
|
+
removed: removed,
|
|
777
|
+
removedCount: removed.length,
|
|
778
|
+
startSeconds: ${args.start_seconds},
|
|
779
|
+
endSeconds: ${args.end_seconds}
|
|
780
|
+
});
|
|
781
|
+
`);
|
|
782
|
+
return sendCommand(script, bridgeOptions);
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
//# sourceMappingURL=track-management.js.map
|