feat(uxp): v2.2.1 — Export Timeline is now one-click push + render → asset
Contract: clicking Export Timeline does the whole pipeline with no
prompts. Behavior matches what the user actually expected from the
button label:
1. readActiveSequence — pulls the Premiere timeline + clip map
2. resolveExportProject — picks the target MAM project. First run
uses the first project on the server and caches its id in
localStorage (df.uxp.exportProjectId). Subsequent runs reuse
the cache. If the cached project was deleted server-side we
transparently re-pick.
3. Timeline.startConform with sensible defaults:
codec=prores_hq, quality=high, resolution=source, audio=broadcast
This both pushes the sequence + clip rows AND queues a real
conform job (the prior Push-to-MAM button never queued a job,
which is why "no jobs spin up" happened earlier).
4. pollConform every 2s, mapping job progress 20→95% on the
panel progress bar.
5. On completion, toast + Library.refresh() so the rendered hi-res
asset shows up in the grid without needing to click around.
The Conform slide panel stays wired for Advanced → Export & Conform
so power users can still override the codec/preset for one-off jobs.
The Push-only slide panel that this replaces is now orphaned chrome
and will be removed in a later cleanup.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
e4e69973e5
commit
540d333758
2 changed files with 92 additions and 2 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"manifestVersion": 5,
|
||||
"id": "net.wilddragon.dragonflight.uxp",
|
||||
"name": "Dragonflight MAM",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"main": "index.html",
|
||||
"host": {
|
||||
"app": "premierepro",
|
||||
|
|
|
|||
|
|
@ -181,7 +181,10 @@
|
|||
finally { Library._syncActions(); }
|
||||
});
|
||||
|
||||
$('export-timeline-btn').addEventListener('click', openExportPanel);
|
||||
// v2.2.1: Export Timeline is now a single-click pipeline —
|
||||
// push to MAM → start conform → poll → new asset lands in Library.
|
||||
// The Conform slide panel is still wired for Advanced → Export & Conform.
|
||||
$('export-timeline-btn').addEventListener('click', oneClickExport);
|
||||
$('export-conform-btn').addEventListener('click', openConformPanel);
|
||||
$('fetch-relink-btn').addEventListener('click', openRelinkPanel);
|
||||
|
||||
|
|
@ -202,6 +205,93 @@
|
|||
|
||||
let _seqCache = null;
|
||||
|
||||
// ── One-click Export Timeline ────────────────────────────────────
|
||||
//
|
||||
// Contract: clicking Export Timeline reads the active Premiere
|
||||
// sequence, pushes it to MAM, kicks off a hi-res conform render, and
|
||||
// lets the result land as an asset in the Library. No prompts.
|
||||
//
|
||||
// Defaults are baked in (ProRes 422 HQ / source res / broadcast
|
||||
// audio). Advanced → Export & Conform is still wired for fine-grained
|
||||
// control.
|
||||
const LS_EXPORT_PROJECT = 'df.uxp.exportProjectId';
|
||||
const DEFAULT_EXPORT_OPTS = {
|
||||
codec: 'prores_hq',
|
||||
quality: 'high',
|
||||
resolution: 'source',
|
||||
audio: 'broadcast',
|
||||
};
|
||||
|
||||
// Resolve a target MAM project for the export. Cached in localStorage
|
||||
// after first run so subsequent exports require zero clicks. Falls
|
||||
// back to the first project on the server. If the cache references a
|
||||
// project that no longer exists, we transparently re-pick.
|
||||
async function resolveExportProject() {
|
||||
const cached = localStorage.getItem(LS_EXPORT_PROJECT) || '';
|
||||
let projects;
|
||||
try { projects = await API.listProjects(); }
|
||||
catch (e) { throw new Error('Project lookup failed: ' + e.message); }
|
||||
if (!Array.isArray(projects) || !projects.length) {
|
||||
throw new Error('No projects in MAM — create one in the web UI first');
|
||||
}
|
||||
if (cached && projects.some(p => p.id === cached)) return cached;
|
||||
const id = projects[0].id;
|
||||
localStorage.setItem(LS_EXPORT_PROJECT, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
async function oneClickExport() {
|
||||
UI.$('#export-timeline-btn').disabled = true;
|
||||
try {
|
||||
UI.showProgress('Reading Premiere sequence…', 5);
|
||||
let td;
|
||||
try { td = await Timeline.readActiveSequence(); }
|
||||
catch (e) { throw new Error('Timeline read failed: ' + e.message); }
|
||||
if (!td || !td.clips || !td.clips.length) {
|
||||
throw new Error('No clips in active sequence');
|
||||
}
|
||||
|
||||
UI.showProgress('Resolving target project…', 10);
|
||||
const projectId = await resolveExportProject();
|
||||
const seqName = td.sequenceName || 'Premiere Export';
|
||||
|
||||
UI.showProgress('Pushing timeline + queueing render…', 15);
|
||||
let jobId;
|
||||
try {
|
||||
jobId = await Timeline.startConform(projectId, seqName, td, DEFAULT_EXPORT_OPTS);
|
||||
} catch (e) { throw new Error('Export failed: ' + e.message); }
|
||||
if (!jobId) throw new Error('Export started but no job id was returned');
|
||||
|
||||
// Poll the conform job to completion. Map progress 20→95% so the
|
||||
// user sees the bar move during the long render step.
|
||||
UI.showProgress('Rendering hi-res…', 20);
|
||||
await new Promise((resolve) => {
|
||||
Timeline.pollConform(jobId,
|
||||
(progress, status) => {
|
||||
UI.showProgress('Rendering hi-res (' + (status || 'queued') + ')…', 20 + 0.75 * (Number(progress) || 0));
|
||||
},
|
||||
(job) => {
|
||||
UI.hideProgress();
|
||||
if (job && job.status === 'completed') {
|
||||
UI.toast('Rendered: ' + seqName + ' — now in Library', 'ok');
|
||||
const q = ($('search-input') && $('search-input').value) || '';
|
||||
Library.refresh(q).catch(() => {});
|
||||
} else {
|
||||
const why = (job && (job.error || job.message)) || 'unknown reason';
|
||||
UI.toast('Render failed: ' + why, 'error');
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
UI.hideProgress();
|
||||
UI.toast(e.message || 'Export failed', 'error');
|
||||
} finally {
|
||||
UI.$('#export-timeline-btn').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openExportPanel() {
|
||||
UI.showProgress('Reading Premiere sequence…', 20);
|
||||
try { _seqCache = await Timeline.readActiveSequence(); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue