From 5774f61ac7646f3236caac8edd40960b0d6bc8ae Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Thu, 28 May 2026 09:06:43 -0400 Subject: [PATCH] =?UTF-8?q?UXP=20v2.1.5:=20timeline.js=20=E2=80=94=20await?= =?UTF-8?q?=20all=20premierepro=20calls;=20runtime=20is=20async?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/premiere-plugin-uxp/src/timeline.js | 154 +++++++++---------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/services/premiere-plugin-uxp/src/timeline.js b/services/premiere-plugin-uxp/src/timeline.js index a27a59d..f045feb 100644 --- a/services/premiere-plugin-uxp/src/timeline.js +++ b/services/premiere-plugin-uxp/src/timeline.js @@ -1,6 +1,5 @@ -// timeline.js — v2.1.4 -// Reads active Premiere sequence via UXP premierepro DOM API. -// All Premiere DOM methods are synchronous. No redirect:manual (not in UXP). +// timeline.js — v2.1.5 +// premierepro API: docs say sync, runtime returns Promises. Await everything. (function () { const Timeline = {}; @@ -15,13 +14,13 @@ // ── Read active sequence ───────────────────────────────────────── Timeline.readActiveSequence = async function () { const P = ppro(); - const project = P.Project.getActiveProject(); // sync + const project = await P.Project.getActiveProject(); if (!project) throw new Error('No active Premiere project'); - const seq = project.getActiveSequence(); // sync + const seq = await project.getActiveSequence(); if (!seq) throw new Error('No active sequence'); - const name = seq.name || 'Sequence 1'; - const settings = seq.getSettings(); // sync + const name = seq.name || (await seq.getName && await seq.getName()) || 'Sequence 1'; + const settings = await seq.getSettings(); let frameRate = 29.97; try { const fr = settings.videoFrameRate; @@ -31,29 +30,34 @@ const height = settings.videoFrameHeight || 1080; const clips = []; - const trackCount = seq.getVideoTrackCount(); // sync + const trackCount = await seq.getVideoTrackCount(); for (let ti = 0; ti < trackCount; ti++) { - const track = seq.getVideoTrack(ti); // sync + const track = await seq.getVideoTrack(ti); if (!track) continue; let items = []; - try { items = track.getTrackItems(1, false); } catch (_) { continue; } + try { items = await track.getTrackItems(1, false); } catch (_) { continue; } if (!items || !items.length) continue; for (const clip of items) { try { - const projItem = clip.getProjectItem(); // sync + const projItem = await clip.getProjectItem(); if (!projItem) continue; let filePath = ''; try { - const clipItem = P.ClipProjectItem.cast(projItem); - filePath = clipItem.getMediaFilePath() || ''; // sync + const clipItem = await P.ClipProjectItem.cast(projItem); + filePath = await clipItem.getMediaFilePath() || ''; } catch (_) {} - const fileName = clip.getName() || (filePath ? path.basename(filePath) : 'clip'); - const tlIn = clip.getStartTime().seconds; - const tlOut = clip.getEndTime().seconds; - const srcIn = clip.getInPoint().seconds; - const srcOut = clip.getOutPoint().seconds; + const clipName = await clip.getName().catch(() => ''); + const fileName = clipName || (filePath ? path.basename(filePath) : 'clip'); + const startT = await clip.getStartTime(); + const endT = await clip.getEndTime(); + const inT = await clip.getInPoint(); + const outT = await clip.getOutPoint(); + const tlIn = startT.seconds; + const tlOut = endT.seconds; + const srcIn = inT.seconds; + const srcOut = outT.seconds; clips.push({ fileName, filePath, trackIndex: ti, timelineInSec: tlIn, timelineOutSec: tlOut, @@ -69,13 +73,13 @@ return { sequenceName: name, frameRate, width, height, clips }; }; - // ── Active sequence info bar ───────────────────────────────────── + // ── Sequence info bar ──────────────────────────────────────────── Timeline.refreshSeqBar = async function () { try { const P = ppro(); - const project = P.Project.getActiveProject(); + const project = await P.Project.getActiveProject(); if (!project) { UI.setHidden('#seq-info-bar', true); return; } - const seq = project.getActiveSequence(); + const seq = await project.getActiveSequence(); if (!seq) { UI.setHidden('#seq-info-bar', true); return; } const name = seq.name || ''; if (name) { @@ -88,54 +92,54 @@ }; // ── FCP XML ────────────────────────────────────────────────────── - Timeline.generateFcpXml = function (timelineData) { - const seqName = UI.escapeXml(timelineData.sequenceName || 'Sequence 1'); - const frameRate = timelineData.frameRate || 29.97; - const width = timelineData.width || 1920; - const height = timelineData.height || 1080; - const clips = timelineData.clips || []; - let totalFrames = 0; - clips.forEach(c => { if ((c.timelineOutFrames||0) > totalFrames) totalFrames = c.timelineOutFrames; }); - if (totalFrames < 1) totalFrames = 100; - const duration = UI.timecodeFromFrames(totalFrames, frameRate); - const frateStr = UI.formatFrameRate(frameRate); + Timeline.generateFcpXml = function (td) { + const seqName = UI.escapeXml(td.sequenceName || 'Sequence 1'); + const fps = td.frameRate || 29.97; + const w = td.width || 1920; + const h = td.height || 1080; + const clips = td.clips || []; + let totalF = 0; + clips.forEach(c => { if ((c.timelineOutFrames||0) > totalF) totalF = c.timelineOutFrames; }); + if (totalF < 1) totalF = 100; + const dur = UI.timecodeFromFrames(totalF, fps); + const frate = UI.formatFrameRate(fps); let xml = '\n\n\n \n'; - xml += ' \n'; + xml += ' \n'; const seen = {}; let rid = 1; - clips.forEach(clip => { - const key = clip.filePath || clip.fileName || 'c' + rid; + clips.forEach(c => { + const key = c.filePath || c.fileName || 'c' + rid; if (!seen[key]) { seen[key] = 'r' + rid; - const srcDur = UI.timecodeFromFrames(Math.max(1,(clip.sourceOutFrames||100)-(clip.sourceInFrames||0)), frameRate); - xml += ' \n'; + const sd = UI.timecodeFromFrames(Math.max(1,(c.sourceOutFrames||100)-(c.sourceInFrames||0)), fps); + xml += ' \n'; rid++; } }); - xml += ' \n \n \n \n \n \n'; - clips.forEach(clip => { - const resId = seen[clip.filePath || clip.fileName || 'x'] || 'r1'; - const off = UI.timecodeFromFrames(clip.timelineInFrames||0, frameRate); - const dur = UI.timecodeFromFrames(Math.max(1,(clip.timelineOutFrames||1)-(clip.timelineInFrames||0)), frameRate); - const srcIn = UI.timecodeFromFrames(clip.sourceInFrames||0, frameRate); - xml += ' \n \n \n'; + xml += ' \n \n \n \n \n \n'; + clips.forEach(c => { + const ref = seen[c.filePath || c.fileName || 'x'] || 'r1'; + const off = UI.timecodeFromFrames(c.timelineInFrames||0, fps); + const cd = UI.timecodeFromFrames(Math.max(1,(c.timelineOutFrames||1)-(c.timelineInFrames||0)), fps); + const si = UI.timecodeFromFrames(c.sourceInFrames||0, fps); + xml += ' \n \n \n'; }); xml += ' \n \n \n \n \n'; return xml; }; // ── Push Timeline to MAM ───────────────────────────────────────── - Timeline.pushToMAM = async function (seqName, projectId, timelineData) { - const resolved = Library.resolveClipsToAssets(timelineData.clips || []); + Timeline.pushToMAM = async function (seqName, projectId, td) { + const resolved = Library.resolveClipsToAssets(td.clips || []); const matched = resolved.filter(c => c.asset_id); if (!matched.length) throw new Error('No clips matched MAM assets — import proxies first'); const seqs = await API.listSequences(projectId); let seqId; const existing = seqs.find(s => s.name === seqName); if (existing) { - await API.updateSequence(existing.id, { frame_rate: timelineData.frameRate, width: timelineData.width, height: timelineData.height }); + await API.updateSequence(existing.id, { frame_rate: td.frameRate, width: td.width, height: td.height }); seqId = existing.id; } else { - const created = await API.createSequence(projectId, seqName, timelineData.frameRate, timelineData.width, timelineData.height); + const created = await API.createSequence(projectId, seqName, td.frameRate, td.width, td.height); seqId = created.id; } await API.pushClips(seqId, matched.map(c => ({ @@ -147,49 +151,43 @@ }; // ── Conform ────────────────────────────────────────────────────── - Timeline.startConform = async function (projectId, seqName, timelineData, opts) { - const { seqId } = await Timeline.pushToMAM(seqName, projectId, timelineData); + Timeline.startConform = async function (projectId, seqName, td, opts) { + const { seqId } = await Timeline.pushToMAM(seqName, projectId, td); const resMap = { '1080p':[1920,1080], '720p':[1280,720], 'uhd':[3840,2160], 'source':[0,0] }; const [w, h] = resMap[opts.resolution] || [1920,1080]; - const job = await API.startConform(seqId, { codec: opts.codec, quality: opts.quality, width: w||undefined, height: h||undefined, audio: opts.audio, fcp_xml: Timeline.generateFcpXml(timelineData) }); + const job = await API.startConform(seqId, { codec:opts.codec, quality:opts.quality, width:w||undefined, height:h||undefined, audio:opts.audio, fcp_xml:Timeline.generateFcpXml(td) }); return job.jobId || job.id; }; Timeline.pollConform = function (jobId, onProgress, onDone) { - const timer = setInterval(async () => { + const t = setInterval(async () => { try { const job = await API.getJob(jobId); - onProgress(job.progress || 0, job.status); - if (job.status === 'completed' || job.status === 'failed') { clearInterval(timer); onDone(job); } + onProgress(job.progress||0, job.status); + if (job.status==='completed'||job.status==='failed') { clearInterval(t); onDone(job); } } catch (_) {} }, 2000); - return timer; + return t; }; // ── Batch Hi-Res Relink ────────────────────────────────────────── Timeline.batchRelink = async function (selectedClips) { const P = ppro(); - const project = P.Project.getActiveProject(); // sync + const project = await P.Project.getActiveProject(); if (!project) throw new Error('No active Premiere project'); const results = { succeeded: 0, failed: 0, errors: [] }; - for (const clip of selectedClips) { if (!clip.asset_id) continue; try { UI.showProgress('Fetching hi-res for ' + clip.fileName + '…', 20); const info = await API.getHiresInfo(clip.asset_id); - const safeName = UI.sanitizeFilename(info.filename || clip.fileName + '.' + (info.ext || 'mxf')); + const safeName = UI.sanitizeFilename(info.filename || clip.fileName + '.' + (info.ext||'mxf')); const dest = await Import._tempPath(safeName); - UI.showProgress('Downloading ' + safeName + '…', 30); - // S3 presigned URL — use requestExternal (no auth header, no redirect:manual) const r = await API.requestExternal(info.url); if (!r.ok) throw new Error('Download HTTP ' + r.status); - - UI.showProgress('Writing…', 60); const buf = await r.arrayBuffer(); await Import._writeBuffer(dest, buf); - UI.showProgress('Relinking ' + clip.fileName + '…', 85); await Timeline._relinkInProject(project, clip.filePath, dest); results.succeeded++; @@ -201,38 +199,40 @@ return results; }; - // Walk project items and relink via ClipProjectItem.changeMediaFilePath() + // Relink via ClipProjectItem.changeMediaFilePath — await all calls Timeline._relinkInProject = async function (project, oldPath, newPath) { const P = ppro(); - const root = project.getRootItem(); // sync (FolderItem) + const root = await project.getRootItem(); let count = 0; - function walkSync(folderItem) { + async function walk(item) { let children = []; - try { if (typeof folderItem.getItems === 'function') children = folderItem.getItems() || []; } catch (_) {} - for (const item of children) { + try { children = await item.getItems(); } catch (_) {} + for (const child of children) { try { - const clipItem = P.ClipProjectItem.cast(item); - const mp = clipItem.getMediaFilePath(); - if (mp && mp === oldPath) { clipItem.changeMediaFilePath(newPath, false); count++; } + const ci = await P.ClipProjectItem.cast(child); + const mp = await ci.getMediaFilePath(); + if (mp && mp === oldPath) { await ci.changeMediaFilePath(newPath, false); count++; } } catch (_) { - try { walkSync(item); } catch (__) {} + try { await walk(child); } catch (__) {} } } } - walkSync(root); + + await walk(root); // Fallback: findItemsMatchingMediaPath if (count === 0) { try { - const matches = P.ClipProjectItem.cast(root).findItemsMatchingMediaPath(oldPath, true); - for (const item of (matches || [])) { - try { P.ClipProjectItem.cast(item).changeMediaFilePath(newPath, false); count++; } catch (_) {} + const rootCi = await P.ClipProjectItem.cast(root); + const matches = await rootCi.findItemsMatchingMediaPath(oldPath, true); + for (const item of (matches||[])) { + try { const ci = await P.ClipProjectItem.cast(item); await ci.changeMediaFilePath(newPath, false); count++; } catch (_) {} } } catch (_) {} } - if (count === 0) throw new Error('No matching clips found for: ' + (oldPath || 'unknown')); + if (count === 0) throw new Error('No matching clips found for: ' + (oldPath||'unknown')); return count; };