@synthetic-ai/premiere-mcp 2.2.0 → 2.3.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -135
- package/LICENSE +21 -21
- package/README.md +242 -242
- package/cep-plugin/.debug +8 -8
- package/cep-plugin/CSInterface.js +92 -92
- package/cep-plugin/host.jsx +7 -7
- package/cep-plugin/index.html +472 -472
- package/cep-plugin/main.js +6 -0
- package/cep-plugin/premiere.jsx +24947 -24947
- package/dist/bridge/script-builder.js +378 -378
- package/dist/license.js +38 -0
- package/dist/server.js +60 -60
- package/dist/tools/advanced.js +357 -357
- package/dist/tools/audio-advanced.js +1049 -1049
- package/dist/tools/audio.js +42 -42
- package/dist/tools/captions.js +27 -27
- package/dist/tools/clipboard.js +214 -214
- package/dist/tools/color-advanced.js +253 -253
- package/dist/tools/discovery.js +291 -291
- package/dist/tools/effects.js +285 -285
- package/dist/tools/export-presets.js +206 -206
- package/dist/tools/export.js +255 -255
- package/dist/tools/health.js +10 -10
- package/dist/tools/inspection.js +885 -885
- package/dist/tools/keyframes.js +369 -369
- package/dist/tools/markers.js +89 -89
- package/dist/tools/media.js +178 -178
- package/dist/tools/metadata.js +94 -94
- package/dist/tools/multicam.js +50 -50
- package/dist/tools/playback.js +15 -15
- package/dist/tools/playhead.js +95 -95
- package/dist/tools/project-manager.js +21 -21
- package/dist/tools/project.js +149 -149
- package/dist/tools/scripting.js +319 -319
- package/dist/tools/selection.js +202 -202
- package/dist/tools/sequence-advanced.js +379 -379
- package/dist/tools/sequence-settings.js +564 -564
- package/dist/tools/sequence.js +167 -167
- package/dist/tools/source-monitor.js +68 -68
- package/dist/tools/text.js +58 -58
- package/dist/tools/timeline.js +312 -312
- package/dist/tools/track-management.js +446 -446
- package/dist/tools/track-targeting.js +635 -635
- package/dist/tools/tracks.js +87 -87
- package/dist/tools/transitions-motion.js +174 -174
- package/dist/tools/transitions.js +111 -111
- package/dist/tools/utility.js +606 -606
- package/dist/tools/workspace.js +29 -29
- package/package.json +66 -66
- package/scripts/build-chat-dmg.sh +210 -210
- package/scripts/build-chat-zip.sh +291 -291
- package/scripts/install-cep-windows.ps1 +99 -99
- package/scripts/install-cep.sh +66 -66
- package/scripts/install-chat-plugin.bat +67 -67
- package/scripts/install-chat-plugin.sh +85 -85
- package/scripts/install-windows.bat +95 -95
- package/scripts/package.mjs +254 -254
package/dist/tools/timeline.js
CHANGED
|
@@ -3,18 +3,18 @@ import { sendCommand } from "../bridge/file-bridge.js";
|
|
|
3
3
|
export function getTimelineTools(bridgeOptions) {
|
|
4
4
|
return {
|
|
5
5
|
add_to_timeline: {
|
|
6
|
-
description: `Add a project item (clip, image, or audio file) to the active sequence timeline at a specific position.
|
|
7
|
-
|
|
8
|
-
Preconditions: An active sequence must exist (use create_sequence or set_active_sequence first). The item must already be imported into the project (use import_media first).
|
|
9
|
-
|
|
10
|
-
The clip is inserted using insert-edit semantics — content after the insertion point shifts forward. For overwrite-edit behavior, use overwrite_clip instead.
|
|
11
|
-
|
|
12
|
-
Parameters:
|
|
13
|
-
- item_id: The node ID from list_project_items, or the item's display name (partial match supported).
|
|
14
|
-
- track_index: Video track index (0 = V1, 1 = V2, etc.). Higher tracks composite on top.
|
|
15
|
-
- start_seconds: Timeline position in seconds where the clip should start (e.g., 10.5 = 10 seconds 12 frames at 24fps).
|
|
16
|
-
- audio_track_index: Audio track index for the linked audio (default matches track_index).
|
|
17
|
-
|
|
6
|
+
description: `Add a project item (clip, image, or audio file) to the active sequence timeline at a specific position.
|
|
7
|
+
|
|
8
|
+
Preconditions: An active sequence must exist (use create_sequence or set_active_sequence first). The item must already be imported into the project (use import_media first).
|
|
9
|
+
|
|
10
|
+
The clip is inserted using insert-edit semantics — content after the insertion point shifts forward. For overwrite-edit behavior, use overwrite_clip instead.
|
|
11
|
+
|
|
12
|
+
Parameters:
|
|
13
|
+
- item_id: The node ID from list_project_items, or the item's display name (partial match supported).
|
|
14
|
+
- track_index: Video track index (0 = V1, 1 = V2, etc.). Higher tracks composite on top.
|
|
15
|
+
- start_seconds: Timeline position in seconds where the clip should start (e.g., 10.5 = 10 seconds 12 frames at 24fps).
|
|
16
|
+
- audio_track_index: Audio track index for the linked audio (default matches track_index).
|
|
17
|
+
|
|
18
18
|
Example: add_to_timeline({ item_id: "interview.mp4", track_index: 0, start_seconds: 30, audio_track_index: 0 })`,
|
|
19
19
|
parameters: {
|
|
20
20
|
type: "object",
|
|
@@ -42,22 +42,22 @@ Example: add_to_timeline({ item_id: "interview.mp4", track_index: 0, start_secon
|
|
|
42
42
|
const trackIndex = args.track_index ?? 0;
|
|
43
43
|
const startSeconds = args.start_seconds ?? 0;
|
|
44
44
|
const audioTrackIndex = args.audio_track_index ?? 0;
|
|
45
|
-
const script = buildToolScript(`
|
|
46
|
-
var seq = app.project.activeSequence;
|
|
47
|
-
if (!seq) return __error("No active sequence");
|
|
48
|
-
|
|
49
|
-
var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
|
|
50
|
-
if (!item) return __error("Project item not found: ${escapeForExtendScript(args.item_id)}");
|
|
51
|
-
|
|
52
|
-
var startTicks = __secondsToTicks(${startSeconds}).toString();
|
|
53
|
-
seq.insertClip(item, startTicks, ${trackIndex}, ${audioTrackIndex});
|
|
54
|
-
|
|
55
|
-
return __result({
|
|
56
|
-
added: true,
|
|
57
|
-
item: item.name,
|
|
58
|
-
trackIndex: ${trackIndex},
|
|
59
|
-
startSeconds: ${startSeconds}
|
|
60
|
-
});
|
|
45
|
+
const script = buildToolScript(`
|
|
46
|
+
var seq = app.project.activeSequence;
|
|
47
|
+
if (!seq) return __error("No active sequence");
|
|
48
|
+
|
|
49
|
+
var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
|
|
50
|
+
if (!item) return __error("Project item not found: ${escapeForExtendScript(args.item_id)}");
|
|
51
|
+
|
|
52
|
+
var startTicks = __secondsToTicks(${startSeconds}).toString();
|
|
53
|
+
seq.insertClip(item, startTicks, ${trackIndex}, ${audioTrackIndex});
|
|
54
|
+
|
|
55
|
+
return __result({
|
|
56
|
+
added: true,
|
|
57
|
+
item: item.name,
|
|
58
|
+
trackIndex: ${trackIndex},
|
|
59
|
+
startSeconds: ${startSeconds}
|
|
60
|
+
});
|
|
61
61
|
`);
|
|
62
62
|
return sendCommand(script, bridgeOptions);
|
|
63
63
|
},
|
|
@@ -79,27 +79,27 @@ Example: add_to_timeline({ item_id: "interview.mp4", track_index: 0, start_secon
|
|
|
79
79
|
required: ["node_id"],
|
|
80
80
|
},
|
|
81
81
|
handler: async (args) => {
|
|
82
|
-
const script = buildToolScript(`
|
|
83
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
84
|
-
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
85
|
-
|
|
86
|
-
var clip = result.clip;
|
|
87
|
-
clip.remove(${args.ripple ? "true" : "false"}, ${args.ripple ? "true" : "false"});
|
|
88
|
-
return __result({ removed: true, clipName: clip.name });
|
|
82
|
+
const script = buildToolScript(`
|
|
83
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
84
|
+
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
85
|
+
|
|
86
|
+
var clip = result.clip;
|
|
87
|
+
clip.remove(${args.ripple ? "true" : "false"}, ${args.ripple ? "true" : "false"});
|
|
88
|
+
return __result({ removed: true, clipName: clip.name });
|
|
89
89
|
`);
|
|
90
90
|
return sendCommand(script, bridgeOptions);
|
|
91
91
|
},
|
|
92
92
|
},
|
|
93
93
|
move_clip: {
|
|
94
|
-
description: `Move a clip to a new timeline position, optionally changing its track.
|
|
95
|
-
|
|
96
|
-
Preconditions: An active sequence must exist and the clip's node_id must be valid (get node IDs from get_active_sequence or list_sequence_tracks).
|
|
97
|
-
|
|
98
|
-
The clip moves to the exact start time specified in seconds. If new_track_index is provided, the clip is also moved to that track. The audio/video link is maintained during the move.
|
|
99
|
-
|
|
100
|
-
Note: This uses clip.moveToTime() which is the correct ExtendScript API — clip.start is read-only and cannot be assigned directly.
|
|
101
|
-
|
|
102
|
-
Example: move_clip({ node_id: "abc123", new_start_seconds: 45.5 })
|
|
94
|
+
description: `Move a clip to a new timeline position, optionally changing its track.
|
|
95
|
+
|
|
96
|
+
Preconditions: An active sequence must exist and the clip's node_id must be valid (get node IDs from get_active_sequence or list_sequence_tracks).
|
|
97
|
+
|
|
98
|
+
The clip moves to the exact start time specified in seconds. If new_track_index is provided, the clip is also moved to that track. The audio/video link is maintained during the move.
|
|
99
|
+
|
|
100
|
+
Note: This uses clip.moveToTime() which is the correct ExtendScript API — clip.start is read-only and cannot be assigned directly.
|
|
101
|
+
|
|
102
|
+
Example: move_clip({ node_id: "abc123", new_start_seconds: 45.5 })
|
|
103
103
|
Example: move_clip({ node_id: "abc123", new_start_seconds: 10.0, new_track_index: 1 })`,
|
|
104
104
|
parameters: {
|
|
105
105
|
type: "object",
|
|
@@ -120,52 +120,52 @@ Example: move_clip({ node_id: "abc123", new_start_seconds: 10.0, new_track_index
|
|
|
120
120
|
required: ["node_id", "new_start_seconds"],
|
|
121
121
|
},
|
|
122
122
|
handler: async (args) => {
|
|
123
|
-
const script = buildToolScript(`
|
|
124
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
125
|
-
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
126
|
-
|
|
127
|
-
var clip = result.clip;
|
|
128
|
-
|
|
129
|
-
// clip.start is read-only; clip.move(newInPointTime) is the documented DOM reposition.
|
|
130
|
-
var newTime = new Time();
|
|
131
|
-
newTime.seconds = ${args.new_start_seconds};
|
|
132
|
-
clip.move(newTime);
|
|
133
|
-
|
|
134
|
-
var trackChangeNote = null;
|
|
135
|
-
${args.new_track_index !== undefined ? `
|
|
136
|
-
// DOM TrackItem has no track-change method; route via QE if available.
|
|
137
|
-
try {
|
|
138
|
-
app.enableQE();
|
|
139
|
-
var qeSeq = qe.project.getActiveSequence();
|
|
140
|
-
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
141
|
-
var qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
142
|
-
qeClip.moveToTrack(${args.new_track_index});
|
|
143
|
-
} catch (e) {
|
|
144
|
-
trackChangeNote = "DOM track-change isn't supported; QE move failed: " + e.toString();
|
|
145
|
-
}
|
|
146
|
-
` : ""}
|
|
147
|
-
|
|
148
|
-
return __result({
|
|
149
|
-
moved: true,
|
|
150
|
-
clipName: clip.name,
|
|
151
|
-
newStart: ${args.new_start_seconds},
|
|
152
|
-
trackChangeNote: trackChangeNote
|
|
153
|
-
});
|
|
123
|
+
const script = buildToolScript(`
|
|
124
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
125
|
+
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
126
|
+
|
|
127
|
+
var clip = result.clip;
|
|
128
|
+
|
|
129
|
+
// clip.start is read-only; clip.move(newInPointTime) is the documented DOM reposition.
|
|
130
|
+
var newTime = new Time();
|
|
131
|
+
newTime.seconds = ${args.new_start_seconds};
|
|
132
|
+
clip.move(newTime);
|
|
133
|
+
|
|
134
|
+
var trackChangeNote = null;
|
|
135
|
+
${args.new_track_index !== undefined ? `
|
|
136
|
+
// DOM TrackItem has no track-change method; route via QE if available.
|
|
137
|
+
try {
|
|
138
|
+
app.enableQE();
|
|
139
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
140
|
+
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
141
|
+
var qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
142
|
+
qeClip.moveToTrack(${args.new_track_index});
|
|
143
|
+
} catch (e) {
|
|
144
|
+
trackChangeNote = "DOM track-change isn't supported; QE move failed: " + e.toString();
|
|
145
|
+
}
|
|
146
|
+
` : ""}
|
|
147
|
+
|
|
148
|
+
return __result({
|
|
149
|
+
moved: true,
|
|
150
|
+
clipName: clip.name,
|
|
151
|
+
newStart: ${args.new_start_seconds},
|
|
152
|
+
trackChangeNote: trackChangeNote
|
|
153
|
+
});
|
|
154
154
|
`);
|
|
155
155
|
return sendCommand(script, bridgeOptions);
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
158
|
trim_clip: {
|
|
159
|
-
description: `Trim a clip's in-point and/or out-point on the timeline.
|
|
160
|
-
|
|
161
|
-
Preconditions: Clip must exist on the active sequence (use node_id from get_active_sequence).
|
|
162
|
-
|
|
163
|
-
Time values are in seconds relative to the clip's source media (not the timeline position). For example, if a 60-second clip starts at timeline position 10s, setting new_in_seconds=5 trims the first 5 seconds of the source, making the clip start 5s into the original footage.
|
|
164
|
-
|
|
165
|
-
Note: Uses clip.setInPoint(time, 2) and clip.setOutPoint(time, 2) — the alignment constant 2 (kTime) is required. Direct assignment to clip.inPoint / clip.outPoint does not work in ExtendScript.
|
|
166
|
-
|
|
167
|
-
Example: trim_clip({ node_id: "abc123", new_in_seconds: 2.0 }) — trim 2s off the start
|
|
168
|
-
Example: trim_clip({ node_id: "abc123", new_out_seconds: 58.5 }) — trim the end to 58.5s
|
|
159
|
+
description: `Trim a clip's in-point and/or out-point on the timeline.
|
|
160
|
+
|
|
161
|
+
Preconditions: Clip must exist on the active sequence (use node_id from get_active_sequence).
|
|
162
|
+
|
|
163
|
+
Time values are in seconds relative to the clip's source media (not the timeline position). For example, if a 60-second clip starts at timeline position 10s, setting new_in_seconds=5 trims the first 5 seconds of the source, making the clip start 5s into the original footage.
|
|
164
|
+
|
|
165
|
+
Note: Uses clip.setInPoint(time, 2) and clip.setOutPoint(time, 2) — the alignment constant 2 (kTime) is required. Direct assignment to clip.inPoint / clip.outPoint does not work in ExtendScript.
|
|
166
|
+
|
|
167
|
+
Example: trim_clip({ node_id: "abc123", new_in_seconds: 2.0 }) — trim 2s off the start
|
|
168
|
+
Example: trim_clip({ node_id: "abc123", new_out_seconds: 58.5 }) — trim the end to 58.5s
|
|
169
169
|
Example: trim_clip({ node_id: "abc123", new_in_seconds: 1.0, new_out_seconds: 59.0 }) — trim both ends`,
|
|
170
170
|
parameters: {
|
|
171
171
|
type: "object",
|
|
@@ -186,55 +186,55 @@ Example: trim_clip({ node_id: "abc123", new_in_seconds: 1.0, new_out_seconds: 59
|
|
|
186
186
|
required: ["node_id"],
|
|
187
187
|
},
|
|
188
188
|
handler: async (args) => {
|
|
189
|
-
const script = buildToolScript(`
|
|
190
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
191
|
-
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
192
|
-
|
|
193
|
-
var clip = result.clip;
|
|
194
|
-
|
|
195
|
-
// DOM TrackItem has no setInPoint/setOutPoint; route the trim via QE if available.
|
|
196
|
-
var qeClip = null;
|
|
197
|
-
try {
|
|
198
|
-
app.enableQE();
|
|
199
|
-
var qeSeq = qe.project.getActiveSequence();
|
|
200
|
-
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
201
|
-
qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
202
|
-
} catch (e) {
|
|
203
|
-
qeClip = null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (!qeClip) {
|
|
207
|
-
return __error("Timeline trim isn't supported via this path (DOM TrackItem has no setInPoint/setOutPoint, and QE is unavailable)");
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
var didTrim = false;
|
|
211
|
-
${args.new_in_seconds !== undefined ? `
|
|
212
|
-
qeClip.setInPoint(__secondsToTicks(${args.new_in_seconds}).toString());
|
|
213
|
-
didTrim = true;
|
|
214
|
-
` : ""}
|
|
215
|
-
${args.new_out_seconds !== undefined ? `
|
|
216
|
-
qeClip.setOutPoint(__secondsToTicks(${args.new_out_seconds}).toString());
|
|
217
|
-
didTrim = true;
|
|
218
|
-
` : ""}
|
|
219
|
-
|
|
220
|
-
return __result({
|
|
221
|
-
trimmed: didTrim,
|
|
222
|
-
clipName: clip.name,
|
|
223
|
-
newInSeconds: ${args.new_in_seconds !== undefined ? args.new_in_seconds : "null"},
|
|
224
|
-
newOutSeconds: ${args.new_out_seconds !== undefined ? args.new_out_seconds : "null"}
|
|
225
|
-
});
|
|
189
|
+
const script = buildToolScript(`
|
|
190
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
191
|
+
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
192
|
+
|
|
193
|
+
var clip = result.clip;
|
|
194
|
+
|
|
195
|
+
// DOM TrackItem has no setInPoint/setOutPoint; route the trim via QE if available.
|
|
196
|
+
var qeClip = null;
|
|
197
|
+
try {
|
|
198
|
+
app.enableQE();
|
|
199
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
200
|
+
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
201
|
+
qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
qeClip = null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!qeClip) {
|
|
207
|
+
return __error("Timeline trim isn't supported via this path (DOM TrackItem has no setInPoint/setOutPoint, and QE is unavailable)");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var didTrim = false;
|
|
211
|
+
${args.new_in_seconds !== undefined ? `
|
|
212
|
+
qeClip.setInPoint(__secondsToTicks(${args.new_in_seconds}).toString());
|
|
213
|
+
didTrim = true;
|
|
214
|
+
` : ""}
|
|
215
|
+
${args.new_out_seconds !== undefined ? `
|
|
216
|
+
qeClip.setOutPoint(__secondsToTicks(${args.new_out_seconds}).toString());
|
|
217
|
+
didTrim = true;
|
|
218
|
+
` : ""}
|
|
219
|
+
|
|
220
|
+
return __result({
|
|
221
|
+
trimmed: didTrim,
|
|
222
|
+
clipName: clip.name,
|
|
223
|
+
newInSeconds: ${args.new_in_seconds !== undefined ? args.new_in_seconds : "null"},
|
|
224
|
+
newOutSeconds: ${args.new_out_seconds !== undefined ? args.new_out_seconds : "null"}
|
|
225
|
+
});
|
|
226
226
|
`);
|
|
227
227
|
return sendCommand(script, bridgeOptions);
|
|
228
228
|
},
|
|
229
229
|
},
|
|
230
230
|
split_clip: {
|
|
231
|
-
description: `Split (razor cut) a clip at a specific timeline position. Uses QE DOM.
|
|
232
|
-
|
|
233
|
-
Preconditions: An active sequence must exist with at least one clip on the specified track. The time_seconds must fall within a clip on the specified track — splitting empty space has no effect.
|
|
234
|
-
|
|
235
|
-
After the split, the original clip becomes two separate clips that can be independently moved, trimmed, or deleted.
|
|
236
|
-
|
|
237
|
-
Example: split_clip({ time_seconds: 30.0 }) — split V1 at 30 seconds
|
|
231
|
+
description: `Split (razor cut) a clip at a specific timeline position. Uses QE DOM.
|
|
232
|
+
|
|
233
|
+
Preconditions: An active sequence must exist with at least one clip on the specified track. The time_seconds must fall within a clip on the specified track — splitting empty space has no effect.
|
|
234
|
+
|
|
235
|
+
After the split, the original clip becomes two separate clips that can be independently moved, trimmed, or deleted.
|
|
236
|
+
|
|
237
|
+
Example: split_clip({ time_seconds: 30.0 }) — split V1 at 30 seconds
|
|
238
238
|
Example: split_clip({ time_seconds: 15.5, track_index: 1, track_type: "video" })`,
|
|
239
239
|
parameters: {
|
|
240
240
|
type: "object",
|
|
@@ -258,18 +258,18 @@ Example: split_clip({ time_seconds: 15.5, track_index: 1, track_type: "video" })
|
|
|
258
258
|
handler: async (args) => {
|
|
259
259
|
const trackType = args.track_type || "video";
|
|
260
260
|
const trackIndex = args.track_index ?? 0;
|
|
261
|
-
const script = buildToolScript(`
|
|
262
|
-
app.enableQE();
|
|
263
|
-
var seq = qe.project.getActiveSequence();
|
|
264
|
-
if (!seq) return __error("No active sequence (QE)");
|
|
265
|
-
|
|
266
|
-
var track = ${trackType === "video" ? `seq.getVideoTrackAt(${trackIndex})` : `seq.getAudioTrackAt(${trackIndex})`};
|
|
267
|
-
if (!track) return __error("Track not found");
|
|
268
|
-
|
|
269
|
-
var timeTicks = __secondsToTicks(${args.time_seconds}).toString();
|
|
270
|
-
track.razor(timeTicks);
|
|
271
|
-
|
|
272
|
-
return __result({ split: true, atSeconds: ${args.time_seconds}, trackIndex: ${trackIndex}, trackType: "${trackType}" });
|
|
261
|
+
const script = buildToolScript(`
|
|
262
|
+
app.enableQE();
|
|
263
|
+
var seq = qe.project.getActiveSequence();
|
|
264
|
+
if (!seq) return __error("No active sequence (QE)");
|
|
265
|
+
|
|
266
|
+
var track = ${trackType === "video" ? `seq.getVideoTrackAt(${trackIndex})` : `seq.getAudioTrackAt(${trackIndex})`};
|
|
267
|
+
if (!track) return __error("Track not found");
|
|
268
|
+
|
|
269
|
+
var timeTicks = __secondsToTicks(${args.time_seconds}).toString();
|
|
270
|
+
track.razor(timeTicks);
|
|
271
|
+
|
|
272
|
+
return __result({ split: true, atSeconds: ${args.time_seconds}, trackIndex: ${trackIndex}, trackType: "${trackType}" });
|
|
273
273
|
`);
|
|
274
274
|
return sendCommand(script, bridgeOptions);
|
|
275
275
|
},
|
|
@@ -287,22 +287,22 @@ Example: split_clip({ time_seconds: 15.5, track_index: 1, track_type: "video" })
|
|
|
287
287
|
required: ["node_id"],
|
|
288
288
|
},
|
|
289
289
|
handler: async (args) => {
|
|
290
|
-
const script = buildToolScript(`
|
|
291
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
292
|
-
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
293
|
-
|
|
294
|
-
var clip = result.clip;
|
|
295
|
-
var seq = app.project.activeSequence;
|
|
296
|
-
var projectItem = clip.projectItem;
|
|
297
|
-
|
|
298
|
-
if (!projectItem) return __error("Cannot find source project item for clip");
|
|
299
|
-
|
|
300
|
-
var newTrackIndex = result.trackIndex + 1;
|
|
301
|
-
var startTicks = clip.start.ticks;
|
|
302
|
-
|
|
303
|
-
seq.insertClip(projectItem, startTicks, newTrackIndex, newTrackIndex);
|
|
304
|
-
|
|
305
|
-
return __result({ duplicated: true, clipName: clip.name, newTrackIndex: newTrackIndex });
|
|
290
|
+
const script = buildToolScript(`
|
|
291
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
292
|
+
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
293
|
+
|
|
294
|
+
var clip = result.clip;
|
|
295
|
+
var seq = app.project.activeSequence;
|
|
296
|
+
var projectItem = clip.projectItem;
|
|
297
|
+
|
|
298
|
+
if (!projectItem) return __error("Cannot find source project item for clip");
|
|
299
|
+
|
|
300
|
+
var newTrackIndex = result.trackIndex + 1;
|
|
301
|
+
var startTicks = clip.start.ticks;
|
|
302
|
+
|
|
303
|
+
seq.insertClip(projectItem, startTicks, newTrackIndex, newTrackIndex);
|
|
304
|
+
|
|
305
|
+
return __result({ duplicated: true, clipName: clip.name, newTrackIndex: newTrackIndex });
|
|
306
306
|
`);
|
|
307
307
|
return sendCommand(script, bridgeOptions);
|
|
308
308
|
},
|
|
@@ -324,17 +324,17 @@ Example: split_clip({ time_seconds: 15.5, track_index: 1, track_type: "video" })
|
|
|
324
324
|
required: ["node_id", "enabled"],
|
|
325
325
|
},
|
|
326
326
|
handler: async (args) => {
|
|
327
|
-
const script = buildToolScript(`
|
|
328
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
329
|
-
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
330
|
-
|
|
331
|
-
// PP 2026: clip.disabled is a property, clip.setDisabled() does not exist.
|
|
332
|
-
try {
|
|
333
|
-
result.clip.disabled = ${args.enabled ? "false" : "true"};
|
|
334
|
-
} catch(e1) {
|
|
335
|
-
try { result.clip.setDisabled(${args.enabled ? "false" : "true"}); } catch(e2) {}
|
|
336
|
-
}
|
|
337
|
-
return __result({ clipName: result.clip.name, enabled: ${args.enabled} });
|
|
327
|
+
const script = buildToolScript(`
|
|
328
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
329
|
+
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
330
|
+
|
|
331
|
+
// PP 2026: clip.disabled is a property, clip.setDisabled() does not exist.
|
|
332
|
+
try {
|
|
333
|
+
result.clip.disabled = ${args.enabled ? "false" : "true"};
|
|
334
|
+
} catch(e1) {
|
|
335
|
+
try { result.clip.setDisabled(${args.enabled ? "false" : "true"}); } catch(e2) {}
|
|
336
|
+
}
|
|
337
|
+
return __result({ clipName: result.clip.name, enabled: ${args.enabled} });
|
|
338
338
|
`);
|
|
339
339
|
return sendCommand(script, bridgeOptions);
|
|
340
340
|
},
|
|
@@ -376,68 +376,68 @@ Example: split_clip({ time_seconds: 15.5, track_index: 1, track_type: "video" })
|
|
|
376
376
|
required: ["node_id"],
|
|
377
377
|
},
|
|
378
378
|
handler: async (args) => {
|
|
379
|
-
const script = buildToolScript(`
|
|
380
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
381
|
-
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
382
|
-
|
|
383
|
-
var clip = result.clip;
|
|
384
|
-
var changes = {};
|
|
385
|
-
|
|
386
|
-
${args.opacity !== undefined ? `
|
|
387
|
-
// Set opacity via the intrinsic Opacity component (locale-independent)
|
|
388
|
-
var opacityComp = __findIntrinsic(clip, "opacity");
|
|
389
|
-
if (opacityComp) {
|
|
390
|
-
var opacityProp = __findProp(opacityComp, ["Opacity", "Opacidad", "Deckkraft", "Opacité"]);
|
|
391
|
-
if (opacityProp) {
|
|
392
|
-
opacityProp.setValue(${args.opacity}, true);
|
|
393
|
-
changes.opacity = ${args.opacity};
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
` : ""}
|
|
397
|
-
|
|
398
|
-
${args.speed !== undefined ? `
|
|
399
|
-
// PP 2026 setSpeed: (percent, frameInterp, ripple, reverse, maintainAudioPitch).
|
|
400
|
-
app.enableQE();
|
|
401
|
-
var qeSeq = qe.project.getActiveSequence();
|
|
402
|
-
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
403
|
-
var qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
404
|
-
var speedOk = false;
|
|
405
|
-
try { qeClip.setSpeed(${args.speed * 100}, "", false, false, false); speedOk = true; } catch (e1) {}
|
|
406
|
-
if (!speedOk) { try { qeClip.setSpeed(${args.speed * 100}, false, false); speedOk = true; } catch (e2) {} }
|
|
407
|
-
if (!speedOk) { try { qeClip.setSpeed(${args.speed * 100}); speedOk = true; } catch (e3) {} }
|
|
408
|
-
if (speedOk) { changes.speed = ${args.speed}; } else { changes.speedError = "setSpeed failed for all known signatures"; }
|
|
409
|
-
` : ""}
|
|
410
|
-
|
|
411
|
-
${args.scale !== undefined || args.position_x !== undefined || args.position_y !== undefined || args.rotation !== undefined ? `
|
|
412
|
-
// Set transform via the intrinsic Motion component (locale-independent)
|
|
413
|
-
var motionComp = __findIntrinsic(clip, "motion");
|
|
414
|
-
if (motionComp) {
|
|
415
|
-
${args.scale !== undefined ? `
|
|
416
|
-
var scaleProp = __findProp(motionComp, ["Scale", "Escala", "Skalierung", "Échelle"]);
|
|
417
|
-
if (scaleProp) {
|
|
418
|
-
scaleProp.setValue(${args.scale}, true);
|
|
419
|
-
changes.scale = ${args.scale};
|
|
420
|
-
}` : ""}
|
|
421
|
-
${args.position_x !== undefined || args.position_y !== undefined ? `
|
|
422
|
-
var posProp = __findProp(motionComp, ["Position", "Posición", "Position"]);
|
|
423
|
-
if (posProp) {
|
|
424
|
-
var posVal = posProp.getValue();
|
|
425
|
-
var px = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[0] : 0;
|
|
426
|
-
var py = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[1] : 0;
|
|
427
|
-
${args.position_x !== undefined ? `px = ${args.position_x}; changes.position_x = ${args.position_x};` : ""}
|
|
428
|
-
${args.position_y !== undefined ? `py = ${args.position_y}; changes.position_y = ${args.position_y};` : ""}
|
|
429
|
-
posProp.setValue([px, py], true);
|
|
430
|
-
}` : ""}
|
|
431
|
-
${args.rotation !== undefined ? `
|
|
432
|
-
var rotProp = __findProp(motionComp, ["Rotation", "Rotación", "Drehung"]);
|
|
433
|
-
if (rotProp) {
|
|
434
|
-
rotProp.setValue(${args.rotation}, true);
|
|
435
|
-
changes.rotation = ${args.rotation};
|
|
436
|
-
}` : ""}
|
|
437
|
-
}
|
|
438
|
-
` : ""}
|
|
439
|
-
|
|
440
|
-
return __result({ updated: true, clipName: clip.name, changes: changes });
|
|
379
|
+
const script = buildToolScript(`
|
|
380
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
381
|
+
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
382
|
+
|
|
383
|
+
var clip = result.clip;
|
|
384
|
+
var changes = {};
|
|
385
|
+
|
|
386
|
+
${args.opacity !== undefined ? `
|
|
387
|
+
// Set opacity via the intrinsic Opacity component (locale-independent)
|
|
388
|
+
var opacityComp = __findIntrinsic(clip, "opacity");
|
|
389
|
+
if (opacityComp) {
|
|
390
|
+
var opacityProp = __findProp(opacityComp, ["Opacity", "Opacidad", "Deckkraft", "Opacité"]);
|
|
391
|
+
if (opacityProp) {
|
|
392
|
+
opacityProp.setValue(${args.opacity}, true);
|
|
393
|
+
changes.opacity = ${args.opacity};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
` : ""}
|
|
397
|
+
|
|
398
|
+
${args.speed !== undefined ? `
|
|
399
|
+
// PP 2026 setSpeed: (percent, frameInterp, ripple, reverse, maintainAudioPitch).
|
|
400
|
+
app.enableQE();
|
|
401
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
402
|
+
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
403
|
+
var qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
404
|
+
var speedOk = false;
|
|
405
|
+
try { qeClip.setSpeed(${args.speed * 100}, "", false, false, false); speedOk = true; } catch (e1) {}
|
|
406
|
+
if (!speedOk) { try { qeClip.setSpeed(${args.speed * 100}, false, false); speedOk = true; } catch (e2) {} }
|
|
407
|
+
if (!speedOk) { try { qeClip.setSpeed(${args.speed * 100}); speedOk = true; } catch (e3) {} }
|
|
408
|
+
if (speedOk) { changes.speed = ${args.speed}; } else { changes.speedError = "setSpeed failed for all known signatures"; }
|
|
409
|
+
` : ""}
|
|
410
|
+
|
|
411
|
+
${args.scale !== undefined || args.position_x !== undefined || args.position_y !== undefined || args.rotation !== undefined ? `
|
|
412
|
+
// Set transform via the intrinsic Motion component (locale-independent)
|
|
413
|
+
var motionComp = __findIntrinsic(clip, "motion");
|
|
414
|
+
if (motionComp) {
|
|
415
|
+
${args.scale !== undefined ? `
|
|
416
|
+
var scaleProp = __findProp(motionComp, ["Scale", "Escala", "Skalierung", "Échelle"]);
|
|
417
|
+
if (scaleProp) {
|
|
418
|
+
scaleProp.setValue(${args.scale}, true);
|
|
419
|
+
changes.scale = ${args.scale};
|
|
420
|
+
}` : ""}
|
|
421
|
+
${args.position_x !== undefined || args.position_y !== undefined ? `
|
|
422
|
+
var posProp = __findProp(motionComp, ["Position", "Posición", "Position"]);
|
|
423
|
+
if (posProp) {
|
|
424
|
+
var posVal = posProp.getValue();
|
|
425
|
+
var px = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[0] : 0;
|
|
426
|
+
var py = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[1] : 0;
|
|
427
|
+
${args.position_x !== undefined ? `px = ${args.position_x}; changes.position_x = ${args.position_x};` : ""}
|
|
428
|
+
${args.position_y !== undefined ? `py = ${args.position_y}; changes.position_y = ${args.position_y};` : ""}
|
|
429
|
+
posProp.setValue([px, py], true);
|
|
430
|
+
}` : ""}
|
|
431
|
+
${args.rotation !== undefined ? `
|
|
432
|
+
var rotProp = __findProp(motionComp, ["Rotation", "Rotación", "Drehung"]);
|
|
433
|
+
if (rotProp) {
|
|
434
|
+
rotProp.setValue(${args.rotation}, true);
|
|
435
|
+
changes.rotation = ${args.rotation};
|
|
436
|
+
}` : ""}
|
|
437
|
+
}
|
|
438
|
+
` : ""}
|
|
439
|
+
|
|
440
|
+
return __result({ updated: true, clipName: clip.name, changes: changes });
|
|
441
441
|
`);
|
|
442
442
|
return sendCommand(script, bridgeOptions);
|
|
443
443
|
},
|
|
@@ -459,59 +459,59 @@ Example: split_clip({ time_seconds: 15.5, track_index: 1, track_type: "video" })
|
|
|
459
459
|
required: ["node_id", "new_item_id"],
|
|
460
460
|
},
|
|
461
461
|
handler: async (args) => {
|
|
462
|
-
const script = buildToolScript(`
|
|
463
|
-
var seq = app.project.activeSequence;
|
|
464
|
-
if (!seq) return __error("No active sequence");
|
|
465
|
-
|
|
466
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
467
|
-
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
468
|
-
|
|
469
|
-
var newItem = __findProjectItem("${escapeForExtendScript(args.new_item_id)}");
|
|
470
|
-
if (!newItem) return __error("Replacement project item not found: ${escapeForExtendScript(args.new_item_id)}");
|
|
471
|
-
|
|
472
|
-
var clip = result.clip;
|
|
473
|
-
var oldName = clip.name;
|
|
474
|
-
var startTicks = clip.start.ticks;
|
|
475
|
-
var trackIndex = result.trackIndex;
|
|
476
|
-
var trackType = result.trackType;
|
|
477
|
-
|
|
478
|
-
// Remove old clip
|
|
479
|
-
clip.remove(false, false);
|
|
480
|
-
|
|
481
|
-
// Insert new clip at same position
|
|
482
|
-
if (trackType === "video") {
|
|
483
|
-
seq.insertClip(newItem, startTicks, trackIndex, trackIndex);
|
|
484
|
-
} else {
|
|
485
|
-
seq.insertClip(newItem, startTicks, 0, trackIndex);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return __result({
|
|
489
|
-
replaced: true,
|
|
490
|
-
oldClip: oldName,
|
|
491
|
-
newClip: newItem.name,
|
|
492
|
-
trackIndex: trackIndex,
|
|
493
|
-
trackType: trackType
|
|
494
|
-
});
|
|
462
|
+
const script = buildToolScript(`
|
|
463
|
+
var seq = app.project.activeSequence;
|
|
464
|
+
if (!seq) return __error("No active sequence");
|
|
465
|
+
|
|
466
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
467
|
+
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
|
468
|
+
|
|
469
|
+
var newItem = __findProjectItem("${escapeForExtendScript(args.new_item_id)}");
|
|
470
|
+
if (!newItem) return __error("Replacement project item not found: ${escapeForExtendScript(args.new_item_id)}");
|
|
471
|
+
|
|
472
|
+
var clip = result.clip;
|
|
473
|
+
var oldName = clip.name;
|
|
474
|
+
var startTicks = clip.start.ticks;
|
|
475
|
+
var trackIndex = result.trackIndex;
|
|
476
|
+
var trackType = result.trackType;
|
|
477
|
+
|
|
478
|
+
// Remove old clip
|
|
479
|
+
clip.remove(false, false);
|
|
480
|
+
|
|
481
|
+
// Insert new clip at same position
|
|
482
|
+
if (trackType === "video") {
|
|
483
|
+
seq.insertClip(newItem, startTicks, trackIndex, trackIndex);
|
|
484
|
+
} else {
|
|
485
|
+
seq.insertClip(newItem, startTicks, 0, trackIndex);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return __result({
|
|
489
|
+
replaced: true,
|
|
490
|
+
oldClip: oldName,
|
|
491
|
+
newClip: newItem.name,
|
|
492
|
+
trackIndex: trackIndex,
|
|
493
|
+
trackType: trackType
|
|
494
|
+
});
|
|
495
495
|
`);
|
|
496
496
|
return sendCommand(script, bridgeOptions);
|
|
497
497
|
},
|
|
498
498
|
},
|
|
499
499
|
speed_change: {
|
|
500
|
-
description: `Change the playback speed of a clip on the timeline.
|
|
501
|
-
|
|
502
|
-
Preconditions: Active sequence required. Clip node_id must be valid.
|
|
503
|
-
|
|
504
|
-
Speed is specified as a percentage number (NOT a multiplier):
|
|
505
|
-
- 100 = normal speed
|
|
506
|
-
- 200 = double speed (2x faster)
|
|
507
|
-
- 50 = half speed (slow motion)
|
|
508
|
-
- 25 = quarter speed
|
|
509
|
-
|
|
510
|
-
Use reverse=true to play the clip backwards (negative speed). This internally uses a negative percentage.
|
|
511
|
-
|
|
512
|
-
Note: setSpeed() expects a numeric percentage, not a string and not a decimal multiplier. Passing "50" as a string or 0.5 as a multiplier would fail.
|
|
513
|
-
|
|
514
|
-
Example: speed_change({ node_id: "abc123", speed_percent: 200 }) — 2x speed
|
|
500
|
+
description: `Change the playback speed of a clip on the timeline.
|
|
501
|
+
|
|
502
|
+
Preconditions: Active sequence required. Clip node_id must be valid.
|
|
503
|
+
|
|
504
|
+
Speed is specified as a percentage number (NOT a multiplier):
|
|
505
|
+
- 100 = normal speed
|
|
506
|
+
- 200 = double speed (2x faster)
|
|
507
|
+
- 50 = half speed (slow motion)
|
|
508
|
+
- 25 = quarter speed
|
|
509
|
+
|
|
510
|
+
Use reverse=true to play the clip backwards (negative speed). This internally uses a negative percentage.
|
|
511
|
+
|
|
512
|
+
Note: setSpeed() expects a numeric percentage, not a string and not a decimal multiplier. Passing "50" as a string or 0.5 as a multiplier would fail.
|
|
513
|
+
|
|
514
|
+
Example: speed_change({ node_id: "abc123", speed_percent: 200 }) — 2x speed
|
|
515
515
|
Example: speed_change({ node_id: "abc123", speed_percent: 50, reverse: true }) — half speed, reversed`,
|
|
516
516
|
parameters: {
|
|
517
517
|
type: "object",
|
|
@@ -532,40 +532,40 @@ Example: speed_change({ node_id: "abc123", speed_percent: 50, reverse: true })
|
|
|
532
532
|
required: ["node_id", "speed_percent"],
|
|
533
533
|
},
|
|
534
534
|
handler: async (args) => {
|
|
535
|
-
const script = buildToolScript(`
|
|
536
|
-
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
537
|
-
if (!result) return __error("Clip not found");
|
|
538
|
-
|
|
539
|
-
var clip = result.clip;
|
|
540
|
-
|
|
541
|
-
// setSpeed expects a numeric percentage (50 = 50%, NOT 0.5 and NOT the string "50").
|
|
542
|
-
// Validate and ensure it is a positive number.
|
|
543
|
-
var speedPercent = parseFloat("${args.speed_percent}");
|
|
544
|
-
if (isNaN(speedPercent) || speedPercent <= 0) {
|
|
545
|
-
return __error("Invalid speed_percent: must be a positive number (e.g. 100 = normal, 50 = half, 200 = double)");
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// qeClip.setSpeed signature in PP 2026: (percent, frameInterpolation:string,
|
|
549
|
-
// ripple:bool, reverse:bool, maintainAudioPitch:bool). Verified at runtime
|
|
550
|
-
// by exhaustive probing — older PP versions used (percent, ripple, reverse).
|
|
551
|
-
// Try PP 2026 signature first, then fall back to older 3-arg form.
|
|
552
|
-
app.enableQE();
|
|
553
|
-
var qeSeq = qe.project.getActiveSequence();
|
|
554
|
-
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
555
|
-
var qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
556
|
-
var rev = ${args.reverse ? "true" : "false"};
|
|
557
|
-
var ok = false, lastErr = "";
|
|
558
|
-
// PP 2026: (percent, frameInterpolation, ripple, reverse, maintainAudioPitch)
|
|
559
|
-
// empty string accepts default frame sampling
|
|
560
|
-
try { qeClip.setSpeed(speedPercent, "", false, rev, false); ok = true; } catch (e1) { lastErr = e1.toString(); }
|
|
561
|
-
// Older PP: (percent, ripple, reverse)
|
|
562
|
-
if (!ok) { try { qeClip.setSpeed(speedPercent, false, rev); ok = true; } catch (e2) { lastErr = e2.toString(); } }
|
|
563
|
-
if (!ok) { try { qeClip.setSpeed(speedPercent, false); ok = true; } catch (e3) { lastErr = e3.toString(); } }
|
|
564
|
-
if (!ok) {
|
|
565
|
-
return __error("speed_change failed (last error: " + lastErr + "). Workaround: select the clip and use Clip > Speed/Duration in the UI.");
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
return __result({ speedChanged: true, clipName: clip.name, speedPercent: speedPercent, reverse: ${!!args.reverse}, currentSpeed: qeClip.speed });
|
|
535
|
+
const script = buildToolScript(`
|
|
536
|
+
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
|
537
|
+
if (!result) return __error("Clip not found");
|
|
538
|
+
|
|
539
|
+
var clip = result.clip;
|
|
540
|
+
|
|
541
|
+
// setSpeed expects a numeric percentage (50 = 50%, NOT 0.5 and NOT the string "50").
|
|
542
|
+
// Validate and ensure it is a positive number.
|
|
543
|
+
var speedPercent = parseFloat("${args.speed_percent}");
|
|
544
|
+
if (isNaN(speedPercent) || speedPercent <= 0) {
|
|
545
|
+
return __error("Invalid speed_percent: must be a positive number (e.g. 100 = normal, 50 = half, 200 = double)");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// qeClip.setSpeed signature in PP 2026: (percent, frameInterpolation:string,
|
|
549
|
+
// ripple:bool, reverse:bool, maintainAudioPitch:bool). Verified at runtime
|
|
550
|
+
// by exhaustive probing — older PP versions used (percent, ripple, reverse).
|
|
551
|
+
// Try PP 2026 signature first, then fall back to older 3-arg form.
|
|
552
|
+
app.enableQE();
|
|
553
|
+
var qeSeq = qe.project.getActiveSequence();
|
|
554
|
+
var qeTrack = result.trackType === "video" ? qeSeq.getVideoTrackAt(result.trackIndex) : qeSeq.getAudioTrackAt(result.trackIndex);
|
|
555
|
+
var qeClip = qeTrack.getItemAt(result.clipIndex);
|
|
556
|
+
var rev = ${args.reverse ? "true" : "false"};
|
|
557
|
+
var ok = false, lastErr = "";
|
|
558
|
+
// PP 2026: (percent, frameInterpolation, ripple, reverse, maintainAudioPitch)
|
|
559
|
+
// empty string accepts default frame sampling
|
|
560
|
+
try { qeClip.setSpeed(speedPercent, "", false, rev, false); ok = true; } catch (e1) { lastErr = e1.toString(); }
|
|
561
|
+
// Older PP: (percent, ripple, reverse)
|
|
562
|
+
if (!ok) { try { qeClip.setSpeed(speedPercent, false, rev); ok = true; } catch (e2) { lastErr = e2.toString(); } }
|
|
563
|
+
if (!ok) { try { qeClip.setSpeed(speedPercent, false); ok = true; } catch (e3) { lastErr = e3.toString(); } }
|
|
564
|
+
if (!ok) {
|
|
565
|
+
return __error("speed_change failed (last error: " + lastErr + "). Workaround: select the clip and use Clip > Speed/Duration in the UI.");
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return __result({ speedChanged: true, clipName: clip.name, speedPercent: speedPercent, reverse: ${!!args.reverse}, currentSpeed: qeClip.speed });
|
|
569
569
|
`);
|
|
570
570
|
return sendCommand(script, bridgeOptions);
|
|
571
571
|
},
|