diff --git a/services/premiere-plugin-uxp/src/import-flow.js b/services/premiere-plugin-uxp/src/import-flow.js index 11ae88f..48f3e97 100644 --- a/services/premiere-plugin-uxp/src/import-flow.js +++ b/services/premiere-plugin-uxp/src/import-flow.js @@ -1,34 +1,45 @@ -// import-flow.js — v2.1.1 +// import-flow.js — v2.1.2 // Download asset → write to temp → importFiles() via UXP premierepro. +// NOTE: `path` module is NOT available in PPro 26 UXP. Use _join() helper. // Also exposes _tempPath / _streamToFile for timeline.js batch relink. (function () { const Import = {}; - const fs = require('fs'); - const path = require('path'); - let os; try { os = require('os'); } catch (_) { os = {}; } - let uxpFs; try { uxpFs = require('uxp').storage.localFileSystem; } catch (_) { uxpFs = null; } + const fs = require('fs'); + // `path` is stripped in this UXP build — manual join instead. + let os; try { os = require('os'); } catch (_) { os = {}; } + let uxpFs; try { uxpFs = require('uxp').storage.localFileSystem; } catch (_) { uxpFs = null; } + + // Simple path join that works on Windows (backslash) without require('path'). + function _join(base, name) { + const sep = base.indexOf('/') === -1 ? '\\' : '/'; + return base.replace(/[\\/]+$/, '') + sep + name; + } // ── Temp folder ────────────────────────────────────────────────── async function _getTempBase() { + // 1. os.tmpdir (may exist in some UXP builds) try { if (os.tmpdir) { const t = os.tmpdir(); if (typeof t === 'string' && t.length) return t; } } catch (_) {} + // 2. UXP storage API if (uxpFs && uxpFs.getTemporaryFolder) { try { const tmp = await uxpFs.getTemporaryFolder(); if (tmp && tmp.nativePath) return tmp.nativePath; } catch (_) {} } + // 3. Windows env vars (always present under PPro on Windows) 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'); + if (e.LOCALAPPDATA) return e.LOCALAPPDATA + '\\Temp'; } catch (_) {} - try { if (os.homedir) return path.join(os.homedir(), 'AppData', 'Local', 'Temp'); } catch (_) {} + // 4. Homedir fallback (no path.join needed) + 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); + return _join(base, 'dragonflight-' + safeName); }; // ── Stream response body to disk ───────────────────────────────── @@ -39,7 +50,7 @@ let received = 0; const closed = new Promise((resolve, reject) => { out.on('finish', resolve); - out.on('error', reject); + out.on('error', reject); }); try { while (true) { @@ -59,7 +70,6 @@ }; // ── Manual redirect follow ─────────────────────────────────────── - // UXP strips Authorization on cross-origin redirects. Follow manually. async function _fetchFollow(url, opts) { opts = opts || {}; let current = url; @@ -70,7 +80,6 @@ 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(); - // Drop auth on cross-origin hop if (new URL(next).host !== new URL(current).host) { opts = Object.assign({}, opts, { headers: Object.assign({}, opts.headers || {}) }); delete opts.headers['Authorization']; @@ -91,7 +100,6 @@ return Import._ppro_mod; } - // Import a file that is already on disk into the active Premiere project. Import.importIntoProject = async function (filePath) { const P = _ppro(); const project = await P.Project.getActiveProject(); @@ -103,7 +111,6 @@ }; // ── Proxy import ───────────────────────────────────────────────── - // Returns { localPath, safeName } for caller to record import mapping. Import.proxy = async function (asset) { const safeName = UI.sanitizeFilename((asset.display_name || asset.filename || asset.id) + '.mp4'); const dest = await Import._tempPath(safeName); @@ -128,7 +135,6 @@ }; // ── Hi-Res import ──────────────────────────────────────────────── - // Returns { localPath, safeName }. Import.hires = async function (asset) { UI.showProgress('Resolving hi-res URL…', 4); const info = await API.getHiresInfo(asset.id); @@ -136,7 +142,6 @@ const dest = await Import._tempPath(safeName); UI.showProgress('Downloading ' + safeName + ' (' + UI.formatBytes(Number(info.file_size || 0)) + ')…', 8); - // Presigned S3 URL — no Bearer needed const r = await _fetchFollow(info.url, {}); if (!r.ok) throw new Error('Download HTTP ' + r.status);