From 5bb22c17c80c2f1a82d656992413560801044cd3 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Wed, 20 May 2026 00:35:18 -0400 Subject: [PATCH] feat(plugin): add exportTimelineData() and getProjectItems() to ExtendScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit exportTimelineData() walks all video tracks in the active sequence and returns clip source/timeline frame positions + file paths so the panel JS can map them back to MAM asset IDs for timeline export. getProjectItems() enumerates all ProjectItems with paths — useful for rebuilding the import mapping after a Premiere restart. --- services/premiere-plugin/jsx/premiere.jsx | 124 ++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/services/premiere-plugin/jsx/premiere.jsx b/services/premiere-plugin/jsx/premiere.jsx index 328046a..557b75b 100644 --- a/services/premiere-plugin/jsx/premiere.jsx +++ b/services/premiere-plugin/jsx/premiere.jsx @@ -309,6 +309,130 @@ function exportSequence(outputPath, presetPath) { } } +// ============================================================================ +// Timeline Export — read sequence clips for MAM round-trip +// ============================================================================ + +/** + * Reads all clips from all video tracks in the active sequence. + * Returns JSON with sequence settings and a flat clip array. + * + * Each clip entry contains: + * filePath – absolute OS path to source media (for MAM asset ID lookup) + * fileName – project panel display name + * trackIndex – 0-based video track index (0 = V1, 1 = V2, …) + * sourceInFrames – in-point within source media, in frames + * sourceOutFrames – out-point within source media, in frames + * timelineInFrames – clip start position on timeline, in frames + * timelineOutFrames – clip end position on timeline, in frames + * + * sequence.timebase is ticks-per-frame. Premiere uses 254,016,000,000 + * ticks/second, so fps = 254016000000 / timebase. + */ +function exportTimelineData() { + var result = { + success: false, + message: "", + sequenceName: "", + frameRate: 59.94, + width: 1920, + height: 1080, + clips: [] + }; + + 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); + } + + result.sequenceName = sequence.name; + + var TICKS_PER_SECOND = 254016000000; + var timebaseTicks = parseFloat(sequence.timebase) || 4240384; + result.frameRate = parseFloat((TICKS_PER_SECOND / timebaseTicks).toFixed(4)); + + try { result.width = sequence.frameSizeHorizontal || 1920; } catch (e) {} + try { result.height = sequence.frameSizeVertical || 1080; } catch (e) {} + + var videoTracks = sequence.videoTracks; + for (var t = 0; t < videoTracks.numTracks; t++) { + var track = videoTracks[t]; + if (!track || !track.clips) continue; + var numClips = track.clips.numItems; + for (var c = 0; c < numClips; c++) { + try { + var clip = track.clips[c]; + if (!clip || !clip.projectItem) continue; + + var filePath = ""; + try { filePath = clip.projectItem.getMediaPath(); } catch (e) {} + + var srcIn = Math.round(parseFloat(clip.inPoint.ticks) / timebaseTicks); + var srcOut = Math.round(parseFloat(clip.outPoint.ticks) / timebaseTicks); + var recIn = Math.round(parseFloat(clip.start.ticks) / timebaseTicks); + var recOut = Math.round(parseFloat(clip.end.ticks) / timebaseTicks); + + // Skip degenerate clips (zero or negative duration) + if (srcOut <= srcIn || recOut <= recIn) continue; + + result.clips.push({ + trackIndex: t, + filePath: filePath, + fileName: clip.projectItem.name || "", + sourceInFrames: srcIn, + sourceOutFrames: srcOut, + timelineInFrames: recIn, + timelineOutFrames: recOut + }); + } catch (e) { /* skip malformed clip */ } + } + } + + result.success = true; + result.message = result.clips.length + " clip(s) across " + videoTracks.numTracks + " track(s)"; + return JSON.stringify(result); + } catch (error) { + result.message = "Error reading timeline: " + error.message; + return JSON.stringify(result); + } +} + +/** + * Returns all media ProjectItems in the current project with name + file path. + * Useful for rebuilding the asset-path lookup map after a Premiere restart + * when temp-file paths may have changed. + */ +function getProjectItems() { + var result = { items: [] }; + try { + if (!app.project) return JSON.stringify(result); + _collectProjectItems(app.project.rootItem, result.items); + } catch (e) {} + return JSON.stringify(result); +} + +function _collectProjectItems(bin, out) { + if (!bin || !bin.children) return; + for (var i = 0; i < bin.children.numItems; i++) { + var item = bin.children[i]; + if (!item) continue; + if (item.type === ProjectItemType.BIN) { + _collectProjectItems(item, out); + } else { + var path = ""; + try { path = item.getMediaPath(); } catch (e) {} + out.push({ name: item.name, path: path }); + } + } +} + // ============================================================================ // Helper Functions // ============================================================================