@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,1089 @@
|
|
|
1
|
+
import { buildToolScript, escapeForExtendScript } from "../bridge/script-builder.js";
|
|
2
|
+
import { sendCommand } from "../bridge/file-bridge.js";
|
|
3
|
+
export function getInspectionTools(bridgeOptions) {
|
|
4
|
+
return {
|
|
5
|
+
get_full_project_overview: {
|
|
6
|
+
description: "Get a comprehensive overview of the entire project: all bins (recursive tree), all sequences, media stats, offline items, and project settings. This is the best first call to fully understand a project.",
|
|
7
|
+
parameters: {},
|
|
8
|
+
handler: async () => {
|
|
9
|
+
const script = buildToolScript(`
|
|
10
|
+
var project = app.project;
|
|
11
|
+
if (!project) return __error("No project is open");
|
|
12
|
+
|
|
13
|
+
function walkBin(bin, depth) {
|
|
14
|
+
var items = [];
|
|
15
|
+
for (var i = 0; i < bin.children.numItems; i++) {
|
|
16
|
+
var item = bin.children[i];
|
|
17
|
+
var entry = {
|
|
18
|
+
nodeId: item.nodeId,
|
|
19
|
+
name: item.name,
|
|
20
|
+
treePath: item.treePath,
|
|
21
|
+
type: item.type === 1 ? "clip" : item.type === 2 ? "bin" : item.type === 3 ? "root" : item.type === 4 ? "file" : "unknown"
|
|
22
|
+
};
|
|
23
|
+
try { entry.mediaPath = item.getMediaPath(); } catch(e) {}
|
|
24
|
+
try { entry.offline = item.isOffline(); } catch(e) {}
|
|
25
|
+
try { entry.colorLabel = item.getColorLabel(); } catch(e) {}
|
|
26
|
+
if (item.type === 2 && depth < 10) {
|
|
27
|
+
entry.children = walkBin(item, depth + 1);
|
|
28
|
+
entry.childCount = entry.children.length;
|
|
29
|
+
}
|
|
30
|
+
items.push(entry);
|
|
31
|
+
}
|
|
32
|
+
return items;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var binTree = walkBin(project.rootItem, 0);
|
|
36
|
+
|
|
37
|
+
var sequences = [];
|
|
38
|
+
for (var i = 0; i < project.sequences.numSequences; i++) {
|
|
39
|
+
var seq = project.sequences[i];
|
|
40
|
+
var clipCount = 0;
|
|
41
|
+
for (var t = 0; t < seq.videoTracks.numTracks; t++) clipCount += seq.videoTracks[t].clips.numItems;
|
|
42
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) clipCount += seq.audioTracks[t].clips.numItems;
|
|
43
|
+
sequences.push({
|
|
44
|
+
name: seq.name,
|
|
45
|
+
id: seq.sequenceID,
|
|
46
|
+
width: seq.frameSizeHorizontal,
|
|
47
|
+
height: seq.frameSizeVertical,
|
|
48
|
+
durationSeconds: __ticksToSeconds(seq.end),
|
|
49
|
+
videoTracks: seq.videoTracks.numTracks,
|
|
50
|
+
audioTracks: seq.audioTracks.numTracks,
|
|
51
|
+
totalClips: clipCount
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var totalItems = 0;
|
|
56
|
+
var offlineCount = 0;
|
|
57
|
+
var mediaTypes = {};
|
|
58
|
+
function countItems(bin) {
|
|
59
|
+
for (var i = 0; i < bin.children.numItems; i++) {
|
|
60
|
+
var item = bin.children[i];
|
|
61
|
+
totalItems++;
|
|
62
|
+
try { if (item.isOffline()) offlineCount++; } catch(e) {}
|
|
63
|
+
try {
|
|
64
|
+
var mp = item.getMediaPath();
|
|
65
|
+
if (mp) {
|
|
66
|
+
var ext = mp.split(".").pop().toLowerCase();
|
|
67
|
+
if (!mediaTypes[ext]) mediaTypes[ext] = 0;
|
|
68
|
+
mediaTypes[ext]++;
|
|
69
|
+
}
|
|
70
|
+
} catch(e) {}
|
|
71
|
+
if (item.type === 2) countItems(item);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
countItems(project.rootItem);
|
|
75
|
+
|
|
76
|
+
var activeSeqInfo = null;
|
|
77
|
+
var active = project.activeSequence;
|
|
78
|
+
if (active) {
|
|
79
|
+
activeSeqInfo = { name: active.name, id: active.sequenceID };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return __result({
|
|
83
|
+
projectName: project.name,
|
|
84
|
+
projectPath: project.path,
|
|
85
|
+
totalItems: totalItems,
|
|
86
|
+
offlineItems: offlineCount,
|
|
87
|
+
sequenceCount: sequences.length,
|
|
88
|
+
mediaFileTypes: mediaTypes,
|
|
89
|
+
activeSequence: activeSeqInfo,
|
|
90
|
+
sequences: sequences,
|
|
91
|
+
binTree: binTree
|
|
92
|
+
});
|
|
93
|
+
`);
|
|
94
|
+
return sendCommand(script, bridgeOptions);
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
get_bin_contents: {
|
|
98
|
+
description: "Get detailed contents of a specific bin (folder) including all nested items, media paths, offline status, color labels, and metadata. Searches by bin name or node ID.",
|
|
99
|
+
parameters: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
bin_id: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Bin name, node ID, or path (e.g., 'Footage', 'Footage/Raw')",
|
|
105
|
+
},
|
|
106
|
+
recursive: {
|
|
107
|
+
type: "boolean",
|
|
108
|
+
description: "Include items from sub-bins recursively (default: true)",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ["bin_id"],
|
|
112
|
+
},
|
|
113
|
+
handler: async (args) => {
|
|
114
|
+
const recursive = args.recursive !== false;
|
|
115
|
+
const script = buildToolScript(`
|
|
116
|
+
var root = app.project.rootItem;
|
|
117
|
+
var target = null;
|
|
118
|
+
|
|
119
|
+
// Try by node ID first
|
|
120
|
+
function findById(parent, id) {
|
|
121
|
+
for (var i = 0; i < parent.children.numItems; i++) {
|
|
122
|
+
var item = parent.children[i];
|
|
123
|
+
if (item.nodeId === id) return item;
|
|
124
|
+
if (item.type === 2) {
|
|
125
|
+
var found = findById(item, id);
|
|
126
|
+
if (found) return found;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
target = findById(root, "${escapeForExtendScript(args.bin_id)}");
|
|
133
|
+
|
|
134
|
+
// Try by path
|
|
135
|
+
if (!target) {
|
|
136
|
+
var parts = "${escapeForExtendScript(args.bin_id)}".split("/");
|
|
137
|
+
var current = root;
|
|
138
|
+
for (var p = 0; p < parts.length; p++) {
|
|
139
|
+
var found = false;
|
|
140
|
+
for (var i = 0; i < current.children.numItems; i++) {
|
|
141
|
+
if (current.children[i].name === parts[p] && current.children[i].type === 2) {
|
|
142
|
+
current = current.children[i];
|
|
143
|
+
found = true;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!found) { current = null; break; }
|
|
148
|
+
}
|
|
149
|
+
if (current && current !== root) target = current;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Try by name
|
|
153
|
+
if (!target) {
|
|
154
|
+
function findByName(parent, name) {
|
|
155
|
+
for (var i = 0; i < parent.children.numItems; i++) {
|
|
156
|
+
var item = parent.children[i];
|
|
157
|
+
if (item.name === name && item.type === 2) return item;
|
|
158
|
+
if (item.type === 2) {
|
|
159
|
+
var found = findByName(item, name);
|
|
160
|
+
if (found) return found;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
target = findByName(root, "${escapeForExtendScript(args.bin_id)}");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!target) return __error("Bin not found: ${escapeForExtendScript(args.bin_id)}");
|
|
169
|
+
|
|
170
|
+
function getItemDetails(item) {
|
|
171
|
+
var info = {
|
|
172
|
+
nodeId: item.nodeId,
|
|
173
|
+
name: item.name,
|
|
174
|
+
type: item.type === 1 ? "clip" : item.type === 2 ? "bin" : item.type === 4 ? "file" : "unknown",
|
|
175
|
+
treePath: item.treePath
|
|
176
|
+
};
|
|
177
|
+
try { info.mediaPath = item.getMediaPath(); } catch(e) {}
|
|
178
|
+
try { info.offline = item.isOffline(); } catch(e) {}
|
|
179
|
+
try { info.colorLabel = item.getColorLabel(); } catch(e) {}
|
|
180
|
+
try {
|
|
181
|
+
var interp = item.getFootageInterpretation();
|
|
182
|
+
if (interp) {
|
|
183
|
+
info.frameRate = interp.frameRate;
|
|
184
|
+
info.pixelAspectRatio = interp.pixelAspectRatio;
|
|
185
|
+
}
|
|
186
|
+
} catch(e) {}
|
|
187
|
+
try {
|
|
188
|
+
info.inPoint = __ticksToSeconds(item.getInPoint().ticks);
|
|
189
|
+
info.outPoint = __ticksToSeconds(item.getOutPoint().ticks);
|
|
190
|
+
} catch(e) {}
|
|
191
|
+
return info;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function walkItems(bin, recurse) {
|
|
195
|
+
var items = [];
|
|
196
|
+
for (var i = 0; i < bin.children.numItems; i++) {
|
|
197
|
+
var item = bin.children[i];
|
|
198
|
+
var info = getItemDetails(item);
|
|
199
|
+
if (item.type === 2 && recurse) {
|
|
200
|
+
info.children = walkItems(item, true);
|
|
201
|
+
info.childCount = info.children.length;
|
|
202
|
+
}
|
|
203
|
+
items.push(info);
|
|
204
|
+
}
|
|
205
|
+
return items;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
var contents = walkItems(target, ${recursive});
|
|
209
|
+
return __result({
|
|
210
|
+
binName: target.name,
|
|
211
|
+
binNodeId: target.nodeId,
|
|
212
|
+
binPath: target.treePath,
|
|
213
|
+
itemCount: contents.length,
|
|
214
|
+
items: contents
|
|
215
|
+
});
|
|
216
|
+
`);
|
|
217
|
+
return sendCommand(script, bridgeOptions);
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
get_full_sequence_info: {
|
|
221
|
+
description: "Get exhaustive information about a sequence: settings, all tracks with lock/mute/target state, all clips with positions/effects/speed/enabled state, all markers, transitions, in/out points, and work area.",
|
|
222
|
+
parameters: {
|
|
223
|
+
type: "object",
|
|
224
|
+
properties: {
|
|
225
|
+
sequence_id: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: "Sequence name or ID. Uses active sequence if omitted.",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
handler: async (args) => {
|
|
232
|
+
const seqLookup = args.sequence_id
|
|
233
|
+
? `var seq = __findSequence("${escapeForExtendScript(args.sequence_id)}"); if (!seq) return __error("Sequence not found");`
|
|
234
|
+
: `var seq = app.project.activeSequence; if (!seq) return __error("No active sequence");`;
|
|
235
|
+
const script = buildToolScript(`
|
|
236
|
+
${seqLookup}
|
|
237
|
+
|
|
238
|
+
// Settings
|
|
239
|
+
var info = {
|
|
240
|
+
name: seq.name,
|
|
241
|
+
id: seq.sequenceID,
|
|
242
|
+
width: seq.frameSizeHorizontal,
|
|
243
|
+
height: seq.frameSizeVertical,
|
|
244
|
+
durationSeconds: __ticksToSeconds(seq.end)
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
var s = seq.getSettings();
|
|
249
|
+
if (s) {
|
|
250
|
+
info.frameRate = s.videoFrameRate;
|
|
251
|
+
info.audioSampleRate = s.audioSampleRate;
|
|
252
|
+
info.audioChannelType = s.audioChannelType;
|
|
253
|
+
info.audioChannelCount = s.audioChannelCount;
|
|
254
|
+
info.videoFieldType = s.videoFieldType;
|
|
255
|
+
info.videoDisplayFormat = s.videoDisplayFormat;
|
|
256
|
+
info.audioDisplayFormat = s.audioDisplayFormat;
|
|
257
|
+
}
|
|
258
|
+
} catch(e) {}
|
|
259
|
+
|
|
260
|
+
// Playhead
|
|
261
|
+
try { info.playheadSeconds = __ticksToSeconds(seq.getPlayerPosition().ticks); } catch(e) {}
|
|
262
|
+
|
|
263
|
+
// In/Out points
|
|
264
|
+
try { info.inPointSeconds = __ticksToSeconds(seq.getInPoint()); } catch(e) {}
|
|
265
|
+
try { info.outPointSeconds = __ticksToSeconds(seq.getOutPoint()); } catch(e) {}
|
|
266
|
+
|
|
267
|
+
// Work area
|
|
268
|
+
try { info.workAreaIn = __ticksToSeconds(seq.getWorkAreaInPoint()); } catch(e) {}
|
|
269
|
+
try { info.workAreaOut = __ticksToSeconds(seq.getWorkAreaOutPoint()); } catch(e) {}
|
|
270
|
+
|
|
271
|
+
// Zero point
|
|
272
|
+
try { info.zeroPoint = __ticksToSeconds(seq.zeroPoint); } catch(e) {}
|
|
273
|
+
|
|
274
|
+
// Markers
|
|
275
|
+
var markers = [];
|
|
276
|
+
try {
|
|
277
|
+
var m = seq.markers.getFirstMarker();
|
|
278
|
+
while (m) {
|
|
279
|
+
var mInfo = {
|
|
280
|
+
name: m.name,
|
|
281
|
+
comments: m.comments,
|
|
282
|
+
startSeconds: __ticksToSeconds(m.start.ticks),
|
|
283
|
+
endSeconds: __ticksToSeconds(m.end.ticks),
|
|
284
|
+
type: m.type
|
|
285
|
+
};
|
|
286
|
+
try { mInfo.colorIndex = m.getColorByIndex(); } catch(e) {}
|
|
287
|
+
markers.push(mInfo);
|
|
288
|
+
m = seq.markers.getNextMarker(m);
|
|
289
|
+
}
|
|
290
|
+
} catch(e) {}
|
|
291
|
+
info.markers = markers;
|
|
292
|
+
info.markerCount = markers.length;
|
|
293
|
+
|
|
294
|
+
// Video tracks
|
|
295
|
+
info.videoTracks = [];
|
|
296
|
+
for (var t = 0; t < seq.videoTracks.numTracks; t++) {
|
|
297
|
+
var track = seq.videoTracks[t];
|
|
298
|
+
var trackInfo = {
|
|
299
|
+
index: t,
|
|
300
|
+
name: track.name,
|
|
301
|
+
isMuted: track.isMuted(),
|
|
302
|
+
isLocked: track.isLocked()
|
|
303
|
+
};
|
|
304
|
+
try { trackInfo.isTargeted = track.isTargeted(); } catch(e) {}
|
|
305
|
+
|
|
306
|
+
// Clips
|
|
307
|
+
trackInfo.clips = [];
|
|
308
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
309
|
+
var clip = track.clips[c];
|
|
310
|
+
var ci = {
|
|
311
|
+
index: c,
|
|
312
|
+
nodeId: clip.nodeId,
|
|
313
|
+
name: clip.name,
|
|
314
|
+
startSeconds: __ticksToSeconds(clip.start.ticks),
|
|
315
|
+
endSeconds: __ticksToSeconds(clip.end.ticks),
|
|
316
|
+
durationSeconds: __ticksToSeconds(clip.duration.ticks),
|
|
317
|
+
inPointSeconds: __ticksToSeconds(clip.inPoint.ticks),
|
|
318
|
+
outPointSeconds: __ticksToSeconds(clip.outPoint.ticks),
|
|
319
|
+
mediaType: clip.mediaType
|
|
320
|
+
};
|
|
321
|
+
try { ci.enabled = !clip.isDisabled(); } catch(e) { ci.enabled = true; }
|
|
322
|
+
try { ci.speed = clip.getSpeed(); } catch(e) {}
|
|
323
|
+
try { ci.reversed = clip.isSpeedReversed(); } catch(e) {}
|
|
324
|
+
try { ci.isAdjustmentLayer = clip.isAdjustmentLayer(); } catch(e) {}
|
|
325
|
+
try { ci.isSelected = clip.isSelected(); } catch(e) {}
|
|
326
|
+
|
|
327
|
+
// Source media info
|
|
328
|
+
try {
|
|
329
|
+
if (clip.projectItem) {
|
|
330
|
+
ci.sourceNodeId = clip.projectItem.nodeId;
|
|
331
|
+
ci.sourceName = clip.projectItem.name;
|
|
332
|
+
try { ci.sourceMediaPath = clip.projectItem.getMediaPath(); } catch(e) {}
|
|
333
|
+
try { ci.sourceOffline = clip.projectItem.isOffline(); } catch(e) {}
|
|
334
|
+
}
|
|
335
|
+
} catch(e) {}
|
|
336
|
+
|
|
337
|
+
// Effect count
|
|
338
|
+
try {
|
|
339
|
+
ci.effectCount = clip.components.numItems;
|
|
340
|
+
ci.effects = [];
|
|
341
|
+
for (var e = 0; e < clip.components.numItems; e++) {
|
|
342
|
+
ci.effects.push(clip.components[e].displayName);
|
|
343
|
+
}
|
|
344
|
+
} catch(e) {}
|
|
345
|
+
|
|
346
|
+
trackInfo.clips.push(ci);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Transitions
|
|
350
|
+
trackInfo.transitions = [];
|
|
351
|
+
try {
|
|
352
|
+
for (var tr = 0; tr < track.transitions.numItems; tr++) {
|
|
353
|
+
var trans = track.transitions[tr];
|
|
354
|
+
trackInfo.transitions.push({
|
|
355
|
+
index: tr,
|
|
356
|
+
startSeconds: __ticksToSeconds(trans.start.ticks),
|
|
357
|
+
endSeconds: __ticksToSeconds(trans.end.ticks),
|
|
358
|
+
type: trans.type
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
} catch(e) {}
|
|
362
|
+
|
|
363
|
+
trackInfo.clipCount = trackInfo.clips.length;
|
|
364
|
+
trackInfo.transitionCount = trackInfo.transitions.length;
|
|
365
|
+
info.videoTracks.push(trackInfo);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Audio tracks
|
|
369
|
+
info.audioTracks = [];
|
|
370
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
371
|
+
var track = seq.audioTracks[t];
|
|
372
|
+
var trackInfo = {
|
|
373
|
+
index: t,
|
|
374
|
+
name: track.name,
|
|
375
|
+
isMuted: track.isMuted(),
|
|
376
|
+
isLocked: track.isLocked()
|
|
377
|
+
};
|
|
378
|
+
try { trackInfo.isTargeted = track.isTargeted(); } catch(e) {}
|
|
379
|
+
|
|
380
|
+
trackInfo.clips = [];
|
|
381
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
382
|
+
var clip = track.clips[c];
|
|
383
|
+
var ci = {
|
|
384
|
+
index: c,
|
|
385
|
+
nodeId: clip.nodeId,
|
|
386
|
+
name: clip.name,
|
|
387
|
+
startSeconds: __ticksToSeconds(clip.start.ticks),
|
|
388
|
+
endSeconds: __ticksToSeconds(clip.end.ticks),
|
|
389
|
+
durationSeconds: __ticksToSeconds(clip.duration.ticks),
|
|
390
|
+
inPointSeconds: __ticksToSeconds(clip.inPoint.ticks),
|
|
391
|
+
outPointSeconds: __ticksToSeconds(clip.outPoint.ticks),
|
|
392
|
+
mediaType: clip.mediaType
|
|
393
|
+
};
|
|
394
|
+
try { ci.enabled = !clip.isDisabled(); } catch(e) { ci.enabled = true; }
|
|
395
|
+
try { ci.speed = clip.getSpeed(); } catch(e) {}
|
|
396
|
+
try {
|
|
397
|
+
if (clip.projectItem) {
|
|
398
|
+
ci.sourceNodeId = clip.projectItem.nodeId;
|
|
399
|
+
ci.sourceName = clip.projectItem.name;
|
|
400
|
+
try { ci.sourceMediaPath = clip.projectItem.getMediaPath(); } catch(e) {}
|
|
401
|
+
}
|
|
402
|
+
} catch(e) {}
|
|
403
|
+
try {
|
|
404
|
+
ci.effectCount = clip.components.numItems;
|
|
405
|
+
ci.effects = [];
|
|
406
|
+
for (var e = 0; e < clip.components.numItems; e++) {
|
|
407
|
+
ci.effects.push(clip.components[e].displayName);
|
|
408
|
+
}
|
|
409
|
+
} catch(e) {}
|
|
410
|
+
trackInfo.clips.push(ci);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
trackInfo.transitions = [];
|
|
414
|
+
try {
|
|
415
|
+
for (var tr = 0; tr < track.transitions.numItems; tr++) {
|
|
416
|
+
var trans = track.transitions[tr];
|
|
417
|
+
trackInfo.transitions.push({
|
|
418
|
+
index: tr,
|
|
419
|
+
startSeconds: __ticksToSeconds(trans.start.ticks),
|
|
420
|
+
endSeconds: __ticksToSeconds(trans.end.ticks)
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
} catch(e) {}
|
|
424
|
+
|
|
425
|
+
trackInfo.clipCount = trackInfo.clips.length;
|
|
426
|
+
trackInfo.transitionCount = trackInfo.transitions.length;
|
|
427
|
+
info.audioTracks.push(trackInfo);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
info.videoTrackCount = info.videoTracks.length;
|
|
431
|
+
info.audioTrackCount = info.audioTracks.length;
|
|
432
|
+
|
|
433
|
+
return __result(info);
|
|
434
|
+
`);
|
|
435
|
+
return sendCommand(script, bridgeOptions);
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
get_full_clip_info: {
|
|
439
|
+
description: "Get exhaustive information about a specific clip: all effects with every property value, source media details, footage interpretation, metadata, markers, speed, enabled state, color label, linked clips, and proxy status.",
|
|
440
|
+
parameters: {
|
|
441
|
+
type: "object",
|
|
442
|
+
properties: {
|
|
443
|
+
node_id: {
|
|
444
|
+
type: "string",
|
|
445
|
+
description: "Node ID of the clip on the timeline",
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
required: ["node_id"],
|
|
449
|
+
},
|
|
450
|
+
handler: async (args) => {
|
|
451
|
+
const script = buildToolScript(`
|
|
452
|
+
var found = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
453
|
+
if (!found) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
454
|
+
|
|
455
|
+
var clip = found.clip;
|
|
456
|
+
var info = {
|
|
457
|
+
nodeId: clip.nodeId,
|
|
458
|
+
name: clip.name,
|
|
459
|
+
trackType: found.trackType,
|
|
460
|
+
trackIndex: found.trackIndex,
|
|
461
|
+
clipIndex: found.clipIndex,
|
|
462
|
+
startSeconds: __ticksToSeconds(clip.start.ticks),
|
|
463
|
+
endSeconds: __ticksToSeconds(clip.end.ticks),
|
|
464
|
+
durationSeconds: __ticksToSeconds(clip.duration.ticks),
|
|
465
|
+
inPointSeconds: __ticksToSeconds(clip.inPoint.ticks),
|
|
466
|
+
outPointSeconds: __ticksToSeconds(clip.outPoint.ticks),
|
|
467
|
+
mediaType: clip.mediaType
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
try { info.enabled = !clip.isDisabled(); } catch(e) { info.enabled = true; }
|
|
471
|
+
try { info.speed = clip.getSpeed(); } catch(e) {}
|
|
472
|
+
try { info.reversed = clip.isSpeedReversed(); } catch(e) {}
|
|
473
|
+
try { info.isSelected = clip.isSelected(); } catch(e) {}
|
|
474
|
+
try { info.isAdjustmentLayer = clip.isAdjustmentLayer(); } catch(e) {}
|
|
475
|
+
|
|
476
|
+
// Source project item details
|
|
477
|
+
try {
|
|
478
|
+
var src = clip.projectItem;
|
|
479
|
+
if (src) {
|
|
480
|
+
info.source = {
|
|
481
|
+
nodeId: src.nodeId,
|
|
482
|
+
name: src.name,
|
|
483
|
+
treePath: src.treePath
|
|
484
|
+
};
|
|
485
|
+
try { info.source.mediaPath = src.getMediaPath(); } catch(e) {}
|
|
486
|
+
try { info.source.offline = src.isOffline(); } catch(e) {}
|
|
487
|
+
try { info.source.colorLabel = src.getColorLabel(); } catch(e) {}
|
|
488
|
+
try {
|
|
489
|
+
var interp = src.getFootageInterpretation();
|
|
490
|
+
if (interp) {
|
|
491
|
+
info.source.frameRate = interp.frameRate;
|
|
492
|
+
info.source.pixelAspectRatio = interp.pixelAspectRatio;
|
|
493
|
+
info.source.fieldType = interp.fieldType;
|
|
494
|
+
info.source.alphaUsage = interp.alphaUsage;
|
|
495
|
+
}
|
|
496
|
+
} catch(e) {}
|
|
497
|
+
// XMP metadata
|
|
498
|
+
try {
|
|
499
|
+
var xmp = src.getProjectMetadata();
|
|
500
|
+
if (xmp && xmp.length < 5000) info.source.projectMetadata = xmp;
|
|
501
|
+
} catch(e) {}
|
|
502
|
+
// Proxy info
|
|
503
|
+
try { info.source.hasProxy = src.hasProxy(); } catch(e) {}
|
|
504
|
+
try { info.source.canChangeMediaPath = src.canChangeMediaPath(); } catch(e) {}
|
|
505
|
+
}
|
|
506
|
+
} catch(e) {}
|
|
507
|
+
|
|
508
|
+
// All effects/components with full property details
|
|
509
|
+
info.components = [];
|
|
510
|
+
try {
|
|
511
|
+
for (var i = 0; i < clip.components.numItems; i++) {
|
|
512
|
+
var comp = clip.components[i];
|
|
513
|
+
var compInfo = {
|
|
514
|
+
index: i,
|
|
515
|
+
displayName: comp.displayName,
|
|
516
|
+
matchName: comp.matchName,
|
|
517
|
+
properties: []
|
|
518
|
+
};
|
|
519
|
+
for (var p = 0; p < comp.properties.numItems; p++) {
|
|
520
|
+
var prop = comp.properties[p];
|
|
521
|
+
var propInfo = {
|
|
522
|
+
index: p,
|
|
523
|
+
displayName: prop.displayName
|
|
524
|
+
};
|
|
525
|
+
try { propInfo.matchName = prop.matchName; } catch(e) {}
|
|
526
|
+
try { propInfo.value = prop.getValue(0, 0); } catch(e) {}
|
|
527
|
+
try { propInfo.isTimeVarying = prop.isTimeVarying(); } catch(e) {}
|
|
528
|
+
try { propInfo.keyframesSupported = prop.areKeyframesSupported(); } catch(e) {}
|
|
529
|
+
try {
|
|
530
|
+
if (prop.isTimeVarying()) {
|
|
531
|
+
var keys = prop.getKeys();
|
|
532
|
+
if (keys && keys.length > 0) {
|
|
533
|
+
propInfo.keyframeCount = keys.length;
|
|
534
|
+
propInfo.keyframeTimes = [];
|
|
535
|
+
for (var k = 0; k < Math.min(keys.length, 20); k++) {
|
|
536
|
+
propInfo.keyframeTimes.push(__ticksToSeconds(keys[k].ticks));
|
|
537
|
+
}
|
|
538
|
+
if (keys.length > 20) propInfo.keyframesTruncated = true;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
} catch(e) {}
|
|
542
|
+
compInfo.properties.push(propInfo);
|
|
543
|
+
}
|
|
544
|
+
info.components.push(compInfo);
|
|
545
|
+
}
|
|
546
|
+
} catch(e) {}
|
|
547
|
+
info.componentCount = info.components.length;
|
|
548
|
+
|
|
549
|
+
// Clip markers
|
|
550
|
+
info.markers = [];
|
|
551
|
+
try {
|
|
552
|
+
var m = clip.markers.getFirstMarker();
|
|
553
|
+
while (m) {
|
|
554
|
+
info.markers.push({
|
|
555
|
+
name: m.name,
|
|
556
|
+
comments: m.comments,
|
|
557
|
+
startSeconds: __ticksToSeconds(m.start.ticks),
|
|
558
|
+
endSeconds: __ticksToSeconds(m.end.ticks)
|
|
559
|
+
});
|
|
560
|
+
m = clip.markers.getNextMarker(m);
|
|
561
|
+
}
|
|
562
|
+
} catch(e) {}
|
|
563
|
+
|
|
564
|
+
return __result(info);
|
|
565
|
+
`);
|
|
566
|
+
return sendCommand(script, bridgeOptions);
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
get_project_item_info: {
|
|
570
|
+
description: "Get detailed information about a project item (media file in the project panel): media path, resolution, duration, frame rate, codec info, metadata, color label, offline status, in/out points, and proxy status.",
|
|
571
|
+
parameters: {
|
|
572
|
+
type: "object",
|
|
573
|
+
properties: {
|
|
574
|
+
item_id: {
|
|
575
|
+
type: "string",
|
|
576
|
+
description: "Node ID or name of the project item",
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
required: ["item_id"],
|
|
580
|
+
},
|
|
581
|
+
handler: async (args) => {
|
|
582
|
+
const script = buildToolScript(`
|
|
583
|
+
var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
|
|
584
|
+
if (!item) return __error("Item not found: ${escapeForExtendScript(args.item_id)}");
|
|
585
|
+
|
|
586
|
+
var info = {
|
|
587
|
+
nodeId: item.nodeId,
|
|
588
|
+
name: item.name,
|
|
589
|
+
type: item.type === 1 ? "clip" : item.type === 2 ? "bin" : item.type === 3 ? "root" : item.type === 4 ? "file" : "unknown",
|
|
590
|
+
treePath: item.treePath
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
try { info.mediaPath = item.getMediaPath(); } catch(e) {}
|
|
594
|
+
try { info.offline = item.isOffline(); } catch(e) {}
|
|
595
|
+
try { info.colorLabel = item.getColorLabel(); } catch(e) {}
|
|
596
|
+
try { info.canChangeMediaPath = item.canChangeMediaPath(); } catch(e) {}
|
|
597
|
+
|
|
598
|
+
// Footage interpretation
|
|
599
|
+
try {
|
|
600
|
+
var interp = item.getFootageInterpretation();
|
|
601
|
+
if (interp) {
|
|
602
|
+
info.frameRate = interp.frameRate;
|
|
603
|
+
info.pixelAspectRatio = interp.pixelAspectRatio;
|
|
604
|
+
info.fieldType = interp.fieldType;
|
|
605
|
+
info.alphaUsage = interp.alphaUsage;
|
|
606
|
+
info.ignoreAlpha = interp.ignoreAlpha;
|
|
607
|
+
info.invertAlpha = interp.invertAlpha;
|
|
608
|
+
}
|
|
609
|
+
} catch(e) {}
|
|
610
|
+
|
|
611
|
+
// In/Out points
|
|
612
|
+
try { info.inPoint = __ticksToSeconds(item.getInPoint().ticks); } catch(e) {}
|
|
613
|
+
try { info.outPoint = __ticksToSeconds(item.getOutPoint().ticks); } catch(e) {}
|
|
614
|
+
|
|
615
|
+
// Proxy
|
|
616
|
+
try { info.hasProxy = item.hasProxy(); } catch(e) {}
|
|
617
|
+
|
|
618
|
+
// Project metadata (XMP)
|
|
619
|
+
try {
|
|
620
|
+
var xmp = item.getProjectMetadata();
|
|
621
|
+
if (xmp && xmp.length < 10000) info.projectMetadata = xmp;
|
|
622
|
+
else if (xmp) info.projectMetadataLength = xmp.length;
|
|
623
|
+
} catch(e) {}
|
|
624
|
+
|
|
625
|
+
// XMP metadata
|
|
626
|
+
try {
|
|
627
|
+
var xmp2 = item.getXMPMetadata();
|
|
628
|
+
if (xmp2 && xmp2.length < 10000) info.xmpMetadata = xmp2;
|
|
629
|
+
else if (xmp2) info.xmpMetadataLength = xmp2.length;
|
|
630
|
+
} catch(e) {}
|
|
631
|
+
|
|
632
|
+
// Item markers
|
|
633
|
+
info.markers = [];
|
|
634
|
+
try {
|
|
635
|
+
var m = item.getMarkers();
|
|
636
|
+
if (m) {
|
|
637
|
+
var marker = m.getFirstMarker();
|
|
638
|
+
while (marker) {
|
|
639
|
+
info.markers.push({
|
|
640
|
+
name: marker.name,
|
|
641
|
+
comments: marker.comments,
|
|
642
|
+
startSeconds: __ticksToSeconds(marker.start.ticks)
|
|
643
|
+
});
|
|
644
|
+
marker = m.getNextMarker(marker);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
} catch(e) {}
|
|
648
|
+
|
|
649
|
+
// Child count for bins
|
|
650
|
+
if (item.type === 2) {
|
|
651
|
+
info.childCount = item.children.numItems;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return __result(info);
|
|
655
|
+
`);
|
|
656
|
+
return sendCommand(script, bridgeOptions);
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
search_project_items: {
|
|
660
|
+
description: "Search for project items by name, media file extension, offline status, or color label. Returns matching items with full details.",
|
|
661
|
+
parameters: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
query: {
|
|
665
|
+
type: "string",
|
|
666
|
+
description: "Search query (matched against item name, case-insensitive substring match)",
|
|
667
|
+
},
|
|
668
|
+
extension: {
|
|
669
|
+
type: "string",
|
|
670
|
+
description: "Filter by file extension (e.g., 'mp4', 'wav', 'png'). Without dot.",
|
|
671
|
+
},
|
|
672
|
+
offline_only: {
|
|
673
|
+
type: "boolean",
|
|
674
|
+
description: "If true, only return offline/missing items",
|
|
675
|
+
},
|
|
676
|
+
color_label: {
|
|
677
|
+
type: "number",
|
|
678
|
+
description: "Filter by color label index (0-15)",
|
|
679
|
+
},
|
|
680
|
+
item_type: {
|
|
681
|
+
type: "string",
|
|
682
|
+
enum: ["clip", "bin", "all"],
|
|
683
|
+
description: "Filter by item type (default: all)",
|
|
684
|
+
},
|
|
685
|
+
max_results: {
|
|
686
|
+
type: "number",
|
|
687
|
+
description: "Maximum results to return (default: 100)",
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
handler: async (args) => {
|
|
692
|
+
const maxResults = args.max_results ?? 100;
|
|
693
|
+
const script = buildToolScript(`
|
|
694
|
+
var results = [];
|
|
695
|
+
var maxR = ${maxResults};
|
|
696
|
+
var query = ${args.query ? `"${escapeForExtendScript(args.query)}".toLowerCase()` : "null"};
|
|
697
|
+
var ext = ${args.extension ? `"${escapeForExtendScript(args.extension)}".toLowerCase()` : "null"};
|
|
698
|
+
var offlineOnly = ${args.offline_only ? "true" : "false"};
|
|
699
|
+
var colorFilter = ${args.color_label !== undefined ? args.color_label : "-1"};
|
|
700
|
+
var typeFilter = "${args.item_type || "all"}";
|
|
701
|
+
|
|
702
|
+
function searchBin(bin) {
|
|
703
|
+
if (results.length >= maxR) return;
|
|
704
|
+
for (var i = 0; i < bin.children.numItems; i++) {
|
|
705
|
+
if (results.length >= maxR) return;
|
|
706
|
+
var item = bin.children[i];
|
|
707
|
+
|
|
708
|
+
// Type filter
|
|
709
|
+
if (typeFilter === "clip" && item.type === 2) {
|
|
710
|
+
searchBin(item);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (typeFilter === "bin" && item.type !== 2) continue;
|
|
714
|
+
|
|
715
|
+
var match = true;
|
|
716
|
+
|
|
717
|
+
// Name filter
|
|
718
|
+
if (query && item.name.toLowerCase().indexOf(query) === -1) match = false;
|
|
719
|
+
|
|
720
|
+
// Extension filter
|
|
721
|
+
if (match && ext) {
|
|
722
|
+
try {
|
|
723
|
+
var mp = item.getMediaPath();
|
|
724
|
+
if (!mp || mp.split(".").pop().toLowerCase() !== ext) match = false;
|
|
725
|
+
} catch(e) { match = false; }
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Offline filter
|
|
729
|
+
if (match && offlineOnly) {
|
|
730
|
+
try { if (!item.isOffline()) match = false; } catch(e) { match = false; }
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Color label filter
|
|
734
|
+
if (match && colorFilter >= 0) {
|
|
735
|
+
try { if (item.getColorLabel() !== colorFilter) match = false; } catch(e) { match = false; }
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (match) {
|
|
739
|
+
var entry = {
|
|
740
|
+
nodeId: item.nodeId,
|
|
741
|
+
name: item.name,
|
|
742
|
+
type: item.type === 1 ? "clip" : item.type === 2 ? "bin" : item.type === 4 ? "file" : "unknown",
|
|
743
|
+
treePath: item.treePath
|
|
744
|
+
};
|
|
745
|
+
try { entry.mediaPath = item.getMediaPath(); } catch(e) {}
|
|
746
|
+
try { entry.offline = item.isOffline(); } catch(e) {}
|
|
747
|
+
try { entry.colorLabel = item.getColorLabel(); } catch(e) {}
|
|
748
|
+
results.push(entry);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (item.type === 2) searchBin(item);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
searchBin(app.project.rootItem);
|
|
756
|
+
return __result({ resultCount: results.length, maxResults: maxR, items: results });
|
|
757
|
+
`);
|
|
758
|
+
return sendCommand(script, bridgeOptions);
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
get_timeline_gaps: {
|
|
762
|
+
description: "Find all gaps (empty spaces) on the timeline between clips. Useful for identifying where content is missing or where clips can be tightened.",
|
|
763
|
+
parameters: {
|
|
764
|
+
type: "object",
|
|
765
|
+
properties: {
|
|
766
|
+
sequence_id: {
|
|
767
|
+
type: "string",
|
|
768
|
+
description: "Sequence name or ID. Uses active sequence if omitted.",
|
|
769
|
+
},
|
|
770
|
+
track_type: {
|
|
771
|
+
type: "string",
|
|
772
|
+
enum: ["video", "audio", "both"],
|
|
773
|
+
description: "Which track type to analyze (default: both)",
|
|
774
|
+
},
|
|
775
|
+
min_gap_seconds: {
|
|
776
|
+
type: "number",
|
|
777
|
+
description: "Minimum gap duration in seconds to report (default: 0.04 = ~1 frame at 24fps)",
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
handler: async (args) => {
|
|
782
|
+
const seqLookup = args.sequence_id
|
|
783
|
+
? `var seq = __findSequence("${escapeForExtendScript(args.sequence_id)}"); if (!seq) return __error("Sequence not found");`
|
|
784
|
+
: `var seq = app.project.activeSequence; if (!seq) return __error("No active sequence");`;
|
|
785
|
+
const minGap = args.min_gap_seconds ?? 0.04;
|
|
786
|
+
const trackType = args.track_type || "both";
|
|
787
|
+
const script = buildToolScript(`
|
|
788
|
+
${seqLookup}
|
|
789
|
+
|
|
790
|
+
var gaps = [];
|
|
791
|
+
var minGapTicks = __secondsToTicks(${minGap});
|
|
792
|
+
|
|
793
|
+
function findGaps(tracks, type) {
|
|
794
|
+
for (var t = 0; t < tracks.numTracks; t++) {
|
|
795
|
+
var track = tracks[t];
|
|
796
|
+
if (track.clips.numItems === 0) continue;
|
|
797
|
+
var prevEnd = 0;
|
|
798
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
799
|
+
var clip = track.clips[c];
|
|
800
|
+
var startTicks = parseFloat(clip.start.ticks);
|
|
801
|
+
var endTicks = parseFloat(clip.end.ticks);
|
|
802
|
+
var gapTicks = startTicks - prevEnd;
|
|
803
|
+
if (gapTicks > minGapTicks) {
|
|
804
|
+
gaps.push({
|
|
805
|
+
trackType: type,
|
|
806
|
+
trackIndex: t,
|
|
807
|
+
trackName: track.name,
|
|
808
|
+
gapStartSeconds: __ticksToSeconds("" + prevEnd),
|
|
809
|
+
gapEndSeconds: __ticksToSeconds("" + startTicks),
|
|
810
|
+
gapDurationSeconds: __ticksToSeconds("" + gapTicks),
|
|
811
|
+
beforeClip: c > 0 ? track.clips[c - 1].name : null,
|
|
812
|
+
afterClip: clip.name
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
prevEnd = endTicks;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if ("${trackType}" !== "audio") findGaps(seq.videoTracks, "video");
|
|
821
|
+
if ("${trackType}" !== "video") findGaps(seq.audioTracks, "audio");
|
|
822
|
+
|
|
823
|
+
return __result({
|
|
824
|
+
sequenceName: seq.name,
|
|
825
|
+
gapCount: gaps.length,
|
|
826
|
+
minGapSeconds: ${minGap},
|
|
827
|
+
gaps: gaps
|
|
828
|
+
});
|
|
829
|
+
`);
|
|
830
|
+
return sendCommand(script, bridgeOptions);
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
get_timeline_summary: {
|
|
834
|
+
description: "Get a human-readable summary of the timeline: total duration, clip count per track, total gaps, coverage percentage, used media files, effect usage, and marker overview. Great for a quick understanding of sequence state.",
|
|
835
|
+
parameters: {
|
|
836
|
+
type: "object",
|
|
837
|
+
properties: {
|
|
838
|
+
sequence_id: {
|
|
839
|
+
type: "string",
|
|
840
|
+
description: "Sequence name or ID. Uses active sequence if omitted.",
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
handler: async (args) => {
|
|
845
|
+
const seqLookup = args.sequence_id
|
|
846
|
+
? `var seq = __findSequence("${escapeForExtendScript(args.sequence_id)}"); if (!seq) return __error("Sequence not found");`
|
|
847
|
+
: `var seq = app.project.activeSequence; if (!seq) return __error("No active sequence");`;
|
|
848
|
+
const script = buildToolScript(`
|
|
849
|
+
${seqLookup}
|
|
850
|
+
|
|
851
|
+
var summary = {
|
|
852
|
+
name: seq.name,
|
|
853
|
+
id: seq.sequenceID,
|
|
854
|
+
resolution: seq.frameSizeHorizontal + "x" + seq.frameSizeVertical,
|
|
855
|
+
durationSeconds: __ticksToSeconds(seq.end)
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
try {
|
|
859
|
+
var s = seq.getSettings();
|
|
860
|
+
if (s) summary.frameRate = s.videoFrameRate;
|
|
861
|
+
} catch(e) {}
|
|
862
|
+
|
|
863
|
+
// Playhead
|
|
864
|
+
try { summary.playheadSeconds = __ticksToSeconds(seq.getPlayerPosition().ticks); } catch(e) {}
|
|
865
|
+
|
|
866
|
+
var totalVideoClips = 0;
|
|
867
|
+
var totalAudioClips = 0;
|
|
868
|
+
var usedMedia = {};
|
|
869
|
+
var effectUsage = {};
|
|
870
|
+
var disabledClips = 0;
|
|
871
|
+
var totalVideoFilled = 0;
|
|
872
|
+
var seqEndTicks = parseFloat(seq.end);
|
|
873
|
+
|
|
874
|
+
// Video tracks
|
|
875
|
+
var videoTrackSummary = [];
|
|
876
|
+
for (var t = 0; t < seq.videoTracks.numTracks; t++) {
|
|
877
|
+
var track = seq.videoTracks[t];
|
|
878
|
+
var trackFilled = 0;
|
|
879
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
880
|
+
totalVideoClips++;
|
|
881
|
+
var clip = track.clips[c];
|
|
882
|
+
trackFilled += parseFloat(clip.end.ticks) - parseFloat(clip.start.ticks);
|
|
883
|
+
try { if (clip.isDisabled()) disabledClips++; } catch(e) {}
|
|
884
|
+
try {
|
|
885
|
+
if (clip.projectItem) {
|
|
886
|
+
var srcName = clip.projectItem.name;
|
|
887
|
+
if (!usedMedia[srcName]) usedMedia[srcName] = 0;
|
|
888
|
+
usedMedia[srcName]++;
|
|
889
|
+
}
|
|
890
|
+
} catch(e) {}
|
|
891
|
+
try {
|
|
892
|
+
for (var e = 0; e < clip.components.numItems; e++) {
|
|
893
|
+
var eName = clip.components[e].displayName;
|
|
894
|
+
if (!effectUsage[eName]) effectUsage[eName] = 0;
|
|
895
|
+
effectUsage[eName]++;
|
|
896
|
+
}
|
|
897
|
+
} catch(e) {}
|
|
898
|
+
}
|
|
899
|
+
if (t === 0) totalVideoFilled = trackFilled;
|
|
900
|
+
videoTrackSummary.push({
|
|
901
|
+
index: t,
|
|
902
|
+
name: track.name,
|
|
903
|
+
clipCount: track.clips.numItems,
|
|
904
|
+
muted: track.isMuted(),
|
|
905
|
+
locked: track.isLocked()
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// Audio tracks
|
|
910
|
+
var audioTrackSummary = [];
|
|
911
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
912
|
+
var track = seq.audioTracks[t];
|
|
913
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
914
|
+
totalAudioClips++;
|
|
915
|
+
var aClip = track.clips[c];
|
|
916
|
+
try {
|
|
917
|
+
if (aClip.projectItem) {
|
|
918
|
+
var srcName = aClip.projectItem.name;
|
|
919
|
+
if (!usedMedia[srcName]) usedMedia[srcName] = 0;
|
|
920
|
+
usedMedia[srcName]++;
|
|
921
|
+
}
|
|
922
|
+
} catch(e) {}
|
|
923
|
+
}
|
|
924
|
+
audioTrackSummary.push({
|
|
925
|
+
index: t,
|
|
926
|
+
name: track.name,
|
|
927
|
+
clipCount: track.clips.numItems,
|
|
928
|
+
muted: track.isMuted(),
|
|
929
|
+
locked: track.isLocked()
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
summary.totalVideoClips = totalVideoClips;
|
|
934
|
+
summary.totalAudioClips = totalAudioClips;
|
|
935
|
+
summary.totalClips = totalVideoClips + totalAudioClips;
|
|
936
|
+
summary.disabledClips = disabledClips;
|
|
937
|
+
summary.videoTrackCount = seq.videoTracks.numTracks;
|
|
938
|
+
summary.audioTrackCount = seq.audioTracks.numTracks;
|
|
939
|
+
summary.videoTracks = videoTrackSummary;
|
|
940
|
+
summary.audioTracks = audioTrackSummary;
|
|
941
|
+
|
|
942
|
+
// V1 coverage
|
|
943
|
+
if (seqEndTicks > 0) {
|
|
944
|
+
summary.v1CoveragePercent = Math.round((totalVideoFilled / seqEndTicks) * 10000) / 100;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// Used media
|
|
948
|
+
var mediaList = [];
|
|
949
|
+
for (var name in usedMedia) {
|
|
950
|
+
if (usedMedia.hasOwnProperty(name)) {
|
|
951
|
+
mediaList.push({ name: name, useCount: usedMedia[name] });
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
summary.usedMedia = mediaList;
|
|
955
|
+
summary.uniqueMediaCount = mediaList.length;
|
|
956
|
+
|
|
957
|
+
// Effect usage
|
|
958
|
+
var effectList = [];
|
|
959
|
+
for (var name in effectUsage) {
|
|
960
|
+
if (effectUsage.hasOwnProperty(name)) {
|
|
961
|
+
effectList.push({ name: name, count: effectUsage[name] });
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
summary.effectUsage = effectList;
|
|
965
|
+
|
|
966
|
+
// Markers
|
|
967
|
+
var markerCount = 0;
|
|
968
|
+
try {
|
|
969
|
+
var m = seq.markers.getFirstMarker();
|
|
970
|
+
while (m) { markerCount++; m = seq.markers.getNextMarker(m); }
|
|
971
|
+
} catch(e) {}
|
|
972
|
+
summary.markerCount = markerCount;
|
|
973
|
+
|
|
974
|
+
return __result(summary);
|
|
975
|
+
`);
|
|
976
|
+
return sendCommand(script, bridgeOptions);
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
get_offline_media: {
|
|
980
|
+
description: "Find all offline/missing media in the project with their expected file paths. Essential for diagnosing broken links.",
|
|
981
|
+
parameters: {},
|
|
982
|
+
handler: async () => {
|
|
983
|
+
const script = buildToolScript(`
|
|
984
|
+
var offline = [];
|
|
985
|
+
function scan(bin) {
|
|
986
|
+
for (var i = 0; i < bin.children.numItems; i++) {
|
|
987
|
+
var item = bin.children[i];
|
|
988
|
+
try {
|
|
989
|
+
if (item.isOffline()) {
|
|
990
|
+
var entry = {
|
|
991
|
+
nodeId: item.nodeId,
|
|
992
|
+
name: item.name,
|
|
993
|
+
treePath: item.treePath
|
|
994
|
+
};
|
|
995
|
+
try { entry.mediaPath = item.getMediaPath(); } catch(e) {}
|
|
996
|
+
try { entry.colorLabel = item.getColorLabel(); } catch(e) {}
|
|
997
|
+
offline.push(entry);
|
|
998
|
+
}
|
|
999
|
+
} catch(e) {}
|
|
1000
|
+
if (item.type === 2) scan(item);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
scan(app.project.rootItem);
|
|
1004
|
+
return __result({ offlineCount: offline.length, items: offline });
|
|
1005
|
+
`);
|
|
1006
|
+
return sendCommand(script, bridgeOptions);
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
get_used_media_report: {
|
|
1010
|
+
description: "Get a report of all media files used in a sequence: which source files are used, how many times each appears, on which tracks, and whether any sources are offline.",
|
|
1011
|
+
parameters: {
|
|
1012
|
+
type: "object",
|
|
1013
|
+
properties: {
|
|
1014
|
+
sequence_id: {
|
|
1015
|
+
type: "string",
|
|
1016
|
+
description: "Sequence name or ID. Uses active sequence if omitted.",
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
handler: async (args) => {
|
|
1021
|
+
const seqLookup = args.sequence_id
|
|
1022
|
+
? `var seq = __findSequence("${escapeForExtendScript(args.sequence_id)}"); if (!seq) return __error("Sequence not found");`
|
|
1023
|
+
: `var seq = app.project.activeSequence; if (!seq) return __error("No active sequence");`;
|
|
1024
|
+
const script = buildToolScript(`
|
|
1025
|
+
${seqLookup}
|
|
1026
|
+
|
|
1027
|
+
var mediaMap = {};
|
|
1028
|
+
|
|
1029
|
+
function scanTracks(tracks, trackType) {
|
|
1030
|
+
for (var t = 0; t < tracks.numTracks; t++) {
|
|
1031
|
+
var track = tracks[t];
|
|
1032
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
1033
|
+
var clip = track.clips[c];
|
|
1034
|
+
try {
|
|
1035
|
+
var src = clip.projectItem;
|
|
1036
|
+
if (!src) continue;
|
|
1037
|
+
var key = src.nodeId;
|
|
1038
|
+
if (!mediaMap[key]) {
|
|
1039
|
+
mediaMap[key] = {
|
|
1040
|
+
nodeId: src.nodeId,
|
|
1041
|
+
name: src.name,
|
|
1042
|
+
mediaPath: "",
|
|
1043
|
+
offline: false,
|
|
1044
|
+
useCount: 0,
|
|
1045
|
+
tracks: []
|
|
1046
|
+
};
|
|
1047
|
+
try { mediaMap[key].mediaPath = src.getMediaPath(); } catch(e) {}
|
|
1048
|
+
try { mediaMap[key].offline = src.isOffline(); } catch(e) {}
|
|
1049
|
+
}
|
|
1050
|
+
mediaMap[key].useCount++;
|
|
1051
|
+
mediaMap[key].tracks.push(trackType + " " + t);
|
|
1052
|
+
} catch(e) {}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
scanTracks(seq.videoTracks, "V");
|
|
1058
|
+
scanTracks(seq.audioTracks, "A");
|
|
1059
|
+
|
|
1060
|
+
var report = [];
|
|
1061
|
+
var offlineUsed = 0;
|
|
1062
|
+
for (var key in mediaMap) {
|
|
1063
|
+
if (mediaMap.hasOwnProperty(key)) {
|
|
1064
|
+
// Deduplicate track list
|
|
1065
|
+
var unique = {};
|
|
1066
|
+
for (var i = 0; i < mediaMap[key].tracks.length; i++) {
|
|
1067
|
+
unique[mediaMap[key].tracks[i]] = true;
|
|
1068
|
+
}
|
|
1069
|
+
var trackList = [];
|
|
1070
|
+
for (var k in unique) { if (unique.hasOwnProperty(k)) trackList.push(k); }
|
|
1071
|
+
mediaMap[key].tracks = trackList;
|
|
1072
|
+
if (mediaMap[key].offline) offlineUsed++;
|
|
1073
|
+
report.push(mediaMap[key]);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
return __result({
|
|
1078
|
+
sequenceName: seq.name,
|
|
1079
|
+
uniqueMediaCount: report.length,
|
|
1080
|
+
offlineUsedCount: offlineUsed,
|
|
1081
|
+
media: report
|
|
1082
|
+
});
|
|
1083
|
+
`);
|
|
1084
|
+
return sendCommand(script, bridgeOptions);
|
|
1085
|
+
},
|
|
1086
|
+
},
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
//# sourceMappingURL=inspection.js.map
|