@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
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface BridgeOptions {
|
|
2
|
+
tempDir?: string;
|
|
3
|
+
timeoutMs?: number;
|
|
4
|
+
}
|
|
5
|
+
export interface CommandResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
data?: unknown;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function getTempDir(options?: BridgeOptions): string;
|
|
11
|
+
/**
|
|
12
|
+
* Send a command (ExtendScript) to the CEP plugin and wait for a response.
|
|
13
|
+
*
|
|
14
|
+
* Protocol:
|
|
15
|
+
* 1. Write script to <tempDir>/cmd_<id>.jsx
|
|
16
|
+
* 2. CEP plugin picks it up, executes, writes result to <tempDir>/res_<id>.json
|
|
17
|
+
* 3. We poll for the response file and parse it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sendCommand(script: string, options?: BridgeOptions): Promise<CommandResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Send a raw/custom ExtendScript allowing all patterns (for LLM-authored scripts).
|
|
22
|
+
* Still enforces size limit. The script should already include helpers via buildToolScript.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sendRawCommand(script: string, options?: BridgeOptions): Promise<CommandResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Clean up any stale command/response files from the temp directory.
|
|
27
|
+
*/
|
|
28
|
+
export declare function cleanupTempDir(options?: BridgeOptions): void;
|
|
29
|
+
//# sourceMappingURL=file-bridge.d.ts.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, readFileSync, unlinkSync, existsSync, readdirSync, renameSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
const DEFAULT_TEMP_DIR = join(tmpdir(), "premiere-mcp-bridge");
|
|
5
|
+
const POLL_INTERVAL_MS = 100;
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
7
|
+
let commandCounter = 0;
|
|
8
|
+
function ensureDir(dir) {
|
|
9
|
+
if (!existsSync(dir)) {
|
|
10
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function getTempDir(options) {
|
|
14
|
+
return options?.tempDir || process.env.PREMIERE_TEMP_DIR || DEFAULT_TEMP_DIR;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Send a command (ExtendScript) to the CEP plugin and wait for a response.
|
|
18
|
+
*
|
|
19
|
+
* Protocol:
|
|
20
|
+
* 1. Write script to <tempDir>/cmd_<id>.jsx
|
|
21
|
+
* 2. CEP plugin picks it up, executes, writes result to <tempDir>/res_<id>.json
|
|
22
|
+
* 3. We poll for the response file and parse it.
|
|
23
|
+
*/
|
|
24
|
+
export async function sendCommand(script, options) {
|
|
25
|
+
const tempDir = getTempDir(options);
|
|
26
|
+
const timeoutMs = options?.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
27
|
+
ensureDir(tempDir);
|
|
28
|
+
const id = `${Date.now()}_${++commandCounter}`;
|
|
29
|
+
const cmdFile = join(tempDir, `cmd_${id}.jsx`);
|
|
30
|
+
const resFile = join(tempDir, `res_${id}.json`);
|
|
31
|
+
// Validate script
|
|
32
|
+
validateScript(script);
|
|
33
|
+
// Write command file atomically: write to a temp name the CEP poller ignores
|
|
34
|
+
// (it only matches the `cmd_` prefix), then rename into place. This prevents
|
|
35
|
+
// the plugin from reading a half-written command file.
|
|
36
|
+
const cmdTmp = join(tempDir, `tmp_${id}.jsx`);
|
|
37
|
+
writeFileSync(cmdTmp, script, "utf-8");
|
|
38
|
+
renameSync(cmdTmp, cmdFile);
|
|
39
|
+
// Poll for response
|
|
40
|
+
const result = await pollForResponse(resFile, timeoutMs);
|
|
41
|
+
// Cleanup
|
|
42
|
+
safeUnlink(cmdFile);
|
|
43
|
+
safeUnlink(resFile);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
function validateScript(script, allowUnsafe = false) {
|
|
47
|
+
const MAX_SCRIPT_SIZE = 500 * 1024; // 500KB
|
|
48
|
+
if (Buffer.byteLength(script, "utf-8") > MAX_SCRIPT_SIZE) {
|
|
49
|
+
throw new Error("Script exceeds 500KB size limit");
|
|
50
|
+
}
|
|
51
|
+
if (allowUnsafe)
|
|
52
|
+
return;
|
|
53
|
+
// Block dangerous patterns in user-provided parameters
|
|
54
|
+
// Note: we don't block these in our own generated code, only check for injection
|
|
55
|
+
const dangerousPatterns = [
|
|
56
|
+
/\beval\s*\(/,
|
|
57
|
+
/\bnew\s+Function\s*\(/,
|
|
58
|
+
/\bSystem\s*\.\s*callSystem\s*\(/,
|
|
59
|
+
];
|
|
60
|
+
for (const pattern of dangerousPatterns) {
|
|
61
|
+
if (pattern.test(script)) {
|
|
62
|
+
throw new Error(`Script contains blocked pattern: ${pattern.source}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Send a raw/custom ExtendScript allowing all patterns (for LLM-authored scripts).
|
|
68
|
+
* Still enforces size limit. The script should already include helpers via buildToolScript.
|
|
69
|
+
*/
|
|
70
|
+
export async function sendRawCommand(script, options) {
|
|
71
|
+
const tempDir = getTempDir(options);
|
|
72
|
+
const timeoutMs = options?.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
73
|
+
ensureDir(tempDir);
|
|
74
|
+
const id = `${Date.now()}_${++commandCounter}`;
|
|
75
|
+
const cmdFile = join(tempDir, `cmd_${id}.jsx`);
|
|
76
|
+
const resFile = join(tempDir, `res_${id}.json`);
|
|
77
|
+
validateScript(script, true);
|
|
78
|
+
const cmdTmp = join(tempDir, `tmp_${id}.jsx`);
|
|
79
|
+
writeFileSync(cmdTmp, script, "utf-8");
|
|
80
|
+
renameSync(cmdTmp, cmdFile);
|
|
81
|
+
const result = await pollForResponse(resFile, timeoutMs);
|
|
82
|
+
safeUnlink(cmdFile);
|
|
83
|
+
safeUnlink(resFile);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
async function pollForResponse(resFile, timeoutMs) {
|
|
87
|
+
const start = Date.now();
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const check = () => {
|
|
90
|
+
if (existsSync(resFile)) {
|
|
91
|
+
try {
|
|
92
|
+
const raw = readFileSync(resFile, "utf-8");
|
|
93
|
+
const result = JSON.parse(raw);
|
|
94
|
+
resolve(result);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
resolve({
|
|
98
|
+
success: false,
|
|
99
|
+
error: `Failed to parse response: ${e instanceof Error ? e.message : String(e)}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (Date.now() - start > timeoutMs) {
|
|
105
|
+
resolve({
|
|
106
|
+
success: false,
|
|
107
|
+
error: `Command timed out after ${timeoutMs}ms. Is the CEP plugin running in Premiere Pro?`,
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
setTimeout(check, POLL_INTERVAL_MS);
|
|
112
|
+
};
|
|
113
|
+
check();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function safeUnlink(path) {
|
|
117
|
+
try {
|
|
118
|
+
if (existsSync(path)) {
|
|
119
|
+
unlinkSync(path);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Ignore cleanup errors
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Clean up any stale command/response files from the temp directory.
|
|
128
|
+
*/
|
|
129
|
+
export function cleanupTempDir(options) {
|
|
130
|
+
const tempDir = getTempDir(options);
|
|
131
|
+
if (!existsSync(tempDir))
|
|
132
|
+
return;
|
|
133
|
+
try {
|
|
134
|
+
const files = readdirSync(tempDir);
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
if (file.startsWith("cmd_") || file.startsWith("res_") || file.startsWith("tmp_")) {
|
|
137
|
+
safeUnlink(join(tempDir, file));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Ignore cleanup errors
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=file-bridge.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds ExtendScript strings with helper functions prepended.
|
|
3
|
+
* All generated code must be ES3-compatible (var, no arrow functions, no let/const).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Build a complete ExtendScript by wrapping user code in an IIFE with helpers.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildScript(code: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Escape a string for safe embedding in ExtendScript.
|
|
11
|
+
*/
|
|
12
|
+
export declare function escapeForExtendScript(value: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Build a script that wraps code returning a value.
|
|
15
|
+
* The code should use `return __result(...)` or `return __error(...)`.
|
|
16
|
+
* @deprecated Use buildScript() directly. This is an alias kept for backward compatibility.
|
|
17
|
+
*/
|
|
18
|
+
export declare const buildToolScript: typeof buildScript;
|
|
19
|
+
//# sourceMappingURL=script-builder.d.ts.map
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds ExtendScript strings with helper functions prepended.
|
|
3
|
+
* All generated code must be ES3-compatible (var, no arrow functions, no let/const).
|
|
4
|
+
*/
|
|
5
|
+
const HELPERS = `
|
|
6
|
+
// === MCP Bridge Helpers (auto-prepended) ===
|
|
7
|
+
|
|
8
|
+
var TICKS_PER_SECOND = 254016000000;
|
|
9
|
+
|
|
10
|
+
function __ticksToSeconds(ticks) {
|
|
11
|
+
return parseFloat(ticks) / TICKS_PER_SECOND;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function __secondsToTicks(seconds) {
|
|
15
|
+
return Math.round(parseFloat(seconds) * TICKS_PER_SECOND);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function __ticksToTimecode(ticks, fps) {
|
|
19
|
+
var totalSeconds = __ticksToSeconds(ticks);
|
|
20
|
+
var hours = Math.floor(totalSeconds / 3600);
|
|
21
|
+
var minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
22
|
+
var secs = Math.floor(totalSeconds % 60);
|
|
23
|
+
var frames = Math.floor((totalSeconds % 1) * fps);
|
|
24
|
+
return __pad(hours) + ":" + __pad(minutes) + ":" + __pad(secs) + ":" + __pad(frames);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function __pad(n) {
|
|
28
|
+
return n < 10 ? "0" + n : "" + n;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function __findSequence(idOrName) {
|
|
32
|
+
var project = app.project;
|
|
33
|
+
for (var i = 0; i < project.sequences.numSequences; i++) {
|
|
34
|
+
var seq = project.sequences[i];
|
|
35
|
+
if (seq.sequenceID === idOrName || seq.name === idOrName) {
|
|
36
|
+
return seq;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function __findProjectItem(nodeIdOrName, rootItem) {
|
|
43
|
+
if (!rootItem) rootItem = app.project.rootItem;
|
|
44
|
+
for (var i = 0; i < rootItem.children.numItems; i++) {
|
|
45
|
+
var item = rootItem.children[i];
|
|
46
|
+
if (item.nodeId === nodeIdOrName || item.name === nodeIdOrName) {
|
|
47
|
+
return item;
|
|
48
|
+
}
|
|
49
|
+
if (item.type === 2) { // Bin
|
|
50
|
+
var found = __findProjectItem(nodeIdOrName, item);
|
|
51
|
+
if (found) return found;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function __findClip(nodeId) {
|
|
58
|
+
var seq = app.project.activeSequence;
|
|
59
|
+
if (!seq) return null;
|
|
60
|
+
|
|
61
|
+
// Search video tracks
|
|
62
|
+
for (var t = 0; t < seq.videoTracks.numTracks; t++) {
|
|
63
|
+
var track = seq.videoTracks[t];
|
|
64
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
65
|
+
var clip = track.clips[c];
|
|
66
|
+
if (clip.nodeId === nodeId) {
|
|
67
|
+
return { clip: clip, trackIndex: t, clipIndex: c, trackType: "video" };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Search audio tracks
|
|
73
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
74
|
+
var track = seq.audioTracks[t];
|
|
75
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
76
|
+
var clip = track.clips[c];
|
|
77
|
+
if (clip.nodeId === nodeId) {
|
|
78
|
+
return { clip: clip, trackIndex: t, clipIndex: c, trackType: "audio" };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function __getAllClips(seq) {
|
|
87
|
+
if (!seq) seq = app.project.activeSequence;
|
|
88
|
+
if (!seq) return [];
|
|
89
|
+
var clips = [];
|
|
90
|
+
|
|
91
|
+
for (var t = 0; t < seq.videoTracks.numTracks; t++) {
|
|
92
|
+
var track = seq.videoTracks[t];
|
|
93
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
94
|
+
var clip = track.clips[c];
|
|
95
|
+
clips.push({
|
|
96
|
+
nodeId: clip.nodeId,
|
|
97
|
+
name: clip.name,
|
|
98
|
+
trackIndex: t,
|
|
99
|
+
trackType: "video",
|
|
100
|
+
inPoint: __ticksToSeconds(clip.inPoint.ticks),
|
|
101
|
+
outPoint: __ticksToSeconds(clip.outPoint.ticks),
|
|
102
|
+
start: __ticksToSeconds(clip.start.ticks),
|
|
103
|
+
end: __ticksToSeconds(clip.end.ticks),
|
|
104
|
+
duration: __ticksToSeconds(clip.duration.ticks),
|
|
105
|
+
mediaType: clip.mediaType
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (var t = 0; t < seq.audioTracks.numTracks; t++) {
|
|
111
|
+
var track = seq.audioTracks[t];
|
|
112
|
+
for (var c = 0; c < track.clips.numItems; c++) {
|
|
113
|
+
var clip = track.clips[c];
|
|
114
|
+
clips.push({
|
|
115
|
+
nodeId: clip.nodeId,
|
|
116
|
+
name: clip.name,
|
|
117
|
+
trackIndex: t,
|
|
118
|
+
trackType: "audio",
|
|
119
|
+
inPoint: __ticksToSeconds(clip.inPoint.ticks),
|
|
120
|
+
outPoint: __ticksToSeconds(clip.outPoint.ticks),
|
|
121
|
+
start: __ticksToSeconds(clip.start.ticks),
|
|
122
|
+
end: __ticksToSeconds(clip.end.ticks),
|
|
123
|
+
duration: __ticksToSeconds(clip.duration.ticks),
|
|
124
|
+
mediaType: clip.mediaType
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return clips;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Recursively coerce undefined to null in an object tree so that the result
|
|
133
|
+
// can be passed to ExtendScript's JSON.stringify safely. ExtendScript's
|
|
134
|
+
// JSON.stringify (a) does not honor the 'replacer' parameter and (b) emits the
|
|
135
|
+
// bare token 'undefined' for undefined values, which is invalid JSON and
|
|
136
|
+
// crashes the CEP-side JSON.parse. Pre-sanitizing is the only reliable fix.
|
|
137
|
+
function __sanitizeForJSON(obj) {
|
|
138
|
+
if (obj === undefined || obj === null) return null;
|
|
139
|
+
// ExtendScript also emits NaN / Infinity literals which are not valid JSON.
|
|
140
|
+
// Coerce them to null so the CEP-side JSON.parse always succeeds.
|
|
141
|
+
if (typeof obj === "number") {
|
|
142
|
+
if (isNaN(obj) || obj === Infinity || obj === -Infinity) return null;
|
|
143
|
+
return obj;
|
|
144
|
+
}
|
|
145
|
+
if (typeof obj !== "object") return obj;
|
|
146
|
+
if (obj instanceof Array) {
|
|
147
|
+
var arr = [];
|
|
148
|
+
for (var i = 0; i < obj.length; i++) arr.push(__sanitizeForJSON(obj[i]));
|
|
149
|
+
return arr;
|
|
150
|
+
}
|
|
151
|
+
// Don't recurse into Premiere host objects (Time, ProjectItem, etc.) — they
|
|
152
|
+
// serialize as [object Object] and have inaccessible internals. Detect them
|
|
153
|
+
// by lack of plain-object constructor.
|
|
154
|
+
try {
|
|
155
|
+
if (obj.constructor && obj.constructor !== Object && obj.constructor !== Array) {
|
|
156
|
+
// For host objects, fall back to their toString / accessible primitive
|
|
157
|
+
return obj.toString ? String(obj.toString()) : "[object]";
|
|
158
|
+
}
|
|
159
|
+
} catch (e) { /* fall through */ }
|
|
160
|
+
var out = {};
|
|
161
|
+
for (var k in obj) {
|
|
162
|
+
if (obj.hasOwnProperty(k)) out[k] = __sanitizeForJSON(obj[k]);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function __jsonStringify(obj) {
|
|
168
|
+
// ES3-compatible JSON stringify. ExtendScript's JSON.stringify emits the
|
|
169
|
+
// literal token 'undefined' for undefined values (vs Node/Browser which omit
|
|
170
|
+
// them) AND ignores the replacer parameter, making the produced string
|
|
171
|
+
// invalid JSON and crashing the CEP-side JSON.parse. Sanitize first.
|
|
172
|
+
if (typeof JSON !== "undefined" && JSON.stringify) {
|
|
173
|
+
return JSON.stringify(__sanitizeForJSON(obj));
|
|
174
|
+
}
|
|
175
|
+
// Fallback for very old ExtendScript
|
|
176
|
+
if (obj === null) return "null";
|
|
177
|
+
if (obj === undefined) return "undefined";
|
|
178
|
+
if (typeof obj === "string") return '"' + obj.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"').replace(/\\n/g, "\\\\n") + '"';
|
|
179
|
+
if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
|
|
180
|
+
if (obj instanceof Array) {
|
|
181
|
+
var arr = [];
|
|
182
|
+
for (var i = 0; i < obj.length; i++) {
|
|
183
|
+
arr.push(__jsonStringify(obj[i]));
|
|
184
|
+
}
|
|
185
|
+
return "[" + arr.join(",") + "]";
|
|
186
|
+
}
|
|
187
|
+
if (typeof obj === "object") {
|
|
188
|
+
var parts = [];
|
|
189
|
+
for (var k in obj) {
|
|
190
|
+
if (obj.hasOwnProperty(k)) {
|
|
191
|
+
parts.push(__jsonStringify(k) + ":" + __jsonStringify(obj[k]));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return "{" + parts.join(",") + "}";
|
|
195
|
+
}
|
|
196
|
+
return String(obj);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function __result(data) {
|
|
200
|
+
return __jsonStringify({ success: true, data: data });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function __error(msg) {
|
|
204
|
+
return __jsonStringify({ success: false, error: String(msg) });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- Locale & version resilient lookups -------------------------------------
|
|
208
|
+
|
|
209
|
+
// Candidate matchNames for Premiere's intrinsic (fixed) effects. matchName is
|
|
210
|
+
// locale-INDEPENDENT, so trying these first makes tools work on any language
|
|
211
|
+
// install. displayName fallbacks are language-specific and only a last resort.
|
|
212
|
+
var __INTRINSIC = {
|
|
213
|
+
motion: { match: ["AE.ADBE Motion", "ADBE Motion", "motionControlEffect"], display: ["Motion", "Movimiento", "Bewegung", "Mouvement"] },
|
|
214
|
+
opacity: { match: ["AE.ADBE Opacity", "ADBE Opacity", "opacityEffect"], display: ["Opacity", "Opacidad", "Deckkraft", "Opacité"] },
|
|
215
|
+
volume: { match: ["AE.ADBE Audio Levels", "audioVolume", "audioGain", "ADBE Audio Levels"], display: ["Volume", "Volumen", "Lautstärke"] },
|
|
216
|
+
panner: { match: ["AE.ADBE Pan", "audioPanner", "ADBE Pan"], display: ["Panner", "Panoramización", "Balance"] },
|
|
217
|
+
lumetri: { match: ["AE.ADBE Lumetri", "ADBE Lumetri", "PR.ADBE Lumetri"], display: ["Lumetri Color", "Color Lumetri", "Lumetri-Farbe"] },
|
|
218
|
+
timeRemap:{ match: ["AE.ADBE Time Remapping", "ADBE Time Remapping"], display: ["Time Remapping", "Reasignación de tiempo"] }
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Find a component on a clip by trying matchName candidates then displayName candidates.
|
|
222
|
+
// matchNames/displayNames are arrays of strings. Returns the component or null.
|
|
223
|
+
function __findComponent(clip, matchNames, displayNames) {
|
|
224
|
+
if (!clip || !clip.components) return null;
|
|
225
|
+
var i, c, k;
|
|
226
|
+
if (matchNames) {
|
|
227
|
+
for (i = 0; i < clip.components.numItems; i++) {
|
|
228
|
+
c = clip.components[i];
|
|
229
|
+
for (k = 0; k < matchNames.length; k++) {
|
|
230
|
+
if (c.matchName === matchNames[k]) return c;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (displayNames) {
|
|
235
|
+
for (i = 0; i < clip.components.numItems; i++) {
|
|
236
|
+
c = clip.components[i];
|
|
237
|
+
for (k = 0; k < displayNames.length; k++) {
|
|
238
|
+
if (c.displayName === displayNames[k]) return c;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Shorthand: find an intrinsic component by key from the __INTRINSIC table.
|
|
246
|
+
function __findIntrinsic(clip, key) {
|
|
247
|
+
var def = __INTRINSIC[key];
|
|
248
|
+
if (!def) return null;
|
|
249
|
+
return __findComponent(clip, def.match, def.display);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Find a property inside a component by displayName candidates, descending one
|
|
253
|
+
// level into nested property groups (needed for Lumetri's grouped params).
|
|
254
|
+
function __findProp(comp, names) {
|
|
255
|
+
if (!comp || !comp.properties) return null;
|
|
256
|
+
var p, n, prop, sp, sub;
|
|
257
|
+
for (p = 0; p < comp.properties.numItems; p++) {
|
|
258
|
+
prop = comp.properties[p];
|
|
259
|
+
for (n = 0; n < names.length; n++) {
|
|
260
|
+
if (prop.displayName === names[n]) return prop;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// second pass: nested groups
|
|
264
|
+
for (p = 0; p < comp.properties.numItems; p++) {
|
|
265
|
+
prop = comp.properties[p];
|
|
266
|
+
if (prop.properties && prop.properties.numItems) {
|
|
267
|
+
for (sp = 0; sp < prop.properties.numItems; sp++) {
|
|
268
|
+
sub = prop.properties[sp];
|
|
269
|
+
for (n = 0; n < names.length; n++) {
|
|
270
|
+
if (sub.displayName === names[n]) return sub;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Locale-aware aliases for common Premiere Pro transition names. matchName
|
|
279
|
+
// would be ideal but QE's transition objects don't reliably expose it, so we
|
|
280
|
+
// map the English canonical name to localized displayNames seen in the wild.
|
|
281
|
+
var __TRANSITION_ALIASES = {
|
|
282
|
+
"Cross Dissolve": ["Cross Dissolve", "Disolvencia cruzada", "Fondu enchaîné", "Weiche Blende"],
|
|
283
|
+
"Dip to Black": ["Dip to Black", "Inmersión a negro", "Fondu au noir", "Schwarzblende"],
|
|
284
|
+
"Dip to White": ["Dip to White", "Inmersión a blanco", "Fondu au blanc", "Weißblende"],
|
|
285
|
+
"Film Dissolve": ["Film Dissolve", "Disolvencia de película"],
|
|
286
|
+
"Additive Dissolve": ["Additive Dissolve", "Disolvencia aditiva"],
|
|
287
|
+
"Morph Cut": ["Morph Cut", "Corte por transformación"],
|
|
288
|
+
"Constant Power": ["Constant Power", "Potencia constante"],
|
|
289
|
+
"Constant Gain": ["Constant Gain", "Ganancia constante"],
|
|
290
|
+
"Exponential Fade": ["Exponential Fade", "Fundido exponencial"]
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// Resolve a transition by name from a QE transition list, locale-resilient.
|
|
294
|
+
// First tries qe.project.getVideoTransitionByName / getAudioTransitionByName,
|
|
295
|
+
// then iterates the list matching the requested name OR any localized alias.
|
|
296
|
+
// kind: "video" | "audio". Returns the QE transition object or null.
|
|
297
|
+
function __findTransition(name, kind) {
|
|
298
|
+
try { app.enableQE(); } catch (e) {}
|
|
299
|
+
if (typeof qe === "undefined" || !qe.project) return null;
|
|
300
|
+
var direct = null;
|
|
301
|
+
try {
|
|
302
|
+
if (kind === "audio" && qe.project.getAudioTransitionByName) {
|
|
303
|
+
direct = qe.project.getAudioTransitionByName(name);
|
|
304
|
+
} else if (qe.project.getVideoTransitionByName) {
|
|
305
|
+
direct = qe.project.getVideoTransitionByName(name);
|
|
306
|
+
}
|
|
307
|
+
} catch (e) {}
|
|
308
|
+
if (direct) return direct;
|
|
309
|
+
var list = null;
|
|
310
|
+
try {
|
|
311
|
+
list = (kind === "audio") ? qe.project.getAudioTransitionList() : qe.project.getVideoTransitionList();
|
|
312
|
+
} catch (e) { list = null; }
|
|
313
|
+
if (!list || !list.numItems) return null;
|
|
314
|
+
// Candidate names: the requested name, plus aliases for it (forward map),
|
|
315
|
+
// plus the canonical name if the request matches any alias (reverse map).
|
|
316
|
+
var candidates = [name];
|
|
317
|
+
var k, j, aliases;
|
|
318
|
+
if (__TRANSITION_ALIASES[name]) {
|
|
319
|
+
aliases = __TRANSITION_ALIASES[name];
|
|
320
|
+
for (j = 0; j < aliases.length; j++) candidates.push(aliases[j]);
|
|
321
|
+
}
|
|
322
|
+
for (k in __TRANSITION_ALIASES) {
|
|
323
|
+
if (__TRANSITION_ALIASES.hasOwnProperty(k)) {
|
|
324
|
+
aliases = __TRANSITION_ALIASES[k];
|
|
325
|
+
for (j = 0; j < aliases.length; j++) {
|
|
326
|
+
if (aliases[j] === name) {
|
|
327
|
+
candidates.push(k);
|
|
328
|
+
for (var jj = 0; jj < aliases.length; jj++) candidates.push(aliases[jj]);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (var i = 0; i < list.numItems; i++) {
|
|
334
|
+
var t = list[i];
|
|
335
|
+
var tName = t.name;
|
|
336
|
+
var tMatch = t.matchName;
|
|
337
|
+
for (var c = 0; c < candidates.length; c++) {
|
|
338
|
+
if (tName === candidates[c]) return t;
|
|
339
|
+
if (tMatch && tMatch === candidates[c]) return t;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Resolve an ID that could be either a project item nodeId/name OR a timeline
|
|
346
|
+
// clip nodeId, to a ProjectItem. Used by tools whose schema says "project item
|
|
347
|
+
// node_id or name" but where callers commonly pass timeline clip nodeIds
|
|
348
|
+
// (which come from get_active_sequence / list_sequence_tracks).
|
|
349
|
+
// Returns the ProjectItem or null.
|
|
350
|
+
function __resolveProjectItemOrTimelineClip(idOrName) {
|
|
351
|
+
if (!idOrName) return null;
|
|
352
|
+
// First try the project tree
|
|
353
|
+
var pi = __findProjectItem(idOrName);
|
|
354
|
+
if (pi) return pi;
|
|
355
|
+
// Fall back: treat as timeline clip nodeId, resolve to its projectItem
|
|
356
|
+
try {
|
|
357
|
+
var r = __findClip(idOrName);
|
|
358
|
+
if (r && r.clip && r.clip.projectItem) return r.clip.projectItem;
|
|
359
|
+
} catch (e) {}
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Read a member that some Premiere versions expose as a property and others as
|
|
364
|
+
// a zero-arg method (e.g. isMuted, isSelected, isOffline, isSequence). Returns
|
|
365
|
+
// the resolved value, or the provided default if it cannot be read.
|
|
366
|
+
function __readMaybe(obj, name, dflt) {
|
|
367
|
+
try {
|
|
368
|
+
var v = obj[name];
|
|
369
|
+
if (typeof v === "function") return v.call(obj);
|
|
370
|
+
if (v === undefined) return dflt;
|
|
371
|
+
return v;
|
|
372
|
+
} catch(e) { return dflt; }
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// === End MCP Bridge Helpers ===
|
|
376
|
+
`;
|
|
377
|
+
/**
|
|
378
|
+
* Build a complete ExtendScript by wrapping user code in an IIFE with helpers.
|
|
379
|
+
*/
|
|
380
|
+
export function buildScript(code) {
|
|
381
|
+
return `${HELPERS}
|
|
382
|
+
(function() {
|
|
383
|
+
try {
|
|
384
|
+
${code}
|
|
385
|
+
} catch(e) {
|
|
386
|
+
return __error(e.toString());
|
|
387
|
+
}
|
|
388
|
+
})();`;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Escape a string for safe embedding in ExtendScript.
|
|
392
|
+
*/
|
|
393
|
+
export function escapeForExtendScript(value) {
|
|
394
|
+
return value
|
|
395
|
+
.replace(/\\/g, "\\\\")
|
|
396
|
+
.replace(/"/g, '\\"')
|
|
397
|
+
.replace(/'/g, "\\'")
|
|
398
|
+
.replace(/\n/g, "\\n")
|
|
399
|
+
.replace(/\r/g, "\\r")
|
|
400
|
+
.replace(/\t/g, "\\t");
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Build a script that wraps code returning a value.
|
|
404
|
+
* The code should use `return __result(...)` or `return __error(...)`.
|
|
405
|
+
* @deprecated Use buildScript() directly. This is an alias kept for backward compatibility.
|
|
406
|
+
*/
|
|
407
|
+
export const buildToolScript = buildScript;
|
|
408
|
+
//# sourceMappingURL=script-builder.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* HTTP/SSE transport entry point for remote deployment (e.g. Fly.io).
|
|
4
|
+
*
|
|
5
|
+
* The MCP server is identical to the stdio version — only the transport differs.
|
|
6
|
+
* Clients connect via the MCP Streamable HTTP transport:
|
|
7
|
+
* POST /mcp — send JSON-RPC messages
|
|
8
|
+
* GET /mcp — open SSE stream
|
|
9
|
+
*
|
|
10
|
+
* The bridge still uses the local filesystem temp directory, so the CEP plugin
|
|
11
|
+
* must be reachable from the same machine OR you must set PREMIERE_TEMP_DIR to
|
|
12
|
+
* a shared volume mount that the CEP plugin also writes to.
|
|
13
|
+
*
|
|
14
|
+
* Environment variables:
|
|
15
|
+
* PORT HTTP port to listen on (default: 3000)
|
|
16
|
+
* PREMIERE_TEMP_DIR Shared temp directory for the file bridge
|
|
17
|
+
* PREMIERE_TIMEOUT_MS Command timeout in ms (default: 30000)
|
|
18
|
+
* MCP_AUTH_TOKEN Optional bearer token to require on all requests
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=http-server.d.ts.map
|