Files
jhonny-editor/code/src/tools/tracks.ts
T
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

205 lines
8.3 KiB
TypeScript
Executable File

import { buildToolScript, escapeForExtendScript } from "../bridge/script-builder.js";
import { sendCommand, BridgeOptions } from "../bridge/file-bridge.js";
function isPositiveInteger(value: unknown): value is number {
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
}
export function getTrackTools(bridgeOptions: BridgeOptions) {
return {
add_track: {
description:
"Add verified video or audio tracks to the active sequence. Returns an error if Premiere cannot add the exact requested count.",
parameters: {
type: "object" as const,
properties: {
track_type: {
type: "string",
enum: ["video", "audio"],
description: "Type of track to add",
},
count: {
type: "number",
description: "Number of tracks to add (default: 1)",
},
},
required: ["track_type"],
},
handler: async (args: { track_type: string; count?: number }) => {
const count = args.count ?? 1;
if (args.track_type !== "video" && args.track_type !== "audio") {
return { success: false, error: "track_type must be either video or audio" };
}
if (!isPositiveInteger(count)) {
return { success: false, error: "count must be a positive integer" };
}
const isVideo = args.track_type === "video";
const script = buildToolScript(`
var seq = app.project.activeSequence;
if (!seq) return __error("No active sequence");
var before = ${isVideo ? "seq.videoTracks.numTracks" : "seq.audioTracks.numTracks"};
var expected = before + ${count};
var method = "public DOM";
var publicFailure = "";
// Premiere 26.x can expose this public Sequence API but reject the
// call. Do not report the request as successful until the DOM count
// proves it happened. If the public call made no change, QE is a
// bounded fallback; a partially applied public call is never retried.
try {
if (typeof seq.${isVideo ? "insertVideoTrackAt" : "insertAudioTrackAt"} !== "function") {
publicFailure = "Sequence.${isVideo ? "insertVideoTrackAt" : "insertAudioTrackAt"} is unavailable";
} else {
${isVideo
? `seq.insertVideoTrackAt(before, ${count});`
: `seq.insertAudioTrackAt(before, ${count});`}
}
} catch (publicError) {
publicFailure = publicError.toString();
}
var afterPublic = ${isVideo ? "seq.videoTracks.numTracks" : "seq.audioTracks.numTracks"};
if (afterPublic !== expected && afterPublic !== before) {
return __error("Track add partially applied through the public DOM: requested ${count} ${args.track_type} track(s), had " + before + ", now has " + afterPublic + ". It was not retried.");
}
if (afterPublic !== expected) {
method = "QE fallback";
if (typeof app.enableQE !== "function") {
return __error("Could not add ${args.track_type} track(s): " + publicFailure + ". QE fallback is unavailable on this Premiere build.");
}
app.enableQE();
if (typeof qe === "undefined" || !qe.project || typeof qe.project.getActiveSequence !== "function") {
return __error("Could not add ${args.track_type} track(s): " + publicFailure + ". QE active-sequence access is unavailable on this Premiere build.");
}
var qeSeq = qe.project.getActiveSequence();
if (!qeSeq || typeof qeSeq.addTracks !== "function") {
return __error("Could not add ${args.track_type} track(s): " + publicFailure + ". QE addTracks is unavailable on this Premiere build.");
}
try {
qeSeq.addTracks(${isVideo ? count : 0}, ${isVideo ? 0 : count}, 0, 0);
} catch (qeError) {
return __error("Could not add ${args.track_type} track(s): public DOM failed (" + publicFailure + ") and QE addTracks failed (" + qeError.toString() + ").");
}
}
var after = ${isVideo ? "seq.videoTracks.numTracks" : "seq.audioTracks.numTracks"};
if (after !== expected) {
return __error("Premiere did not add the requested ${args.track_type} tracks: requested ${count}, had " + before + ", now has " + after + " (" + method + ").");
}
return __result({
added: ${count},
trackType: "${args.track_type}",
totalTracks: after,
method: method,
verified: true
});
`);
return sendCommand(script, bridgeOptions);
},
},
delete_track: {
description: "Delete a video or audio track from the active sequence",
parameters: {
type: "object" as const,
properties: {
track_type: {
type: "string",
enum: ["video", "audio"],
description: "Type of track to delete",
},
track_index: {
type: "number",
description: "Index of the track to delete (0-based)",
},
},
required: ["track_type", "track_index"],
},
handler: async (args: { track_type: string; track_index: number }) => {
const script = buildToolScript(`
var seq = app.project.activeSequence;
if (!seq) return __error("No active sequence");
${args.track_type === "video"
? `if (${args.track_index} >= seq.videoTracks.numTracks) return __error("Track index out of range");
seq.deleteVideoTrackAt(${args.track_index});`
: `if (${args.track_index} >= seq.audioTracks.numTracks) return __error("Track index out of range");
seq.deleteAudioTrackAt(${args.track_index});`
}
return __result({ deleted: true, trackType: "${args.track_type}", trackIndex: ${args.track_index} });
`);
return sendCommand(script, bridgeOptions);
},
},
lock_track: {
description: "Lock or unlock a video track",
parameters: {
type: "object" as const,
properties: {
track_index: {
type: "number",
description: "Video track index (0-based)",
},
locked: {
type: "boolean",
description: "True to lock, false to unlock",
},
},
required: ["track_index", "locked"],
},
handler: async (args: { track_index: number; locked: boolean }) => {
const script = buildToolScript(`
var seq = app.project.activeSequence;
if (!seq) return __error("No active sequence");
if (${args.track_index} >= seq.videoTracks.numTracks) return __error("Track index out of range");
var track = seq.videoTracks[${args.track_index}];
track.setLocked(${args.locked ? 1 : 0});
return __result({ trackIndex: ${args.track_index}, locked: ${args.locked}, trackName: track.name });
`);
return sendCommand(script, bridgeOptions);
},
},
toggle_track_visibility: {
description: "Toggle a video track's visibility (eye icon)",
parameters: {
type: "object" as const,
properties: {
track_index: {
type: "number",
description: "Video track index (0-based)",
},
visible: {
type: "boolean",
description: "True to show, false to hide",
},
},
required: ["track_index", "visible"],
},
handler: async (args: { track_index: number; visible: boolean }) => {
const script = buildToolScript(`
var seq = app.project.activeSequence;
if (!seq) return __error("No active sequence");
if (${args.track_index} >= seq.videoTracks.numTracks) return __error("Track index out of range");
var track = seq.videoTracks[${args.track_index}];
track.setMute(${args.visible ? 0 : 1});
return __result({ trackIndex: ${args.track_index}, visible: ${args.visible}, trackName: track.name });
`);
return sendCommand(script, bridgeOptions);
},
},
};
}