/** * Wild Dragon MAM - Premiere Pro ExtendScript * * 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 ES3-level JavaScript — no arrow functions, no const/let, * no template literals, no destructuring. */ // ============================================================================ // Core Import Functions // ============================================================================ /** * 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 */ function importFileToProject(filePath) { var result = { success: false, message: "", itemID: null }; try { if (!app.project) { result.message = "No active Premiere Pro project"; return JSON.stringify(result); } var file = new File(filePath); if (!file.exists) { result.message = "File does not exist: " + filePath; return JSON.stringify(result); } 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; return JSON.stringify(result); } } /** * Gets the active sequence in the project. * @returns {string} JSON string with sequence name and ID, or nulls */ function getActiveSequence() { var result = { sequenceName: null, sequenceID: null }; try { if (!app.project) return JSON.stringify(result); var activeSequence = app.project.activeSequence; if (activeSequence) { result.sequenceName = activeSequence.name; result.sequenceID = activeSequence.sequenceID; } return JSON.stringify(result); } catch (error) { return JSON.stringify(result); } } /** * 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 */ function insertClipToSequence(filePath, trackIndex) { var result = { success: false, message: "" }; try { if (!app.project) { result.message = "No active project"; return JSON.stringify(result); } var sequence = app.project.activeSequence; if (!sequence) { result.message = "No active sequence"; return JSON.stringify(result); } app.project.importFiles([filePath]); var file = new File(filePath); var fileName = file.displayName; var rootBin = app.project.rootItem; var projectItem = findProjectItemByName(rootBin, fileName); if (!projectItem) { result.message = "Could not find imported file in project"; return JSON.stringify(result); } 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.getPlayerPosition(); var trackItem = track.insertClip(projectItem, playheadTime); if (trackItem) { result.success = true; result.message = "Clip inserted at track " + trackIndex; } else { result.message = "Failed to insert clip into track"; } return JSON.stringify(result); } catch (error) { result.message = "Error inserting clip: " + error.message; return JSON.stringify(result); } } /** * Gets the current project file path. * @returns {string} JSON string with project path and name */ function getProjectPath() { var result = { projectPath: null, projectName: null }; try { 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); } } /** * 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: [] }; try { if (!app.project) return JSON.stringify(result); var sequence = app.project.activeSequence; 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" }); } return JSON.stringify(result); } catch (error) { return JSON.stringify(result); } } /** * Gets the current playhead position in the active sequence. * @returns {string} JSON string with timeInSeconds and SMPTE timeCode */ function getPlayheadPosition() { var result = { timeInSeconds: 0, timeCode: "" }; try { if (!app.project) return JSON.stringify(result); var sequence = app.project.activeSequence; if (!sequence) return JSON.stringify(result); 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; // 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); return JSON.stringify(result); } catch (error) { return JSON.stringify(result); } } /** * Gets basic project information. * @returns {string} JSON string with project name, path, and sequence count */ function getProjectInfo() { var result = { projectName: "", projectPath: "", sequenceCount: 0 }; try { if (!app.project) return JSON.stringify(result); result.projectName = app.project.name; result.projectPath = app.project.path; var rootBin = app.project.rootItem; if (rootBin && rootBin.children) { for (var i = 0; i < rootBin.children.numItems; i++) { if (rootBin.children[i].type === ProjectItemType.SEQUENCE) { result.sequenceCount++; } } } return JSON.stringify(result); } catch (error) { return JSON.stringify(result); } } /** * 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, presetPath) { var result = { success: false, message: "", jobId: null }; try { if (!app.project) { result.message = "No active project"; return JSON.stringify(result); } var sequence = app.project.activeSequence; if (!sequence) { result.message = "No active sequence"; return JSON.stringify(result); } // Ensure Adobe Media Encoder is running before queuing app.encoder.launchEncoder(); // 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 (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"; } return JSON.stringify(result); } catch (error) { result.message = "Export error: " + error.message; return JSON.stringify(result); } } // ============================================================================ // Helper Functions // ============================================================================ /** * 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; }