feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
// Download asset → import into the active Premiere project via UXP's
|
|
|
|
|
// `premierepro` module. This is what the CEP panel could no longer do
|
|
|
|
|
// (csInterface.evalScript callback was lost in PPro 26).
|
|
|
|
|
|
|
|
|
|
(function () {
|
|
|
|
|
const Import = {};
|
|
|
|
|
|
|
|
|
|
// Use UXP's Node-style fs for streaming writes (avoids buffering multi-GB
|
2026-05-28 00:43:44 -04:00
|
|
|
// files in memory). `os` is a stripped subset — `os.tmpdir` is not exposed
|
|
|
|
|
// in all UXP builds, so resolve the temp folder defensively below.
|
|
|
|
|
const fs = require('fs');
|
feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
const path = require('path');
|
2026-05-28 00:43:44 -04:00
|
|
|
let os; try { os = require('os'); } catch (_) { os = {}; }
|
|
|
|
|
let uxpFs;
|
|
|
|
|
try { uxpFs = require('uxp').storage.localFileSystem; } catch (_) { uxpFs = null; }
|
|
|
|
|
|
|
|
|
|
// Resolve a writable temp folder using whichever API this UXP build
|
|
|
|
|
// actually exposes. Returns an absolute path string suitable for both
|
|
|
|
|
// fs.createWriteStream and premierepro.Project.importFiles.
|
|
|
|
|
async function getTempBase() {
|
|
|
|
|
// 1. Node-style os.tmpdir (newer UXP, simplest case)
|
|
|
|
|
try { if (os.tmpdir) { const t = os.tmpdir(); if (typeof t === 'string' && t.length) return t; } } catch (_) {}
|
|
|
|
|
// 2. UXP storage API — documented portable approach
|
|
|
|
|
if (uxpFs && uxpFs.getTemporaryFolder) {
|
|
|
|
|
try { const tmp = await uxpFs.getTemporaryFolder(); if (tmp && tmp.nativePath) return tmp.nativePath; } catch (_) {}
|
|
|
|
|
}
|
|
|
|
|
// 3. Windows env vars (always set under PPro)
|
|
|
|
|
try {
|
|
|
|
|
const e = (typeof process !== 'undefined' && process.env) || {};
|
|
|
|
|
if (e.TEMP) return e.TEMP;
|
|
|
|
|
if (e.TMP) return e.TMP;
|
|
|
|
|
if (e.LOCALAPPDATA) return path.join(e.LOCALAPPDATA, 'Temp');
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
// 4. Homedir + Windows-style fallback
|
|
|
|
|
try { if (os.homedir) return path.join(os.homedir(), 'AppData', 'Local', 'Temp'); } catch (_) {}
|
|
|
|
|
throw new Error('Could not determine a writable temp folder on this system');
|
|
|
|
|
}
|
feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
|
2026-05-28 00:43:44 -04:00
|
|
|
async function tempPath(safeName) {
|
|
|
|
|
const base = await getTempBase();
|
|
|
|
|
return path.join(base, 'dragonflight-' + safeName);
|
feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stream the body of a fetch Response to a file on disk.
|
|
|
|
|
// Returns the absolute path. Reports progress via onProgress({ received, total }).
|
|
|
|
|
async function streamToFile(response, destPath, onProgress) {
|
|
|
|
|
const total = Number(response.headers.get('content-length') || 0);
|
|
|
|
|
const reader = response.body.getReader();
|
|
|
|
|
const out = fs.createWriteStream(destPath);
|
|
|
|
|
let received = 0;
|
|
|
|
|
// Wrap close in a Promise so we don't resolve before the OS finishes
|
|
|
|
|
// flushing — without this, importFiles can race the writer.
|
|
|
|
|
const closed = new Promise((resolve, reject) => {
|
|
|
|
|
out.on('finish', resolve);
|
|
|
|
|
out.on('error', reject);
|
|
|
|
|
});
|
|
|
|
|
try {
|
|
|
|
|
while (true) {
|
|
|
|
|
const { value, done } = await reader.read();
|
|
|
|
|
if (done) break;
|
|
|
|
|
if (!out.write(Buffer.from(value))) {
|
|
|
|
|
// Backpressure — wait for drain so memory stays flat.
|
|
|
|
|
await new Promise(r => out.once('drain', r));
|
|
|
|
|
}
|
|
|
|
|
received += value.byteLength;
|
|
|
|
|
if (onProgress) onProgress({ received, total });
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
out.end();
|
|
|
|
|
}
|
|
|
|
|
await closed;
|
|
|
|
|
return destPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Manual-follow fetch for redirects: UXP strips Authorization across
|
|
|
|
|
// origins (per Adobe security policy), so for any /api/... call that might
|
|
|
|
|
// 302 to S3 we follow the redirect ourselves and DROP the Bearer header
|
|
|
|
|
// on the next hop.
|
|
|
|
|
async function fetchFollow(url, opts) {
|
|
|
|
|
opts = opts || {};
|
|
|
|
|
let current = url;
|
|
|
|
|
let auth = (opts.headers || {})['Authorization'] || null;
|
|
|
|
|
for (let hop = 0; hop < 5; hop++) {
|
|
|
|
|
const r = await fetch(current, Object.assign({}, opts, { redirect: 'manual' }));
|
|
|
|
|
if (r.status >= 300 && r.status < 400 && r.headers.get('location')) {
|
|
|
|
|
const next = r.headers.get('location');
|
|
|
|
|
const absNext = /^https?:\/\//i.test(next) ? next : new URL(next, current).toString();
|
|
|
|
|
const sameHost = (new URL(absNext)).host === (new URL(current)).host;
|
|
|
|
|
opts = Object.assign({}, opts, {
|
|
|
|
|
headers: Object.assign({}, opts.headers || {}, sameHost ? {} : { Authorization: undefined }),
|
|
|
|
|
});
|
|
|
|
|
if (!sameHost) delete opts.headers.Authorization;
|
|
|
|
|
current = absNext;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
throw new Error('Too many redirects');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the new ProjectItem after importFiles. Premiere's UXP importFiles
|
|
|
|
|
// returns only a boolean — we diff the rootItem.getItems() snapshot.
|
|
|
|
|
// (Not currently used but available for callers that need the item.)
|
|
|
|
|
Import.locateImported = async function (project, filename) {
|
|
|
|
|
try {
|
|
|
|
|
const root = await project.getRootItem();
|
|
|
|
|
const items = await root.getItems();
|
|
|
|
|
for (const it of items) {
|
|
|
|
|
const name = await it.getName().catch(() => null);
|
|
|
|
|
if (name && name === filename) return it;
|
|
|
|
|
}
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Talk to Premiere. Lazy-require so the module isn't loaded until the user
|
|
|
|
|
// actually triggers an import (faster panel boot, clearer error if UXP host
|
|
|
|
|
// is missing the API).
|
|
|
|
|
function ppro() {
|
|
|
|
|
if (Import._ppro) return Import._ppro;
|
|
|
|
|
try { Import._ppro = require('premierepro'); }
|
|
|
|
|
catch (e) { throw new Error('UXP premierepro module unavailable: ' + e.message); }
|
|
|
|
|
return Import._ppro;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hand a downloaded file off to Premiere.
|
|
|
|
|
Import.importIntoProject = async function (filePath) {
|
|
|
|
|
const P = ppro();
|
|
|
|
|
const project = await P.Project.getActiveProject();
|
|
|
|
|
if (!project) throw new Error('No active Premiere project. Open or create a project first.');
|
|
|
|
|
const root = await project.getRootItem();
|
|
|
|
|
// suppressUI=true, targetBin=root, asNumberedStills=false.
|
|
|
|
|
const ok = await project.importFiles([filePath], true, root, false);
|
|
|
|
|
if (!ok) throw new Error('Premiere refused to import the file.');
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ── Proxy import: /stream returns a same-host /video URL ──────────
|
|
|
|
|
Import.proxy = async function (asset) {
|
|
|
|
|
const safeName = UI.sanitizeFilename((asset.display_name || asset.filename || asset.id) + '.mp4');
|
2026-05-28 00:43:44 -04:00
|
|
|
const dest = await tempPath(safeName);
|
feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
|
|
|
|
|
UI.showProgress('Resolving proxy URL…', 4);
|
|
|
|
|
const { url } = await API.getProxyUrl(asset.id);
|
|
|
|
|
|
|
|
|
|
UI.showProgress('Downloading ' + safeName + '…', 10);
|
|
|
|
|
const r = await fetchFollow(url, { headers: { Authorization: 'Bearer ' + API.state.apiToken } });
|
|
|
|
|
if (!r.ok) throw new Error('Download HTTP ' + r.status);
|
|
|
|
|
|
|
|
|
|
await streamToFile(r, dest, ({ received, total }) => {
|
|
|
|
|
const pct = total ? 10 + (received / total) * 75 : 10;
|
|
|
|
|
UI.showProgress('Downloading ' + UI.formatBytes(received) + (total ? ' / ' + UI.formatBytes(total) : '') + '…', pct);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
UI.showProgress('Importing into Premiere…', 92);
|
|
|
|
|
await Import.importIntoProject(dest);
|
|
|
|
|
UI.hideProgress();
|
|
|
|
|
UI.toast('Imported: ' + safeName, 'ok');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ── Hi-Res import: /hires returns a presigned S3 URL on broadcastmgmt.cloud
|
|
|
|
|
Import.hires = async function (asset) {
|
|
|
|
|
UI.showProgress('Resolving hi-res URL…', 4);
|
|
|
|
|
const info = await API.getHiresInfo(asset.id);
|
|
|
|
|
const safeName = UI.sanitizeFilename(info.filename || (asset.display_name || asset.id) + '.' + (info.ext || 'mxf'));
|
2026-05-28 00:43:44 -04:00
|
|
|
const dest = await tempPath(safeName);
|
feat(premiere-plugin-uxp): v2.0.0 — UXP port replacing CEP for import
CEP `csInterface.evalScript` callback is broken in Premiere Pro 26.0.x —
nothing called from the panel ever returns, so importFiles deadlocks. Adobe's
path forward is UXP. This is the minimum viable port that restores the
Import Proxy / Import Hi-Res workflow.
Scope (v2.0.0):
- Connect to a Dragonflight server (URL + Bearer token; persisted)
- Asset library (search, refresh, grid with thumbnails)
- Import Proxy via streamed download → Project.importFiles
- Import Hi-Res via presigned S3 URL → Project.importFiles
Layout:
manifest.json UXP v5, host=premierepro, minVersion=26.0.0
index.html Panel shell
styles.css Mirrors web UI dark tokens
src/ui.js DOM helpers, toast, progress, formatting
src/api.js HTTP client (Bearer; manual redirect-follow drops auth
when hopping to a different host per UXP security policy)
src/library.js Asset grid render + selection
src/import-flow.js Streaming download (fs.createWriteStream) +
premierepro.Project.importFiles into rootBin
src/main.js Bootstrap, event wiring
build/pack.mjs Packs into .ccx; installs via UnifiedPluginInstallerAgent
Coexists with services/premiere-plugin/ (CEP) — keeps the CEP panel for any
features that still work there while running v2.0.0 for import. Future v2.x
will add live preview, conform, timeline export, settings.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:19:28 -04:00
|
|
|
|
|
|
|
|
UI.showProgress('Downloading ' + safeName + ' (' + UI.formatBytes(Number(info.file_size || 0)) + ')…', 8);
|
|
|
|
|
// info.url is presigned S3 — DO NOT add Bearer (would break signature).
|
|
|
|
|
const r = await fetchFollow(info.url, {});
|
|
|
|
|
if (!r.ok) throw new Error('Download HTTP ' + r.status);
|
|
|
|
|
|
|
|
|
|
await streamToFile(r, dest, ({ received, total }) => {
|
|
|
|
|
const pct = total ? 8 + (received / total) * 80 : 8;
|
|
|
|
|
UI.showProgress('Downloading ' + UI.formatBytes(received) + (total ? ' / ' + UI.formatBytes(total) : '') + '…', pct);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
UI.showProgress('Importing into Premiere…', 92);
|
|
|
|
|
await Import.importIntoProject(dest);
|
|
|
|
|
UI.hideProgress();
|
|
|
|
|
UI.toast('Hi-res imported: ' + safeName, 'ok');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.Import = Import;
|
|
|
|
|
})();
|