fix(uxp): null-safe Time object access in readActiveSequence

getStartTime/getEndTime/getInPoint/getOutPoint can return null for
non-clip track items (gaps, transitions) that slip past the
getProjectItem check. Accessing .seconds on null threw a TypeError
that the outer catch swallowed — silently dropping every clip and
leaving clips[] empty, so the export panel never opened.

Also skip clips where all four time values resolve to 0 (filler items).
This commit is contained in:
Zac Gaetano 2026-05-28 11:12:32 -04:00
parent 382f432693
commit 60e5093c6b

View file

@ -1,4 +1,4 @@
// timeline.js — v2.1.5
// timeline.js — v2.1.6
// premierepro API: docs say sync, runtime returns Promises. Await everything.
(function () {
@ -11,23 +11,31 @@
return Timeline._ppro;
}
// Safe helper — returns the numeric seconds value from a Time object,
// or 0 if the object is null/undefined/malformed.
function _secs(t) {
if (!t) return 0;
if (typeof t.seconds === 'number') return t.seconds;
return 0;
}
// ── Read active sequence ─────────────────────────────────────────
Timeline.readActiveSequence = async function () {
const P = ppro();
const project = await P.Project.getActiveProject();
if (!project) throw new Error('No active Premiere project');
const seq = await project.getActiveSequence();
if (!seq) throw new Error('No active sequence');
if (!seq) throw new Error('No active sequence — open a sequence in the timeline first');
const name = seq.name || (await seq.getName && await seq.getName()) || 'Sequence 1';
const name = seq.name || (seq.getName ? await seq.getName() : '') || 'Sequence 1';
const settings = await seq.getSettings();
let frameRate = 29.97;
try {
const fr = settings.videoFrameRate;
const fr = settings && settings.videoFrameRate;
if (fr && typeof fr.seconds === 'number' && fr.seconds > 0) frameRate = 1 / fr.seconds;
} catch (_) {}
const width = settings.videoFrameWidth || 1920;
const height = settings.videoFrameHeight || 1080;
const width = (settings && settings.videoFrameWidth) || 1920;
const height = (settings && settings.videoFrameHeight) || 1080;
const clips = [];
const trackCount = await seq.getVideoTrackCount();
@ -43,21 +51,30 @@
try {
const projItem = await clip.getProjectItem();
if (!projItem) continue;
let filePath = '';
try {
const clipItem = await P.ClipProjectItem.cast(projItem);
filePath = await clipItem.getMediaFilePath() || '';
} catch (_) {}
const clipName = await clip.getName().catch(() => '');
const fileName = clipName || (filePath ? path.basename(filePath) : 'clip');
const startT = await clip.getStartTime();
const endT = await clip.getEndTime();
const inT = await clip.getInPoint();
const outT = await clip.getOutPoint();
const tlIn = startT.seconds;
const tlOut = endT.seconds;
const srcIn = inT.seconds;
const srcOut = outT.seconds;
// Null-safe time access — non-clip items can return null Time objects
const startT = await clip.getStartTime().catch(() => null);
const endT = await clip.getEndTime().catch(() => null);
const inT = await clip.getInPoint().catch(() => null);
const outT = await clip.getOutPoint().catch(() => null);
const tlIn = _secs(startT);
const tlOut = _secs(endT);
const srcIn = _secs(inT);
const srcOut = _secs(outT);
// Skip filler/gap items where all times are zero
if (tlIn === 0 && tlOut === 0 && srcIn === 0 && srcOut === 0 && !filePath) continue;
clips.push({
fileName, filePath, trackIndex: ti,
timelineInSec: tlIn, timelineOutSec: tlOut,
@ -67,7 +84,9 @@
sourceInFrames: Math.round(srcIn * frameRate),
sourceOutFrames: Math.round(srcOut * frameRate),
});
} catch (_) {}
} catch (clipErr) {
console.warn('[df] readActiveSequence: skipped clip on track', ti, '—', clipErr && clipErr.message);
}
}
}
return { sequenceName: name, frameRate, width, height, clips };
@ -131,7 +150,7 @@
Timeline.pushToMAM = async function (seqName, projectId, td) {
const resolved = Library.resolveClipsToAssets(td.clips || []);
const matched = resolved.filter(c => c.asset_id);
if (!matched.length) throw new Error('No clips matched MAM assets — import proxies first');
if (!matched.length) throw new Error('No clips matched MAM assets — import proxies first so the plugin can map file paths to asset IDs');
const seqs = await API.listSequences(projectId);
let seqId;
const existing = seqs.find(s => s.name === seqName);