diff --git a/services/premiere-plugin-uxp/src/main.js b/services/premiere-plugin-uxp/src/main.js index 05c07ad..0148310 100644 --- a/services/premiere-plugin-uxp/src/main.js +++ b/services/premiere-plugin-uxp/src/main.js @@ -1,4 +1,4 @@ -// Dragonflight UXP Panel — main.js v2.1.3 +// Dragonflight UXP Panel — main.js v2.1.5 (function () { if (window.__df_uxp_started) return; @@ -10,7 +10,6 @@ $('connect-btn').disabled = !$('server-url').value.trim() || !$('api-token').value.trim(); } - // ── Connect ────────────────────────────────────────────────────── async function tryConnect(serverUrl, apiToken) { UI.setStatus('#connect-status', 'Connecting…', 'muted'); try { @@ -30,7 +29,6 @@ } } - // ── Connect pane ───────────────────────────────────────────────── function wireConnectPane() { $('server-url').value = API.state.serverUrl; $('api-token').value = API.state.apiToken; @@ -44,7 +42,6 @@ }); } - // ── Library pane ───────────────────────────────────────────────── function wireLibraryPane() { $('disconnect-btn').addEventListener('click', () => { API.disconnect(); @@ -77,33 +74,28 @@ $('refresh-btn').addEventListener('click', () => Library.refresh($('search-input').value)); - // ── Import proxy ── $('import-proxy-btn').addEventListener('click', async () => { - const a = Library.selectedAsset(); - if (!a) return; + const a = Library.selectedAsset(); if (!a) return; _disableImportBtns(true); try { const { localPath, safeName } = await Import.proxy(a); - Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); + Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); Library.recordImport('name:' + safeName, { assetId: a.id, displayName: a.display_name || a.filename }); } catch (e) { UI.hideProgress(); UI.toast('Proxy import failed: ' + e.message, 'error'); } finally { _disableImportBtns(false); Library._syncActions(); } }); - // ── Import hi-res ── $('import-hires-btn').addEventListener('click', async () => { - const a = Library.selectedAsset(); - if (!a) return; + const a = Library.selectedAsset(); if (!a) return; _disableImportBtns(true); try { const { localPath, safeName } = await Import.hires(a); - Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); + Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); Library.recordImport('name:' + safeName, { assetId: a.id, displayName: a.display_name || a.filename }); } catch (e) { UI.hideProgress(); UI.toast('Hi-res import failed: ' + e.message, 'error'); } finally { _disableImportBtns(false); Library._syncActions(); } }); - // ── Import all ── $('import-all-btn').addEventListener('click', async () => { const assets = Library.state.assets; if (!assets.length) { UI.toast('No assets', 'error'); return; } @@ -112,7 +104,7 @@ for (const a of assets) { try { const { localPath, safeName } = await Import.proxy(a); - Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); + Library.recordImport(localPath, { assetId: a.id, displayName: a.display_name || a.filename }); Library.recordImport('name:' + safeName, { assetId: a.id, displayName: a.display_name || a.filename }); ok++; } catch (_) { fail++; } @@ -123,10 +115,8 @@ Library._syncActions(); }); - // ── Mount live ── $('mount-live-btn').addEventListener('click', async () => { - const a = Library.selectedAsset(); - if (!a) return; + const a = Library.selectedAsset(); if (!a) return; $('mount-live-btn').disabled = true; UI.showProgress('Resolving live path…', 10); try { @@ -138,16 +128,14 @@ Library.startLiveStatusPoll(a.id); UI.hideProgress(); UI.toast('Mounted live: ' + (info.display_name || a.id), 'ok'); - } catch (e) { - UI.hideProgress(); - UI.toast('Mount live failed: ' + e.message, 'error'); - } finally { Library._syncActions(); } + } catch (e) { UI.hideProgress(); UI.toast('Mount live failed: ' + e.message, 'error'); } + finally { Library._syncActions(); } }); // ── Relink single asset (proxy → hi-res) ── + // ALL premierepro calls must be awaited — runtime returns Promises $('relink-btn').addEventListener('click', async () => { - const a = Library.selectedAsset(); - if (!a) return; + const a = Library.selectedAsset(); if (!a) return; const entry = Library.getImport('live:' + a.id); if (!entry) { UI.toast('No live mount recorded', 'error'); return; } $('relink-btn').disabled = true; @@ -157,25 +145,22 @@ const safeName = UI.sanitizeFilename(info.filename || (a.display_name || a.id) + '.' + (info.ext || 'mxf')); const dest = await Import._tempPath(safeName); UI.showProgress('Downloading ' + safeName + '…', 20); - const r = await API.requestFollow(info.url, {}); + // S3 presigned URL — no auth, auto-follow redirects (UXP handles) + const r = await API.requestExternal(info.url); if (!r.ok) throw new Error('Download HTTP ' + r.status); - await Import._streamToFile(r, dest, ({ received, total }) => { - const pct = total ? 20 + (received / total) * 60 : 20; - UI.showProgress(UI.formatBytes(received) + '…', pct); - }); + UI.showProgress('Writing to disk…', 65); + const buf = await r.arrayBuffer(); + await Import._writeBuffer(dest, buf); UI.showProgress('Relinking…', 85); - // Timeline._relinkInProject uses ppro() internally (sync getActiveProject) const P = require('premierepro'); - const proj = P.Project.getActiveProject(); // sync — no await + const proj = await P.Project.getActiveProject(); // ← must await if (!proj) throw new Error('No active project'); await Timeline._relinkInProject(proj, entry.livePath, dest); Library.recordImport(dest, { assetId: a.id, displayName: a.display_name || a.filename }); UI.hideProgress(); UI.toast('Relinked: ' + safeName, 'ok'); - } catch (e) { - UI.hideProgress(); - UI.toast('Relink failed: ' + e.message, 'error'); - } finally { Library._syncActions(); } + } catch (e) { UI.hideProgress(); UI.toast('Relink failed: ' + e.message, 'error'); } + finally { Library._syncActions(); } }); $('export-timeline-btn').addEventListener('click', openExportPanel); @@ -187,7 +172,6 @@ ['import-proxy-btn','import-hires-btn'].forEach(id => { $(id).disabled = dis; }); } - // ── Export timeline panel ───────────────────────────────────────── let _seqCache = null; async function openExportPanel() { @@ -222,7 +206,6 @@ }); } - // ── Conform panel ───────────────────────────────────────────────── async function openConformPanel() { UI.showProgress('Reading Premiere sequence…', 20); try { _seqCache = await Timeline.readActiveSequence(); } @@ -237,8 +220,7 @@ if (conformProj) { conformProj.innerHTML = ''; Library.state.projects.forEach(p => { - const o = document.createElement('option'); - o.value = p.id; o.textContent = p.name; + const o = document.createElement('option'); o.value = p.id; o.textContent = p.name; conformProj.appendChild(o); }); } @@ -250,20 +232,16 @@ $('conform-cancel-btn').addEventListener('click', () => UI.closeSlide('conform-overlay', 'conform-panel')); $('conform-overlay').addEventListener('click', () => UI.closeSlide('conform-overlay', 'conform-panel')); $('preset-cards').addEventListener('click', e => { - const card = e.target.closest('.preset-card'); - if (!card) return; + const card = e.target.closest('.preset-card'); if (!card) return; document.querySelectorAll('.preset-card').forEach(c => c.classList.remove('selected')); card.classList.add('selected'); const presets = { - broadcast: { codec:'prores_hq', quality:'high', resolution:'1080p', audio:'broadcast' }, - web: { codec:'h264', quality:'medium', resolution:'1080p', audio:'web' }, - archive: { codec:'prores_4444', quality:'high', resolution:'uhd', audio:'archive' }, + broadcast: { codec:'prores_hq', quality:'high', resolution:'1080p', audio:'broadcast' }, + web: { codec:'h264', quality:'medium', resolution:'1080p', audio:'web' }, + archive: { codec:'prores_4444', quality:'high', resolution:'uhd', audio:'archive' }, }; const p = presets[card.dataset.preset]; - if (p) { - $('conform-codec').value = p.codec; $('conform-quality').value = p.quality; - $('conform-resolution').value = p.resolution; $('conform-audio').value = p.audio; - } + if (p) { $('conform-codec').value=p.codec; $('conform-quality').value=p.quality; $('conform-resolution').value=p.resolution; $('conform-audio').value=p.audio; } }); $('conform-start-btn').addEventListener('click', async () => { if (!_seqCache) return; @@ -274,22 +252,21 @@ UI.showProgress('Starting conform job…', 15); try { const jobId = await Timeline.startConform(projectId, _seqCache.sequenceName, _seqCache, { - codec: $('conform-codec').value, quality: $('conform-quality').value, - resolution: $('conform-resolution').value, audio: $('conform-audio').value, + codec:$('conform-codec').value, quality:$('conform-quality').value, + resolution:$('conform-resolution').value, audio:$('conform-audio').value, }); Timeline.pollConform(jobId, (pct, status) => UI.showProgress('Conform: ' + status + ' (' + pct + '%)…', 15 + pct * 0.8), job => { UI.hideProgress(); - if (job.status === 'completed') { UI.toast('Conform complete', 'ok'); Library.refresh(Library.state.searchQuery); } - else { UI.toast('Conform failed: ' + (job.error || 'unknown'), 'error'); } + if (job.status==='completed') { UI.toast('Conform complete','ok'); Library.refresh(Library.state.searchQuery); } + else { UI.toast('Conform failed: '+(job.error||'unknown'),'error'); } } ); } catch (e) { UI.hideProgress(); UI.toast('Conform failed: ' + e.message, 'error'); } }); } - // ── Batch Relink panel ──────────────────────────────────────────── let _relinkClips = []; async function openRelinkPanel() { @@ -311,17 +288,13 @@ const row = document.createElement('div'); row.className = 'clip-item' + (matched ? ' matched' : ' unmatched'); const cb = document.createElement('input'); - cb.type = 'checkbox'; cb.id = 'rclip-' + i; - cb.disabled = !matched; cb.checked = matched; + cb.type='checkbox'; cb.id='rclip-'+i; cb.disabled=!matched; cb.checked=matched; cb.addEventListener('change', _syncRelinkStart); const lbl = document.createElement('label'); - lbl.htmlFor = 'rclip-' + i; lbl.className = 'clip-item-name'; - lbl.textContent = clip.fileName || 'clip'; + lbl.htmlFor='rclip-'+i; lbl.className='clip-item-name'; lbl.textContent=clip.fileName||'clip'; const st = document.createElement('span'); - st.className = 'clip-item-status'; - st.textContent = matched ? 'matched' : 'not in MAM'; - row.append(cb, lbl, st); - list.appendChild(row); + st.className='clip-item-status'; st.textContent=matched?'matched':'not in MAM'; + row.append(cb, lbl, st); list.appendChild(row); }); } UI.openSlide('relink-overlay', 'relink-panel'); @@ -337,10 +310,10 @@ $('relink-cancel-btn').addEventListener('click', () => UI.closeSlide('relink-overlay', 'relink-panel')); $('relink-overlay').addEventListener('click', () => UI.closeSlide('relink-overlay', 'relink-panel')); $('relink-start-btn').addEventListener('click', async () => { - const checked = document.querySelectorAll('#clip-list input[type="checkbox"]:checked'); + const checked = document.querySelectorAll('#clip-list input[type="checkbox"]:checked'); const selected = []; checked.forEach(cb => { - const i = parseInt(cb.id.replace('rclip-', ''), 10); + const i = parseInt(cb.id.replace('rclip-',''), 10); if (_relinkClips[i] && _relinkClips[i].asset_id) selected.push(_relinkClips[i]); }); if (!selected.length) return; @@ -354,7 +327,6 @@ }); } - // ── Init ────────────────────────────────────────────────────────── function init() { wireConnectPane(); wireLibraryPane(); wireExportPanel(); wireConformPanel(); wireRelinkPanel();