dragonflight/services/premiere-plugin-uxp/src/import-flow.js

159 lines
6.3 KiB
JavaScript
Raw Normal View History

// import-flow.js — v2.1.6
// premierepro API: docs say sync, runtime returns Promises. Await everything.
(function () {
const Import = {};
const fs = require('fs');
// window.path is a UXP global (v6.4+) — no require('path')
let os; try { os = require('os'); } catch (_) { os = {}; }
let uxpFs; try { uxpFs = require('uxp').storage.localFileSystem; } catch (_) { uxpFs = null; }
// ── Temp folder ──────────────────────────────────────────────────
async function _getTempBase() {
if (uxpFs && uxpFs.getTemporaryFolder) {
try {
const tmp = await uxpFs.getTemporaryFolder();
if (tmp && tmp.nativePath) return tmp.nativePath;
} catch (_) {}
}
try {
const e = (typeof process !== 'undefined' && process.env) || {};
if (e.TEMP && e.TEMP.length) return e.TEMP;
if (e.TMP && e.TMP.length) return e.TMP;
if (e.LOCALAPPDATA) return e.LOCALAPPDATA + '\\Temp';
} catch (_) {}
try {
if (os.homedir) {
const h = os.homedir();
if (h) return h + '\\AppData\\Local\\Temp';
}
} catch (_) {}
throw new Error('Cannot find writable temp folder');
}
Import._tempPath = async function (safeName) {
const base = await _getTempBase();
return path.join(base, 'dragonflight-' + safeName);
};
// Returns true if the path already exists on disk.
Import._fileExists = async function (filePath) {
try { await fs.stat(filePath); return true; } catch (_) { return false; }
};
// Write ArrayBuffer to disk via fs.writeFile.
// If the file is locked (EBUSY — Premiere already has it open from a
// previous import) we treat that as success: the bytes are already there.
Import._writeBuffer = async function (destPath, arrayBuffer) {
try {
await fs.writeFile(destPath, arrayBuffer);
} catch (e) {
const busy = e.code === 'EBUSY' || /resource busy/i.test(String(e.message));
if (!busy) throw e;
// File locked by Premiere — it's already there, proceed.
console.warn('[df] _writeBuffer EBUSY on', destPath, '— using existing file');
}
return destPath;
};
// Fetch — no redirect option (not supported in UXP)
// addAuth: only for same-origin proxy URLs, NOT for S3 presigned URLs
async function _fetch(url, addAuth) {
const headers = {};
if (addAuth && API.state.apiToken) {
headers['Authorization'] = 'Bearer ' + API.state.apiToken;
}
const r = await fetch(url, { headers });
if (!r.ok) throw new Error('HTTP ' + r.status + ' fetching ' + url);
return r;
}
// ── premierepro lazy require ─────────────────────────────────────
function _ppro() {
if (Import._ppro_mod) return Import._ppro_mod;
try { Import._ppro_mod = require('premierepro'); }
catch (e) { throw new Error('UXP premierepro unavailable: ' + e.message); }
return Import._ppro_mod;
}
// Import a file into the active Premiere project.
// ALL premierepro calls must be awaited — runtime returns Promises
// even though docs list them as synchronous.
// importFiles returns false when the file is already in the project — not an error.
Import.importIntoProject = async function (filePath) {
const P = _ppro();
const project = await P.Project.getActiveProject();
if (!project) throw new Error('No active Premiere project');
const root = await project.getRootItem();
await project.importFiles([filePath], true, root, false);
// Return value false = already in project. Either way, we're done.
return true;
};
// ── Proxy import ─────────────────────────────────────────────────
Import.proxy = async function (asset) {
const safeName = UI.sanitizeFilename((asset.display_name || asset.filename || asset.id) + '.mp4');
const dest = await Import._tempPath(safeName);
const alreadyOnDisk = await Import._fileExists(dest);
if (alreadyOnDisk) {
// File exists from a previous import — skip download, just (re-)import.
UI.showProgress('Importing into Premiere…', 92);
} else {
UI.showProgress('Resolving proxy URL…', 4);
const { url } = await API.getProxyUrl(asset.id);
UI.showProgress('Downloading ' + safeName + '…', 10);
const r = await _fetch(url, true); // same-origin — add auth
UI.showProgress('Writing to disk…', 70);
const buf = await r.arrayBuffer();
await Import._writeBuffer(dest, buf);
UI.showProgress('Importing into Premiere…', 92);
}
await Import.importIntoProject(dest);
UI.hideProgress();
UI.toast('Imported: ' + safeName, 'ok');
return { localPath: dest, safeName };
};
// ── Hi-Res import ────────────────────────────────────────────────
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'));
const dest = await Import._tempPath(safeName);
const alreadyOnDisk = await Import._fileExists(dest);
if (alreadyOnDisk) {
UI.showProgress('Importing into Premiere…', 92);
} else {
UI.showProgress('Downloading ' + safeName + ' (' + UI.formatBytes(Number(info.file_size || 0)) + ')…', 8);
const r = await _fetch(info.url, false); // presigned S3 — no auth
UI.showProgress('Writing to disk…', 75);
const buf = await r.arrayBuffer();
await Import._writeBuffer(dest, buf);
UI.showProgress('Importing into Premiere…', 92);
}
await Import.importIntoProject(dest);
UI.hideProgress();
UI.toast('Hi-res imported: ' + safeName, 'ok');
return { localPath: dest, safeName };
};
// Used by timeline.js batch relink
Import._streamToFile = async function (response, destPath /*, onProgress */) {
const buf = await response.arrayBuffer();
await Import._writeBuffer(destPath, buf);
return destPath;
};
window.Import = Import;
})();