// import-flow.js — v2.1.3 // Fixes: // • require('path') → window.path (global, no require needed, UXP v6.4+) // • fs.createWriteStream does NOT exist in UXP fs — replaced with // fd-based chunked write: fs.open → loop fs.write(fd,buf,...) → fs.close // • os.tmpdir() not documented → use os.homedir() / process.env.TEMP fallback (function () { const Import = {}; const fs = require('fs'); // path is window.path (global) — no require('path') in UXP // os.tmpdir() missing from docs; os.homedir() is available let os; try { os = require('os'); } catch (_) { os = {}; } let uxpFs; try { uxpFs = require('uxp').storage.localFileSystem; } catch (_) { uxpFs = null; } // ── Temp folder ────────────────────────────────────────────────── // os.tmpdir() not in UXP docs → fall through to other methods async function _getTempBase() { // 1. UXP storage API (most portable) if (uxpFs && uxpFs.getTemporaryFolder) { try { const tmp = await uxpFs.getTemporaryFolder(); if (tmp && tmp.nativePath) return tmp.nativePath; } catch (_) {} } // 2. Windows env vars (always set under PPro on Windows) 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 (_) {} // 3. os.homedir() is documented in UXP os module try { if (os.homedir) { const h = os.homedir(); if (h) return h + '\\AppData\\Local\\Temp'; } } catch (_) {} throw new Error('Cannot find writable temp folder'); } // window.path is a documented UXP global (v6.4+) — no require needed Import._tempPath = async function (safeName) { const base = await _getTempBase(); return path.join(base, 'dragonflight-' + safeName); }; // ── Stream response body to disk via fd-based chunked write ────── // fs.createWriteStream does NOT exist in UXP's require('fs'). // Use open() → write() chunks → close() instead. // Chunk size 256 KB — balances memory vs syscall overhead. Import._streamToFile = async function (response, destPath, onProgress) { const total = Number(response.headers.get('content-length') || 0); const reader = response.body.getReader(); // Open file for writing (create/truncate) const fd = await fs.open(destPath, 'w'); let received = 0; let filePos = 0; try { while (true) { const { value, done } = await reader.read(); if (done) break; // value is Uint8Array; fs.write needs ArrayBuffer const buf = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength); const { bytesWritten } = await fs.write(fd, buf, 0, buf.byteLength, filePos); filePos += bytesWritten; received += value.byteLength; if (onProgress) onProgress({ received, total }); } } finally { await fs.close(fd); } return destPath; }; // ── Manual redirect follow ─────────────────────────────────────── async function _fetchFollow(url, opts) { opts = opts || {}; let current = url; for (let hop = 0; hop < 6; hop++) { const r = await fetch(current, Object.assign({}, opts, { redirect: 'manual' })); if (r.status >= 200 && r.status < 300) return r; if (r.status >= 300 && r.status < 400) { const loc = r.headers.get('location'); if (!loc) throw new Error('Redirect with no Location'); const next = /^https?:\/\//i.test(loc) ? loc : new URL(loc, current).toString(); if (new URL(next).host !== new URL(current).host) { opts = Object.assign({}, opts, { headers: Object.assign({}, opts.headers || {}) }); delete opts.headers['Authorization']; } current = next; continue; } throw new Error('HTTP ' + r.status); } throw new Error('Too many redirects'); } // ── 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 already on disk into the active Premiere project. Import.importIntoProject = async function (filePath) { const P = _ppro(); const project = P.Project.getActiveProject(); if (!project) throw new Error('No active Premiere project'); const root = project.getRootItem(); const ok = await project.importFiles([filePath], true, root, false); if (!ok) throw new Error('Premiere refused to import file'); 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); 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 Import._streamToFile(r, dest, ({ received, total }) => { const pct = total ? 10 + (received / total) * 75 : 10; UI.showProgress(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'); 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); UI.showProgress('Downloading ' + safeName + ' (' + UI.formatBytes(Number(info.file_size || 0)) + ')…', 8); const r = await _fetchFollow(info.url, {}); if (!r.ok) throw new Error('Download HTTP ' + r.status); await Import._streamToFile(r, dest, ({ received, total }) => { const pct = total ? 8 + (received / total) * 80 : 8; UI.showProgress(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'); return { localPath: dest, safeName }; }; window.Import = Import; })();