From 668e7c6c244d8dc8b6c53c6cc7b3edb5d4fc322d Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Fri, 15 May 2026 21:36:15 -0400 Subject: [PATCH] fix(premiere-plugin): CSInterface init, correct API prefix, Node.js download, lazy thumbnails, proper ExtendScript export API: premiere.jsx --- services/premiere-plugin/jsx/premiere.jsx | 294 +++++++++------------- 1 file changed, 114 insertions(+), 180 deletions(-) diff --git a/services/premiere-plugin/jsx/premiere.jsx b/services/premiere-plugin/jsx/premiere.jsx index 8d8e7d7..328046a 100644 --- a/services/premiere-plugin/jsx/premiere.jsx +++ b/services/premiere-plugin/jsx/premiere.jsx @@ -1,11 +1,12 @@ /** * Wild Dragon MAM - Premiere Pro ExtendScript * - * This file contains ExtendScript functions that interact with Premiere Pro - * to import media files and manage the timeline. + * This file runs in the Premiere Pro host context (not the browser panel). + * It is registered via in manifest.xml and called from the + * panel via csInterface.evalScript(). * - * ExtendScript is Adobe's implementation of JavaScript that runs in the - * Premiere Pro host application context. + * ExtendScript is ES3-level JavaScript — no arrow functions, no const/let, + * no template literals, no destructuring. */ // ============================================================================ @@ -13,7 +14,7 @@ // ============================================================================ /** - * Imports a media file into the active Premiere Pro project + * Imports a media file into the active Premiere Pro project. * @param {string} filePath - Full path to the file to import * @returns {string} JSON string with success status and message */ @@ -25,25 +26,21 @@ function importFileToProject(filePath) { }; try { - // Check if project is open if (!app.project) { result.message = "No active Premiere Pro project"; return JSON.stringify(result); } - // Check if file exists var file = new File(filePath); if (!file.exists) { result.message = "File does not exist: " + filePath; return JSON.stringify(result); } - // Import the file into the project app.project.importFiles([filePath]); result.success = true; result.message = "File imported successfully"; - return JSON.stringify(result); } catch (error) { result.message = "Error importing file: " + error.message; @@ -52,8 +49,8 @@ function importFileToProject(filePath) { } /** - * Gets the active sequence in the project - * @returns {string} JSON string with sequence name or null + * Gets the active sequence in the project. + * @returns {string} JSON string with sequence name and ID, or nulls */ function getActiveSequence() { var result = { @@ -62,16 +59,13 @@ function getActiveSequence() { }; try { - if (!app.project) { - return JSON.stringify(result); - } + if (!app.project) return JSON.stringify(result); var activeSequence = app.project.activeSequence; if (activeSequence) { result.sequenceName = activeSequence.name; - result.sequenceID = activeSequence.sequenceID; + result.sequenceID = activeSequence.sequenceID; } - return JSON.stringify(result); } catch (error) { return JSON.stringify(result); @@ -79,8 +73,8 @@ function getActiveSequence() { } /** - * Inserts a clip into the active sequence at the playhead position - * @param {string} filePath - Full path to the media file + * Inserts a clip into the active sequence at the playhead position. + * @param {string} filePath - Full path to the media file * @param {number} trackIndex - Video track index (1-based) * @returns {string} JSON string with success status */ @@ -102,42 +96,25 @@ function insertClipToSequence(filePath, trackIndex) { return JSON.stringify(result); } - // Import the file app.project.importFiles([filePath]); - // Find the imported item in the project - var projectItem = null; var file = new File(filePath); var fileName = file.displayName; - - // Search through project items for the imported file var rootBin = app.project.rootItem; - projectItem = findProjectItemByName(rootBin, fileName); + var projectItem = findProjectItemByName(rootBin, fileName); if (!projectItem) { result.message = "Could not find imported file in project"; return JSON.stringify(result); } - // Get the track at the specified index if (trackIndex < 1 || trackIndex > sequence.videoTracks.numTracks) { result.message = "Invalid track index: " + trackIndex; return JSON.stringify(result); } var track = sequence.videoTracks[trackIndex - 1]; - var playheadTime = sequence.getTime(); - - // Create a clip from the project item - var clip = projectItem.getMedia(); - if (!clip) { - result.message = "Could not get media from project item"; - return JSON.stringify(result); - } - - // Insert at playhead position - // Note: This is a simplified version. Real timeline editing requires - // more sophisticated handling of clips and tracks + var playheadTime = sequence.getPlayerPosition(); var trackItem = track.insertClip(projectItem, playheadTime); if (trackItem) { @@ -155,8 +132,8 @@ function insertClipToSequence(filePath, trackIndex) { } /** - * Gets the current project file path - * @returns {string} JSON string with project path + * Gets the current project file path. + * @returns {string} JSON string with project path and name */ function getProjectPath() { var result = { @@ -165,16 +142,10 @@ function getProjectPath() { }; try { - if (!app.project) { - return JSON.stringify(result); - } - - var projectFile = app.project.path; - if (projectFile && projectFile.exists) { - result.projectPath = projectFile.fsName; - result.projectName = projectFile.displayName; - } + if (!app.project) return JSON.stringify(result); + result.projectPath = app.project.path; + result.projectName = app.project.name; return JSON.stringify(result); } catch (error) { return JSON.stringify(result); @@ -182,33 +153,26 @@ function getProjectPath() { } /** - * Gets information about all video tracks in the active sequence - * @returns {string} JSON string with track information + * Gets information about all video tracks in the active sequence. + * @returns {string} JSON string with an array of track objects */ function getSequenceTracks() { - var result = { - tracks: [] - }; + var result = { tracks: [] }; try { - if (!app.project) { - return JSON.stringify(result); - } + if (!app.project) return JSON.stringify(result); var sequence = app.project.activeSequence; - if (!sequence) { - return JSON.stringify(result); - } + if (!sequence) return JSON.stringify(result); for (var i = 0; i < sequence.videoTracks.numTracks; i++) { var track = sequence.videoTracks[i]; result.tracks.push({ index: i + 1, - name: track.name || ("V" + (i + 1)), - type: "video" + name: track.name || ("V" + (i + 1)), + type: "video" }); } - return JSON.stringify(result); } catch (error) { return JSON.stringify(result); @@ -216,8 +180,8 @@ function getSequenceTracks() { } /** - * Gets the current playhead position in the active sequence - * @returns {string} JSON string with playhead time in seconds + * Gets the current playhead position in the active sequence. + * @returns {string} JSON string with timeInSeconds and SMPTE timeCode */ function getPlayheadPosition() { var result = { @@ -226,128 +190,59 @@ function getPlayheadPosition() { }; try { - if (!app.project) { - return JSON.stringify(result); - } + if (!app.project) return JSON.stringify(result); var sequence = app.project.activeSequence; - if (!sequence) { - return JSON.stringify(result); - } + if (!sequence) return JSON.stringify(result); - var time = sequence.getTime(); - result.timeInSeconds = time.seconds; + var time = sequence.getPlayerPosition(); + var ticks = parseFloat(time.ticks); + // Premiere Pro ticks: 254016000000 ticks per second + var TICKS_PER_SECOND = 254016000000; + var totalSeconds = ticks / TICKS_PER_SECOND; + result.timeInSeconds = totalSeconds; - // Format as timecode - var ticks = time.ticks; - var ticksPerFrame = sequence.timebase; - var frameNumber = Math.floor(ticks / ticksPerFrame); - var fps = sequence.timebase / 254016000000; - - var hours = Math.floor(frameNumber / (fps * 3600)); - var minutes = Math.floor((frameNumber % (fps * 3600)) / (fps * 60)); - var seconds = Math.floor((frameNumber % (fps * 60)) / fps); - var frames = frameNumber % Math.floor(fps); + // Build SMPTE timecode — use sequence frame rate + var frameRate = sequence.timebase ? (TICKS_PER_SECOND / parseFloat(sequence.timebase)) : 25; + var totalFrames = Math.floor(totalSeconds * frameRate); + var hours = Math.floor(totalFrames / (frameRate * 3600)); + var minutes = Math.floor((totalFrames % (frameRate * 3600)) / (frameRate * 60)); + var seconds = Math.floor((totalFrames % (frameRate * 60)) / frameRate); + var frames = totalFrames % Math.round(frameRate); result.timeCode = pad(hours, 2) + ":" + pad(minutes, 2) + ":" + - pad(seconds, 2) + ":" + pad(frames, 2); - + pad(seconds, 2) + ":" + pad(frames, 2); return JSON.stringify(result); } catch (error) { return JSON.stringify(result); } } -// ============================================================================ -// Helper Functions -// ============================================================================ - /** - * Recursively searches for a project item by name - * @param {Bin} bin - The bin to search in - * @param {string} name - The name to search for - * @returns {ProjectItem} The found project item or null - */ -function findProjectItemByName(bin, name) { - if (!bin || !bin.children) { - return null; - } - - for (var i = 0; i < bin.children.numItems; i++) { - var item = bin.children[i]; - - // Check if this item matches - if (item.name === name) { - return item; - } - - // If it's a bin, search recursively - if (item.type === "bin") { - var found = findProjectItemByName(item, name); - if (found) { - return found; - } - } - } - - return null; -} - -/** - * Pads a number with leading zeros - * @param {number} num - The number to pad - * @param {number} digits - The number of digits to pad to - * @returns {string} The padded number - */ -function pad(num, digits) { - var str = "" + num; - while (str.length < digits) { - str = "0" + str; - } - return str; -} - -/** - * Gets basic project information - * @returns {string} JSON string with project info + * Gets basic project information. + * @returns {string} JSON string with project name, path, and sequence count */ function getProjectInfo() { var result = { projectName: "", projectPath: "", - videoSequenceCount: 0, - audioSequenceCount: 0 + sequenceCount: 0 }; try { - if (!app.project) { - return JSON.stringify(result); - } + if (!app.project) return JSON.stringify(result); result.projectName = app.project.name; + result.projectPath = app.project.path; - var projectFile = app.project.path; - if (projectFile) { - result.projectPath = projectFile.fsName; - } - - // Count sequences var rootBin = app.project.rootItem; if (rootBin && rootBin.children) { for (var i = 0; i < rootBin.children.numItems; i++) { - var item = rootBin.children[i]; - if (item.type === "sequence") { - // Check sequence type by checking tracks - if (item.videoTracks && item.videoTracks.numTracks > 0) { - result.videoSequenceCount++; - } - if (item.audioTracks && item.audioTracks.numTracks > 0) { - result.audioSequenceCount++; - } + if (rootBin.children[i].type === ProjectItemType.SEQUENCE) { + result.sequenceCount++; } } } - return JSON.stringify(result); } catch (error) { return JSON.stringify(result); @@ -355,15 +250,23 @@ function getProjectInfo() { } /** - * Exports the current sequence to a specified location - * @param {string} outputPath - The path where the file should be exported - * @param {string} presetName - The export preset name (e.g., "Apple ProRes 422 HQ") - * @returns {string} JSON string with export status + * Exports the current sequence using Adobe Media Encoder. + * + * AME must be installed. This function launches AME (if not already running) + * and queues the export job — it returns immediately; encoding happens in the + * background and the output file appears when AME finishes. + * + * @param {string} outputPath - Absolute path for the output file + * @param {string} presetPath - Absolute path to an AME preset file (.epr); + * pass an empty string to use the sequence's + * current export settings. + * @returns {string} JSON string with success flag, message, and jobId */ -function exportSequence(outputPath, presetName) { +function exportSequence(outputPath, presetPath) { var result = { success: false, - message: "" + message: "", + jobId: null }; try { @@ -378,35 +281,66 @@ function exportSequence(outputPath, presetName) { return JSON.stringify(result); } - var outputFile = new File(outputPath); + // Ensure Adobe Media Encoder is running before queuing + app.encoder.launchEncoder(); - // Get the export preset - // Note: This requires the preset to be available in Premiere Pro - var preset = app.getExportPreset(presetName); + // encodeSequence(sequence, outputFilePath, presetPath, workAreaType, removeOnCompletion) + // workAreaType: ENCODE_ENTIRE = 0, ENCODE_IN_TO_OUT = 1 + var jobId = app.encoder.encodeSequence( + sequence, + outputPath, + presetPath || "", + app.encoder.ENCODE_ENTIRE, + false // keep job in AME queue after completion + ); - if (!preset) { - result.message = "Export preset not found: " + presetName; - return JSON.stringify(result); + if (jobId) { + result.success = true; + result.message = "Export queued in Adobe Media Encoder"; + result.jobId = jobId; + } else { + result.message = "AME returned no job ID — verify that the preset path is valid"; } - // Export the sequence - app.project.exportSequenceToFile(sequence, outputFile, preset); - - result.success = true; - result.message = "Export completed"; - return JSON.stringify(result); } catch (error) { - result.message = "Error exporting: " + error.message; + result.message = "Export error: " + error.message; return JSON.stringify(result); } } // ============================================================================ -// Initialization +// Helper Functions // ============================================================================ -// Log that the script has loaded -if (typeof(alert) !== "undefined") { - // alert("Wild Dragon MAM ExtendScript loaded"); +/** + * Recursively searches for a project item by display name. + * @param {ProjectItem} bin - The bin/root to search + * @param {string} name - Name to match + * @returns {ProjectItem|null} + */ +function findProjectItemByName(bin, name) { + if (!bin || !bin.children) return null; + + for (var i = 0; i < bin.children.numItems; i++) { + var item = bin.children[i]; + if (item.name === name) return item; + if (item.type === ProjectItemType.BIN) { + var found = findProjectItemByName(item, name); + if (found) return found; + } + } + return null; +} + +/** + * Pads a number with leading zeros. + * @param {number} num - Number to pad + * @param {number} digits - Minimum digit count + * @returns {string} + */ +function pad(num, digits) { + var str = "" + Math.floor(num); + while (str.length < digits) str = "0" + str; + return str; }