Files
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

2383 lines
173 KiB
JavaScript
Executable File

(function (root, factory) {
const api = factory();
if (typeof module === "object" && module.exports) module.exports = api;
root.PremiereMcpAdvancedWorkflows = api;
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
"use strict";
const MAX_SELECTION_ITEMS = 64;
const MAX_PROJECT_ITEMS = 4096;
const MAX_VIEW_ITEMS = 256;
const MAX_PROJECT_TREE_ITEMS = 512;
const MAX_PROJECT_TREE_DEPTH = 16;
const MAX_KEYFRAMES = 256;
const MAX_MARKERS = 2048;
const MAX_SEQUENCES = 1024;
const MAX_BIN_CHILDREN = 1024;
const MAX_TIMELINE_TRACKS = 64;
const MAX_TIMELINE_ITEMS = 512;
const MAX_DISPLAY_FORMAT_CODE = 2147483647;
function createAdvancedWorkflowDefinitions(deps) {
const ppro = deps.ppro, Protocol = deps.Protocol, workspace = deps.workspace, events = deps.events;
const appendLocks = new Map();
const parameterTimeVaryingLocks = new Map();
const pointParameterLocks = new Map();
const colorParameterLocks = new Map();
const colorLabelLocks = deps.colorLabelLocks && typeof deps.colorLabelLocks.withProjectItemColorLabelLock === "function"
? deps.colorLabelLocks
: { withProjectItemColorLabelLock: withColorLabelLock };
const colorLabelFallbackTails = new Map();
const definitions = {
"projectSelection.views": { readOnly: true, minHostVersion: "25.6.0", probe: canUseProjectViews, handler: listProjectViews },
"projectSelection.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canUseProjectViews, handler: inspectProjectSelection },
"projectTree.inspect": { readOnly: true, minHostVersion: "26.3.0", probe: canUseProjectTree, handler: inspectProjectTree },
"markers.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseMarkers, handler: inspectMarkers },
"markers.add": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseMarkers, handler: addMarker },
"markers.addBeatGrid": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseMarkers, handler: addBeatGrid },
"markers.update": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseMarkers, handler: updateMarker },
"markers.remove": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseMarkers, handler: removeMarker },
"markers.removeMany": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseMarkers, handler: removeManyMarkers },
"bins.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseBins, handler: inspectBin },
"bins.create": { destructive: true, undoable: true, minHostVersion: "25.6.0", probe: canUseBins, handler: createBin },
"bins.createSmart": { destructive: true, undoable: true, minHostVersion: "25.6.0", probe: canUseBins, handler: createSmartBin },
"bins.rename": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseBins, handler: renameProjectItem },
"bins.move": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseBins, handler: moveProjectItem },
"bins.color": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseBins, handler: colorProjectItem },
"bins.remove": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseBins, handler: removeProjectItem },
"sequenceSettings.get": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "26.2.0", probe: canUseSequenceSettings, handler: getSequenceSettings },
"sequenceSettings.update": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "26.2.0", probe: canUseSequenceSettings, handler: updateSequenceSettings },
"sequence.displayFormat.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseSequenceDisplayFormats, handler: inspectSequenceDisplayFormats },
"sequence.displayFormat.update": { destructive: true, undoable: true, idempotent: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseSequenceDisplayFormats, handler: updateSequenceDisplayFormats },
"project.import": { destructive: true, undoable: false, requiresWorkspace: true, minHostVersion: "25.6.0", probe: canImportProjectMedia, handler: importProjectMedia },
"parameters.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: inspectParameter },
"parameters.point.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUsePointParameters, handler: inspectPointParameter },
"parameters.point.displacement.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUsePointParameters, handler: inspectPointParameterDisplacement },
"parameters.point.set": { destructive: true, undoable: true, idempotent: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUsePointParameters, handler: setPointParameter },
"parameters.color.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseColorParameters, handler: inspectColorParameter },
"parameters.color.set": { destructive: true, undoable: true, idempotent: true, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canUseColorParameters, handler: setColorParameter },
"parameters.set": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: setParameterValue },
"parameters.keyframeAdd": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: addParameterKeyframe },
"parameters.keyframeRemove": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: removeParameterKeyframe },
"parameters.keyframeRemoveRange": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: removeParameterKeyframeRange },
"parameters.keyframeInterpolation": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: setParameterInterpolation },
"parameters.keyframe.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: inspectParameterKeyframe },
"parameters.timeVarying.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: inspectParameterTimeVarying },
"parameters.timeVarying.set": { destructive: true, undoable: true, idempotent: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseParameters, handler: setParameterTimeVarying },
"trackItem.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseTrackItems, handler: inspectTrackItem },
"trackItem.update": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseTrackItems, handler: updateTrackItem },
"trackItem.splitEdit": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseTrackItems, handler: makeSplitEdit },
"timeline.insert": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseSequenceEditor, handler: insertTimelineItem },
"timeline.overwrite": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseSequenceEditor, handler: overwriteTimelineItem },
"timeline.cloneSelection": { destructive: true, undoable: true, minHostVersion: "25.6.0", probe: canUseSequenceEditor, handler: cloneTimelineSelection },
"timeline.removeSelection": { destructive: true, undoable: true, minHostVersion: "25.6.0", probe: canUseSequenceEditor, handler: removeTimelineSelection },
"timeline.mogrtPath": { destructive: true, undoable: false, requiresWorkspace: true, minHostVersion: "25.6.0", probe: canUseMogrtPath, handler: insertMogrtPath },
"timeline.mogrtLibrary": { destructive: true, undoable: false, minHostVersion: "25.6.0", probe: canUseMogrtLibrary, handler: insertMogrtLibrary },
"timeline.structure.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canInspectTimelineStructure, handler: inspectTimelineStructure },
"sequences.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canUseSequences, handler: inspectSequences },
"sequences.createEmpty": { destructive: true, undoable: false, idempotent: true, minHostVersion: "26.3.0", probe: canCreateEmptySequence, handler: createEmptySequence },
"sequences.createFromMedia": { destructive: true, undoable: false, minHostVersion: "25.6.0", probe: canUseSequences, handler: createSequenceFromMedia },
"silence.deriveSequence": { destructive: true, undoable: false, targetCapabilityProbe: true, minHostVersion: "26.3.0", probe: canDeriveSilenceSequence, handler: deriveSilenceSequence },
"sequences.clone": { destructive: true, undoable: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseSequences, handler: cloneSequence },
"sequences.subsequence": { destructive: true, undoable: false, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseSequences, handler: createSubsequence },
"sequences.activate": { idempotent: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseSequences, handler: activateSequence },
"sequences.open": { idempotent: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseSequences, handler: openSequence },
"sequences.close": { idempotent: true, targetCapabilityProbe: true, minHostVersion: "26.2.0", probe: canCloseSequence, handler: closeSequence },
"sequences.delete": { destructive: true, undoable: false, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canUseSequences, handler: deleteSequence },
"encoder.preflight": { readOnly: true, conditionalWorkspace: true, minHostVersion: "25.6.0", probe: canUseEncoder, handler: encoderPreflight },
"encoder.jobs": { readOnly: true, minHostVersion: "25.6.0", probe: canTrackEncoderJobs, handler: inspectEncoderJobs },
"encoder.wait": { readOnly: true, minHostVersion: "25.6.0", probe: canTrackEncoderJobs, handler: waitForEncoderJob },
"encoder.sequence": { destructive: true, undoable: false, requiresWorkspace: true, minHostVersion: "25.6.0", probe: canUseEncoder, handler: encodeSequence },
"encoder.projectItem": { destructive: true, undoable: false, targetCapabilityProbe: true, requiresWorkspace: true, minHostVersion: "25.6.0", probe: canUseEncoder, handler: encodeProjectItem },
"encoder.file": { destructive: true, undoable: false, requiresWorkspace: true, minHostVersion: "25.6.0", probe: canUseEncoder, handler: encodeFile }
};
async function activeProject(requireTransactions) {
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
if (requireTransactions && (typeof project.lockedAccess !== "function" || typeof project.executeTransaction !== "function")) {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose locked undoable transactions");
}
return project;
}
async function activeContext(requireTransactions) {
const project = await activeProject(requireTransactions), sequence = await project.getActiveSequence();
if (!sequence) throw commandError("UXP_NO_ACTIVE_SEQUENCE", "No active sequence");
return { project, sequence };
}
function commitActions(project, label, actions) {
// Every caller creates the actions and invokes this helper from the same
// lexical project.lockedAccess() callback; the helper only centralizes
// compound-action rejection and commit checks.
// eslint-disable-next-line @adobe/premierepro/prefer-locked-access-wrapper
const committed = project.executeTransaction((compoundAction) => {
for (const action of actions) {
if (!action || compoundAction.addAction(action) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected an action in " + label);
}
}
}, label);
if (!committed) throw commandError("UXP_TRANSACTION_FAILED", "Premiere did not commit " + label);
}
function tick(seconds, name) {
const value = finiteNumber(seconds, name || "seconds", -86400, 86400);
if (!ppro.TickTime || typeof ppro.TickTime.createWithSeconds !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot create TickTime values");
}
return ppro.TickTime.createWithSeconds(value);
}
function tickSeconds(value) {
const seconds = value && Number(value.seconds);
return Number.isFinite(seconds) ? seconds : null;
}
function guidString(value) {
if (value == null) return "";
try { return typeof value.toString === "function" ? String(value.toString()) : String(value); } catch (_) { return ""; }
}
function guidFromString(value, name) {
const text = boundedString(value, name, 128);
if (!ppro.Guid || typeof ppro.Guid.fromString !== "function") return text;
try { return ppro.Guid.fromString(text); } catch (_) { throw commandError("UXP_INVALID_ARGUMENT", name + " is not a valid GUID"); }
}
async function projectItemId(item) {
if (!item || typeof item.getId !== "function") return "";
const value = await item.getId();
return value == null ? "" : String(value);
}
async function projectItemSnapshot(item) {
let colorLabelIndex = null, parentId = null;
try { if (typeof item.getColorLabelIndex === "function") colorLabelIndex = await item.getColorLabelIndex(); } catch (_) {}
try { if (typeof item.getParentBin === "function") parentId = await projectItemId(await item.getParentBin()); } catch (_) {}
return { id: await projectItemId(item), name: String(item && item.name || ""), type: item && item.type != null ? item.type : null, colorLabelIndex, parentId };
}
async function safeProjectItemSnapshots(items) {
const snapshots = [];
for (const item of items || []) {
try { snapshots.push(await projectItemSnapshot(item)); }
catch (_) {
let name = "", type = null;
try { name = String(item && item.name || ""); } catch (_) {}
try { type = item && item.type != null ? item.type : null; } catch (_) {}
snapshots.push({ id: "", name, type, colorLabelIndex: null, parentId: null });
}
}
return snapshots;
}
async function safeProjectItemIds(items) {
const ids = [];
for (const item of items || []) {
try {
const id = await projectItemId(item);
if (id) ids.push(id);
} catch (_) {}
}
return ids;
}
function isFolder(item) {
if (!ppro.FolderItem || typeof ppro.FolderItem.cast !== "function") return false;
try { return !!ppro.FolderItem.cast(item); } catch (_) { return false; }
}
function asFolder(item, label) {
if (!ppro.FolderItem || typeof ppro.FolderItem.cast !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Folder APIs are unavailable");
try {
const folder = ppro.FolderItem.cast(item);
if (folder) return folder;
} catch (_) {}
throw commandError("UXP_TARGET_UNSUPPORTED", label + " is not a project bin");
}
function asClip(item, label) {
if (!ppro.ClipProjectItem || typeof ppro.ClipProjectItem.cast !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Clip project-item APIs are unavailable");
try {
const clip = ppro.ClipProjectItem.cast(item);
if (clip) return clip;
} catch (_) {}
throw commandError("UXP_TARGET_UNSUPPORTED", label + " is not a media clip");
}
async function selectedProjectItems(project, viewId) {
if (!ppro.ProjectUtils) throw commandError("UXP_COMMAND_UNAVAILABLE", "Project panel selection APIs are unavailable");
let selection;
if (viewId) {
if (typeof ppro.ProjectUtils.getSelectionFromViewId !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "View-specific selection is unavailable");
selection = await ppro.ProjectUtils.getSelectionFromViewId(guidFromString(viewId, "viewId"));
} else {
if (typeof ppro.ProjectUtils.getSelection !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Project panel selection is unavailable");
selection = await ppro.ProjectUtils.getSelection(project);
}
const items = selection && typeof selection.getItems === "function" ? Array.from(await selection.getItems() || []) : [];
if (items.length > MAX_VIEW_ITEMS) throw commandError("UXP_SELECTION_TOO_LARGE", "Project panel selection exceeds " + MAX_VIEW_ITEMS + " items");
return items;
}
async function findProjectItem(project, wantedId) {
const id = boundedString(wantedId, "projectItemId", 512);
try {
const selected = await selectedProjectItems(project);
for (const item of selected) if (await projectItemId(item) === id) return item;
} catch (_) {}
const root = await project.getRootItem(), queue = root ? [root] : [];
let visited = 0;
while (queue.length) {
const folder = queue.shift();
const children = folder && typeof folder.getItems === "function" ? Array.from(await folder.getItems() || []) : [];
for (const item of children) {
visited += 1;
if (visited > MAX_PROJECT_ITEMS) throw commandError("UXP_PROJECT_TOO_LARGE", "Project-item lookup exceeded " + MAX_PROJECT_ITEMS + " entries; select the target in its Project view");
if (await projectItemId(item) === id) return item;
if (isFolder(item)) queue.push(item);
}
}
throw commandError("UXP_TARGET_NOT_FOUND", "projectItemId was not found");
}
async function resolveProjectItem(project, id, requireOneSelection) {
if (id) return findProjectItem(project, id);
const items = await selectedProjectItems(project);
if (requireOneSelection !== false && items.length !== 1) throw commandError("UXP_INVALID_ARGUMENT", "Select exactly one Project item or pass projectItemId");
return requireOneSelection === false ? items : items[0];
}
async function resolveFolder(project, id, label) {
return id ? asFolder(await findProjectItem(project, id), label || "projectItemId") : asFolder(await project.getRootItem(), "project root");
}
async function sequenceSnapshot(sequence) {
return { id: guidString(sequence && sequence.guid), name: String(sequence && sequence.name || "") };
}
async function safeSequenceSnapshot(sequence) {
if (!sequence) return null;
try {
if (sequence.guid == null && sequence.id != null) return { id: String(sequence.id), name: String(sequence.name || "") };
return await sequenceSnapshot(sequence);
} catch (_) { return null; }
}
async function boundedSequences(project) {
const values = typeof project.getSequences === "function" ? Array.from(await project.getSequences() || []) : [];
if (values.length > MAX_SEQUENCES) throw commandError("UXP_PROJECT_TOO_LARGE", "Sequence lookup exceeds " + MAX_SEQUENCES + " entries");
return values;
}
async function listSequences(project) {
const values = await boundedSequences(project);
const result = [];
for (const sequence of values) result.push(await sequenceSnapshot(sequence));
return result;
}
async function resolveSequence(project, sequenceId) {
if (!sequenceId) {
const active = await project.getActiveSequence();
if (!active) throw commandError("UXP_NO_ACTIVE_SEQUENCE", "No active sequence");
return active;
}
const wanted = boundedString(sequenceId, "sequenceId", 128), sequences = await boundedSequences(project);
for (const sequence of sequences) if (guidString(sequence.guid) === wanted) return sequence;
throw commandError("UXP_TARGET_NOT_FOUND", "sequenceId was not found");
}
async function trackItemAt(sequence, mediaType, trackIndex, clipIndex) {
const title = mediaType === "video" ? "Video" : "Audio", countMethod = "get" + title + "TrackCount", trackMethod = "get" + title + "Track";
const count = await sequence[countMethod]();
if (trackIndex >= count) throw commandError("UXP_TARGET_NOT_FOUND", mediaType + " trackIndex is out of range");
const track = await sequence[trackMethod](trackIndex), itemType = ppro.Constants && ppro.Constants.TrackItemType;
if (!track || !itemType || itemType.CLIP == null || typeof track.getTrackItems !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Clip track-item APIs are unavailable");
const items = Array.from(await track.getTrackItems(itemType.CLIP, false) || []);
if (!items[clipIndex]) throw commandError("UXP_TARGET_NOT_FOUND", "clipIndex is out of range");
return items[clipIndex];
}
async function selectedTrackItems(sequence) {
const selection = await sequence.getSelection(), items = selection && typeof selection.getTrackItems === "function" ? Array.from(await selection.getTrackItems() || []) : [];
if (!items.length) throw commandError("UXP_EMPTY_SELECTION", "Select at least one timeline item");
if (items.length > MAX_SELECTION_ITEMS) throw commandError("UXP_SELECTION_TOO_LARGE", "Select at most " + MAX_SELECTION_ITEMS + " timeline items");
return { selection, items };
}
function timelineTrackIndices(value) {
if (value == null) return null;
if (!Array.isArray(value) || !value.length || value.length > MAX_TIMELINE_TRACKS) {
throw commandError("UXP_INVALID_ARGUMENT", "trackIndices must contain 1-" + MAX_TIMELINE_TRACKS + " indices");
}
const seen = new Set(), indices = [];
for (let index = 0; index < value.length; index += 1) {
const trackIndex = nonNegativeInt(value[index], "trackIndices[" + index + "]");
if (seen.has(trackIndex)) throw commandError("UXP_INVALID_ARGUMENT", "trackIndices must not contain duplicate indices");
seen.add(trackIndex); indices.push(trackIndex);
}
return indices.sort((left, right) => left - right);
}
async function inspectTimelineStructure(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "expectedSequenceId", "mediaType", "trackIndices", "includeEmptyTracks", "includeSourceProjectItems", "includeSourceProjectItemClassification", "includeSourceProjectItemContentType", "includeSourceNestedSequenceIdentity", "maxItems"]);
const project = await activeProject(false), sequence = await resolveSequence(project, args.sequenceId);
const sequenceId = guidString(sequence.guid), expectedSequenceId = args.expectedSequenceId == null ? null : boundedString(args.expectedSequenceId, "expectedSequenceId", 128);
if (expectedSequenceId && expectedSequenceId !== sequenceId) {
throw commandError("UXP_STALE_SEQUENCE", "The requested sequence identity no longer matches; inspect the current timeline and retry");
}
const mediaType = enumValue(args.mediaType == null ? "all" : args.mediaType, "mediaType", ["all", "video", "audio"]);
const trackIndices = timelineTrackIndices(args.trackIndices), includeEmptyTracks = optionalBoolean(args.includeEmptyTracks, false, "includeEmptyTracks"), includeSourceProjectItems = optionalBoolean(args.includeSourceProjectItems, false, "includeSourceProjectItems"), includeSourceProjectItemClassification = optionalBoolean(args.includeSourceProjectItemClassification, false, "includeSourceProjectItemClassification"), includeSourceProjectItemContentType = optionalBoolean(args.includeSourceProjectItemContentType, false, "includeSourceProjectItemContentType"), includeSourceNestedSequenceIdentity = optionalBoolean(args.includeSourceNestedSequenceIdentity, false, "includeSourceNestedSequenceIdentity");
if (includeSourceProjectItemClassification && !includeSourceProjectItems) {
throw commandError("UXP_INVALID_ARGUMENT", "includeSourceProjectItemClassification requires includeSourceProjectItems");
}
if (includeSourceProjectItemContentType && !includeSourceProjectItems) {
throw commandError("UXP_INVALID_ARGUMENT", "includeSourceProjectItemContentType requires includeSourceProjectItems");
}
if (includeSourceNestedSequenceIdentity && (!includeSourceProjectItems || !includeSourceProjectItemClassification)) {
throw commandError("UXP_INVALID_ARGUMENT", "includeSourceNestedSequenceIdentity requires source Project-item IDs and classification");
}
const maxItems = args.maxItems == null ? 128 : boundedInt(args.maxItems, "maxItems", 1, MAX_TIMELINE_ITEMS);
const itemType = ppro.Constants && ppro.Constants.TrackItemType;
if (!itemType || itemType.CLIP == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "Clip track-item APIs are unavailable");
const groups = mediaType === "all" ? ["video", "audio"] : [mediaType], tracks = [], trackCounts = {};
let itemCount = 0, emptyTracksOmitted = 0;
for (const currentMediaType of groups) {
const title = currentMediaType === "video" ? "Video" : "Audio", countMethod = "get" + title + "TrackCount", trackMethod = "get" + title + "Track";
if (typeof sequence[countMethod] !== "function" || typeof sequence[trackMethod] !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", currentMediaType + " track inspection APIs are unavailable");
}
trackCounts[currentMediaType] = nonNegativeInt(await sequence[countMethod](), currentMediaType + " track count");
}
const requestedTrackCount = trackIndices == null
? groups.reduce((total, currentMediaType) => total + trackCounts[currentMediaType], 0)
: trackIndices.length * groups.length;
if (requestedTrackCount > MAX_TIMELINE_TRACKS) {
throw commandError("UXP_TIMELINE_TOO_LARGE", "Requested timeline scope exceeds " + MAX_TIMELINE_TRACKS + " tracks; filter mediaType or trackIndices");
}
for (const currentMediaType of groups) {
const title = currentMediaType === "video" ? "Video" : "Audio", trackMethod = "get" + title + "Track", count = trackCounts[currentMediaType];
const indices = trackIndices == null ? Array.from({ length: count }, (_, index) => index) : trackIndices;
for (const trackIndex of indices) {
if (trackIndex >= count) throw commandError("UXP_TARGET_NOT_FOUND", currentMediaType + " trackIndices contains an out-of-range index");
const track = await sequence[trackMethod](trackIndex);
if (!track || typeof track.getTrackItems !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", currentMediaType + " track-item APIs are unavailable");
const items = Array.from(await track.getTrackItems(itemType.CLIP, false) || []);
if (itemCount + items.length > maxItems) {
throw commandError("UXP_TIMELINE_TOO_LARGE", "Requested timeline scope exceeds maxItems; filter trackIndices or raise maxItems up to " + MAX_TIMELINE_ITEMS);
}
if (!items.length && !includeEmptyTracks) { emptyTracksOmitted += 1; continue; }
const snapshots = [];
for (let clipIndex = 0; clipIndex < items.length; clipIndex += 1) {
snapshots.push(await trackItemSnapshot({ item: items[clipIndex], mediaType: currentMediaType, trackIndex, clipIndex, includeSourceProjectItems, includeSourceProjectItemClassification, includeSourceProjectItemContentType, includeSourceNestedSequenceIdentity }));
}
itemCount += snapshots.length;
tracks.push({ mediaType: currentMediaType, trackIndex, name: String(track.name || ""), itemCount: snapshots.length, items: snapshots });
}
}
return {
sequence: { id: sequenceId, name: String(sequence.name || "") }, mediaType, trackIndices, maxItems,
trackCounts, trackCount: tracks.length, itemCount, emptyTracksOmitted, tracks,
verificationBoundary: "bounded_track_item_readback"
};
}
async function listProjectViews(args) {
assertObject(args); assertOnlyKeys(args, []);
const ids = Array.from(await ppro.ProjectUtils.getProjectViewIds() || []), views = [];
for (const id of ids.slice(0, 64)) {
const project = await ppro.ProjectUtils.getProjectFromViewId(id);
views.push({ viewId: guidString(id), projectId: guidString(project && project.guid), projectName: String(project && project.name || "") });
}
return { count: views.length, limited: ids.length > 64, views };
}
async function inspectProjectSelection(args) {
assertObject(args); assertOnlyKeys(args, ["viewId"]);
const project = await activeProject(false), items = await selectedProjectItems(project, args.viewId), snapshots = [];
for (const item of items) snapshots.push(await projectItemSnapshot(item));
return { viewId: args.viewId || null, count: snapshots.length, items: snapshots, resolver: "project_view_selection" };
}
async function projectTreeSnapshot(item, parentId, depth) {
const id = await projectItemId(item);
if (!id) throw commandError("UXP_ITEM_ID_UNAVAILABLE", "A project-tree item did not expose a stable ID");
let colorLabelIndex = null;
try { if (typeof item.getColorLabelIndex === "function") colorLabelIndex = await item.getColorLabelIndex(); } catch (_) {}
let name = "", type = null;
try { name = String(item && item.name || ""); } catch (_) {}
try { type = item && item.type != null ? item.type : null; } catch (_) {}
return { id, name, type, colorLabelIndex, parentId, depth, isBin: isFolder(item) };
}
async function inspectProjectTree(args) {
assertObject(args); assertOnlyKeys(args, ["maxItems", "maxDepth"]);
const maxItems = args.maxItems == null ? 256 : boundedInt(args.maxItems, "maxItems", 1, MAX_PROJECT_TREE_ITEMS);
const maxDepth = args.maxDepth == null ? 6 : boundedInt(args.maxDepth, "maxDepth", 0, MAX_PROJECT_TREE_DEPTH);
const project = await activeProject(false), root = await project.getRootItem();
if (!root) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere did not return a project root item");
const rootSnapshot = await projectTreeSnapshot(root, null, 0);
if (!rootSnapshot.isBin || typeof root.getItems !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere did not expose a readable project-root folder");
}
const pending = [{ folder: root, id: rootSnapshot.id, depth: 0 }], items = [];
let itemLimitReached = false, depthLimitApplied = false;
while (pending.length && items.length < maxItems) {
const current = pending.shift();
if (current.depth >= maxDepth) { depthLimitApplied = true; continue; }
const children = Array.from(await current.folder.getItems() || []);
for (let index = 0; index < children.length; index += 1) {
if (items.length >= maxItems) { itemLimitReached = true; break; }
const child = children[index], snapshot = await projectTreeSnapshot(child, current.id, current.depth + 1);
items.push(snapshot);
if (snapshot.isBin) {
if (snapshot.depth < maxDepth && typeof child.getItems === "function") pending.push({ folder: child, id: snapshot.id, depth: snapshot.depth });
else if (snapshot.depth >= maxDepth) depthLimitApplied = true;
}
}
if (items.length >= maxItems && pending.length) itemLimitReached = true;
}
return {
root: rootSnapshot, count: items.length, items, maxItems, maxDepth,
truncated: itemLimitReached || depthLimitApplied, itemLimitReached, depthLimitApplied,
verificationBoundary: "bounded_project_tree_item_readback"
};
}
async function markerContext(args, includeMutationFields) {
const allowed = ["ownerType", "sequenceId", "projectItemId", "markerGuid", "expectedName"];
if (includeMutationFields) allowed.push("name", "markerType", "startSeconds", "durationSeconds", "comments", "colorIndex", "operationId");
assertObject(args); assertOnlyKeys(args, allowed);
const ownerType = args.ownerType == null ? "sequence" : enumValue(args.ownerType, "ownerType", ["sequence", "projectItem"]);
const project = await activeProject(includeMutationFields);
const owner = ownerType === "sequence" ? await resolveSequence(project, args.sequenceId) : asClip(await resolveProjectItem(project, args.projectItemId, true), "projectItemId");
const collection = await ppro.Markers.getMarkers(owner);
if (!collection || typeof collection.getMarkers !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere did not return a marker collection");
return { project, owner, ownerType, collection };
}
async function markerSnapshot(marker) {
return {
guid: guidString(marker.guid), name: String(await marker.getName() || ""), type: String(await marker.getType() || ""),
comments: String(await marker.getComments() || ""), colorIndex: await marker.getColorIndex(),
startSeconds: tickSeconds(await marker.getStart()), durationSeconds: tickSeconds(await marker.getDuration())
};
}
async function markerList(collection) {
const result = [];
for (const marker of boundedMarkers(collection)) result.push(await markerSnapshot(marker));
return result;
}
async function markerRemovalSnapshot(marker) {
return {
guid: guidString(marker.guid), name: String(await marker.getName() || ""),
startSeconds: tickSeconds(await marker.getStart()), durationSeconds: tickSeconds(await marker.getDuration())
};
}
function markerGuids(collection) {
return boundedMarkers(collection).map((marker) => guidString(marker.guid));
}
async function findMarker(collection, markerGuid, expectedName) {
const wanted = boundedString(markerGuid, "markerGuid", 128);
for (const marker of boundedMarkers(collection)) {
if (guidString(marker.guid) !== wanted) continue;
if (expectedName != null && String(await marker.getName() || "") !== expectedName) throw commandError("UXP_STALE_MARKER", "Marker name no longer matches expectedName");
return marker;
}
throw commandError("UXP_TARGET_NOT_FOUND", "markerGuid was not found");
}
async function inspectMarkers(args) {
const context = await markerContext(args, false), markers = await markerList(context.collection);
return { ownerType: context.ownerType, count: markers.length, markers };
}
async function addMarker(args) {
const context = await markerContext(args, true), name = boundedString(args.name, "name", 255);
const markerType = args.markerType == null ? String(ppro.Marker && ppro.Marker.MARKER_TYPE_COMMENT || "Comment") : boundedString(args.markerType, "markerType", 128);
const start = tick(finiteNumber(args.startSeconds == null ? 0 : args.startSeconds, "startSeconds", 0, 86400), "startSeconds"), duration = tick(finiteNumber(args.durationSeconds == null ? 0 : args.durationSeconds, "durationSeconds", 0, 86400), "durationSeconds");
const comments = args.comments == null ? "" : boundedStringAllowEmpty(args.comments, "comments", 4000);
return withAppendLock(await markerLockKey(context), async () => {
const before = await markerList(context.collection);
assertAppendCapacity(before, MAX_MARKERS, "Marker creation");
context.project.lockedAccess(() => {
commitActions(context.project, "Add marker", [context.collection.createAddMarkerAction(name, markerType, start, duration, comments)]);
});
const after = await markerList(context.collection), added = after.filter((value) => !before.some((old) => old.guid === value.guid));
return mutationResult(added.length === 1, { added: true, marker: added[0] || null, beforeCount: before.length, afterCount: after.length }, "marker_guid_readback", "Add marker");
});
}
async function addBeatGrid(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "beatTimesSeconds", "offsetSeconds", "namePrefix", "comments", "markerType", "operationId"]);
const context = await markerContext({ ownerType: "sequence", sequenceId: args.sequenceId }, true);
if (!Array.isArray(args.beatTimesSeconds) || !args.beatTimesSeconds.length || args.beatTimesSeconds.length > 512) {
throw commandError("UXP_INVALID_ARGUMENT", "beatTimesSeconds must contain between 1 and 512 entries");
}
const offset = finiteNumber(args.offsetSeconds == null ? 0 : args.offsetSeconds, "offsetSeconds", -86400, 86400);
const prefix = args.namePrefix == null ? "Beat" : boundedString(args.namePrefix, "namePrefix", 64);
const comments = args.comments == null ? "" : boundedStringAllowEmpty(args.comments, "comments", 1000);
const markerType = args.markerType == null ? String(ppro.Marker && ppro.Marker.MARKER_TYPE_COMMENT || "Comment") : boundedString(args.markerType, "markerType", 128);
const times = [], seen = new Set();
for (let index = 0; index < args.beatTimesSeconds.length; index++) {
const value = finiteNumber(args.beatTimesSeconds[index], "beatTimesSeconds[" + index + "]", 0, 86400);
const positioned = value + offset;
if (positioned < 0 || positioned > 86400) throw commandError("UXP_INVALID_ARGUMENT", "offset beat times must remain between 0 and 86400 seconds");
const key = positioned.toFixed(9);
if (seen.has(key)) throw commandError("UXP_INVALID_ARGUMENT", "offset beat times must be unique");
seen.add(key); times.push(positioned);
}
for (let index = 1; index < times.length; index++) {
if (times[index] <= times[index - 1]) throw commandError("UXP_INVALID_ARGUMENT", "beatTimesSeconds must be strictly increasing");
}
return withAppendLock(await markerLockKey(context), async () => {
const before = boundedMarkers(context.collection);
if (before.length + times.length > MAX_MARKERS) throw commandError("UXP_COLLECTION_LIMIT", "Beat marker creation would exceed the " + MAX_MARKERS + " marker limit");
const beforeGuids = new Set(before.map((marker) => guidString(marker.guid)));
context.project.lockedAccess(() => {
const actions = times.map((time, index) => context.collection.createAddMarkerAction(prefix + " " + (index + 1), markerType, tick(time, "beat time"), tick(0, "durationSeconds"), comments));
commitActions(context.project, "Add beat grid markers", actions);
});
let afterMarkers, added;
try {
afterMarkers = boundedMarkers(context.collection);
const addedMarkers = afterMarkers.filter((marker) => !beforeGuids.has(guidString(marker.guid)));
added = [];
for (const marker of addedMarkers) added.push(await markerSnapshot(marker));
} catch (_) {
return mutationResult(false, { added: null, markers: [], beforeCount: before.length, afterCount: null, offsetSeconds: offset }, "beat_marker_guid_and_time_readback", "Add beat grid markers");
}
const addedGuids = new Set(added.map((marker) => marker.guid));
const verified = added.length === times.length && addedGuids.size === added.length
&& added.every((marker, index) => Boolean(marker.guid) && marker.name === prefix + " " + (index + 1)
&& marker.startSeconds != null && numbersEqual(marker.startSeconds, times[index]));
return mutationResult(verified, { added: added.length, markers: added, beforeCount: before.length, afterCount: afterMarkers.length, offsetSeconds: offset }, "beat_marker_guid_and_time_readback", "Add beat grid markers");
});
}
async function updateMarker(args) {
const context = await markerContext(args, true);
const requested = [args.name, args.comments, args.markerType, args.durationSeconds, args.startSeconds, args.colorIndex].filter((value) => value != null);
if (!requested.length) throw commandError("UXP_INVALID_ARGUMENT", "Provide at least one marker field to update");
return withAppendLock(await markerLockKey(context), async () => {
const marker = await findMarker(context.collection, args.markerGuid, args.expectedName);
context.project.lockedAccess(() => {
const actions = [];
if (args.name != null) actions.push(marker.createSetNameAction(boundedString(args.name, "name", 255)));
if (args.comments != null) actions.push(marker.createSetCommentsAction(boundedStringAllowEmpty(args.comments, "comments", 4000)));
if (args.markerType != null) actions.push(marker.createSetTypeAction(boundedString(args.markerType, "markerType", 128)));
if (args.durationSeconds != null) actions.push(marker.createSetDurationAction(tick(finiteNumber(args.durationSeconds, "durationSeconds", 0, 86400), "durationSeconds")));
if (args.startSeconds != null) actions.push(context.collection.createMoveMarkerAction(marker, tick(finiteNumber(args.startSeconds, "startSeconds", 0, 86400), "startSeconds")));
if (args.colorIndex != null) actions.push(marker.createSetColorByIndexAction(boundedInt(args.colorIndex, "colorIndex", 0, 6)));
commitActions(context.project, "Update marker", actions);
});
const updated = await findMarker(context.collection, args.markerGuid), snapshot = await markerSnapshot(updated);
const verified = (args.name == null || snapshot.name === args.name)
&& (args.comments == null || snapshot.comments === args.comments)
&& (args.markerType == null || snapshot.type === args.markerType)
&& (args.durationSeconds == null || numbersEqual(snapshot.durationSeconds, args.durationSeconds))
&& (args.startSeconds == null || numbersEqual(snapshot.startSeconds, args.startSeconds))
&& (args.colorIndex == null || snapshot.colorIndex === args.colorIndex);
return mutationResult(verified, { updated: true, marker: snapshot }, "marker_field_readback", "Update marker");
});
}
async function removeMarker(args) {
const context = await markerContext(args, true);
return withAppendLock(await markerLockKey(context), async () => {
const marker = await findMarker(context.collection, args.markerGuid, args.expectedName);
context.project.lockedAccess(() => {
commitActions(context.project, "Remove marker", [context.collection.createRemoveMarkerAction(marker)]);
});
const remaining = await markerList(context.collection), verified = !remaining.some((value) => value.guid === args.markerGuid);
return mutationResult(verified, { removed: true, markerGuid: args.markerGuid, remainingCount: remaining.length }, "marker_guid_absence_readback", "Remove marker");
});
}
async function removeManyMarkers(args) {
assertObject(args); assertOnlyKeys(args, ["ownerType", "sequenceId", "projectItemId", "markerSnapshots", "confirmDestructive", "operationId"]);
requireDestructiveConfirmation(args.confirmDestructive);
const requested = reviewedMarkerSnapshots(args.markerSnapshots);
const targetGuids = requested.map((value) => value.markerGuid);
const context = await markerContext({
ownerType: args.ownerType,
sequenceId: args.sequenceId,
projectItemId: args.projectItemId,
operationId: args.operationId,
}, true);
return withAppendLock(await markerLockKey(context), async () => {
const currentByGuid = new Map();
for (const marker of boundedMarkers(context.collection)) currentByGuid.set(guidString(marker.guid), marker);
const targets = [];
for (const expected of requested) {
const marker = currentByGuid.get(expected.markerGuid);
if (!marker) throw commandError("UXP_TARGET_NOT_FOUND", "markerSnapshots contains a markerGuid that was not found");
const snapshot = await markerRemovalSnapshot(marker);
assertExpected(snapshot.name, expected.expectedName, "UXP_STALE_MARKER", "Marker name");
assertExpectedNumber(snapshot.startSeconds, expected.expectedStartSeconds, "UXP_STALE_MARKER", "Marker startSeconds");
assertExpectedNumber(snapshot.durationSeconds, expected.expectedDurationSeconds, "UXP_STALE_MARKER", "Marker durationSeconds");
targets.push(marker);
}
context.project.lockedAccess(() => {
commitActions(context.project, "Remove reviewed markers", targets.map((marker) => context.collection.createRemoveMarkerAction(marker)));
});
try {
const remainingGuids = markerGuids(context.collection);
const remainingTargetGuids = remainingGuids.filter((guid) => targetGuids.includes(guid));
const verified = remainingTargetGuids.length === 0;
return mutationResult(verified, {
requested: targetGuids.length, removed: verified ? targetGuids.length : null, markerGuids: targetGuids,
remainingCount: remainingGuids.length, remainingTargetGuids,
}, "marker_guid_absence_readback", "Remove reviewed markers");
} catch (_) {
return mutationResult(false, {
requested: targetGuids.length, removed: null, markerGuids: targetGuids,
remainingCount: null, remainingTargetGuids: null,
}, "marker_guid_absence_readback", "Remove reviewed markers");
}
});
}
async function binChildren(folder) {
const children = Array.from(await folder.getItems() || []);
if (children.length > MAX_BIN_CHILDREN) throw commandError("UXP_BIN_TOO_LARGE", "Bin inspection exceeds " + MAX_BIN_CHILDREN + " immediate children");
const values = [];
for (const child of children) values.push(await projectItemSnapshot(child));
return values;
}
async function inspectBin(args) {
assertObject(args); assertOnlyKeys(args, ["binId"]);
const project = await activeProject(false), folder = await resolveFolder(project, args.binId, "binId"), items = await binChildren(folder);
return { bin: await projectItemSnapshot(folder), count: items.length, items };
}
async function createBin(args) {
assertObject(args); assertOnlyKeys(args, ["parentBinId", "name", "makeUnique", "operationId"]);
const project = await activeProject(true), folder = await resolveFolder(project, args.parentBinId, "parentBinId"), name = boundedString(args.name, "name", 255);
const makeUnique = optionalBoolean(args.makeUnique, true, "makeUnique");
return withAppendLock(appendLockKey(project, "bin", await projectItemId(folder)), async () => {
const before = await binChildren(folder);
assertAppendCapacity(before, MAX_BIN_CHILDREN, "Project-bin creation");
project.lockedAccess(() => {
commitActions(project, "Create project bin", [folder.createBinAction(name, makeUnique)]);
});
const after = await binChildren(folder), added = after.filter((value) => !before.some((old) => old.id === value.id));
return mutationResult(added.length === 1, { created: true, item: added[0] || null }, "bin_child_id_readback", "Create project bin");
});
}
async function createSmartBin(args) {
assertObject(args); assertOnlyKeys(args, ["parentBinId", "name", "searchQuery", "operationId"]);
const project = await activeProject(true), folder = await resolveFolder(project, args.parentBinId, "parentBinId"), name = boundedString(args.name, "name", 255), query = boundedString(args.searchQuery, "searchQuery", 4000);
return withAppendLock(appendLockKey(project, "bin", await projectItemId(folder)), async () => {
const before = await binChildren(folder);
assertAppendCapacity(before, MAX_BIN_CHILDREN, "Smart-bin creation");
project.lockedAccess(() => {
commitActions(project, "Create smart bin", [folder.createSmartBinAction(name, query)]);
});
const after = await binChildren(folder), added = after.filter((value) => !before.some((old) => old.id === value.id));
return mutationResult(added.length === 1, { created: true, item: added[0] || null }, "bin_child_id_readback", "Create smart bin");
});
}
async function renameProjectItem(args) {
assertObject(args); assertOnlyKeys(args, ["projectItemId", "expectedName", "name", "operationId"]);
const project = await activeProject(true), item = await resolveProjectItem(project, args.projectItemId, true), before = await projectItemSnapshot(item);
assertExpected(before.name, args.expectedName, "UXP_STALE_PROJECT_ITEM", "Project item name");
const name = boundedString(args.name, "name", 255);
project.lockedAccess(() => {
const action = typeof item.createSetNameAction === "function" ? item.createSetNameAction(name) : asFolder(item, "projectItemId").createRenameBinAction(name);
commitActions(project, "Rename project item", [action]);
});
const after = await projectItemSnapshot(item);
return mutationResult(after.name === name, { renamed: true, before, after }, "project_item_name_readback", "Rename project item");
}
async function moveProjectItem(args) {
assertObject(args); assertOnlyKeys(args, ["projectItemId", "destinationBinId", "expectedParentId", "operationId"]);
const project = await activeProject(true), item = await resolveProjectItem(project, args.projectItemId, true), before = await projectItemSnapshot(item);
assertExpected(before.parentId, args.expectedParentId, "UXP_STALE_PROJECT_ITEM", "Project item parent");
const destination = await resolveFolder(project, args.destinationBinId, "destinationBinId");
let source = await project.getRootItem();
try { if (typeof item.getParentBin === "function") source = asFolder(await item.getParentBin(), "current parent"); } catch (_) {}
project.lockedAccess(() => {
commitActions(project, "Move project item", [source.createMoveItemAction(item, destination)]);
});
const after = await projectItemSnapshot(item), destinationId = await projectItemId(destination);
return mutationResult(after.parentId === destinationId, { moved: true, before, after, destinationBinId: destinationId }, "project_item_parent_readback", "Move project item");
}
async function colorProjectItem(args) {
assertObject(args); assertOnlyKeys(args, ["projectItemId", "colorIndex", "operationId"]);
const project = await activeProject(true), item = await resolveProjectItem(project, args.projectItemId, true), colorIndex = boundedInt(args.colorIndex, "colorIndex", 0, 14), itemId = await projectItemId(item);
if (typeof item.createSetColorLabelAction !== "function") throw commandError("UXP_TARGET_UNSUPPORTED", "Project item does not support color labels");
if (!itemId) throw commandError("UXP_TARGET_NOT_FOUND", "Project item ID is unavailable");
return colorLabelLocks.withProjectItemColorLabelLock(guidString(project.guid) + "\u0000" + itemId, async () => {
const before = await projectItemSnapshot(item);
project.lockedAccess(() => {
commitActions(project, "Set project item color", [item.createSetColorLabelAction(colorIndex)]);
});
const after = await projectItemSnapshot(item);
return mutationResult(after.colorLabelIndex === colorIndex, { updated: true, before, after }, "project_item_color_readback", "Set project item color");
});
}
async function removeProjectItem(args) {
assertObject(args); assertOnlyKeys(args, ["projectItemId", "expectedName", "operationId"]);
const project = await activeProject(true), item = await resolveProjectItem(project, args.projectItemId, true), before = await projectItemSnapshot(item);
assertExpected(before.name, args.expectedName, "UXP_STALE_PROJECT_ITEM", "Project item name");
let parent = await project.getRootItem();
try { if (typeof item.getParentBin === "function") parent = asFolder(await item.getParentBin(), "current parent"); } catch (_) {}
project.lockedAccess(() => {
commitActions(project, "Remove project item", [parent.createRemoveItemAction(item)]);
});
let verified = false;
try { await findProjectItem(project, before.id); } catch (error) { if (error && error.code === "UXP_TARGET_NOT_FOUND") verified = true; }
return mutationResult(verified, { removed: true, item: before }, "project_item_absence_readback", "Remove project item");
}
async function settingsSnapshot(settings) {
const videoRate = typeof settings.getVideoFrameRate === "function" ? settings.getVideoFrameRate() : null;
const audioRate = typeof settings.getAudioSampleRate === "function" ? await settings.getAudioSampleRate() : null;
const videoRect = typeof settings.getVideoFrameRect === "function" ? await settings.getVideoFrameRect() : null;
const previewRect = typeof settings.getPreviewFrameRect === "function" ? await settings.getPreviewFrameRect() : null;
return {
maximumBitDepth: await maybeCall(settings, "getMaximumBitDepth"), maxRenderQuality: await maybeCall(settings, "getMaxRenderQuality"),
compositeInLinearColor: await maybeCall(settings, "getCompositeInLinearColor"), audioChannelCount: await maybeCall(settings, "getAudioChannelCount"),
audioChannelType: await maybeCall(settings, "getAudioChannelType"), audioSampleRate: rateValue(audioRate), videoFrameRate: rateValue(videoRate),
videoFieldType: await maybeCall(settings, "getVideoFieldType"), videoPixelAspectRatio: await maybeCall(settings, "getVideoPixelAspectRatio"),
editingMode: await maybeCall(settings, "getEditingMode"), previewFileFormat: await maybeCall(settings, "getPreviewFileFormat"), previewCodec: await maybeCall(settings, "getPreviewCodec"),
videoFrame: rectValue(videoRect), previewFrame: rectValue(previewRect)
};
}
async function getSequenceSettings(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId"]);
const project = await activeProject(false), sequence = await resolveSequence(project, args.sequenceId), settings = await sequence.getSettings();
return { sequence: await sequenceSnapshot(sequence), settings: await settingsSnapshot(settings) };
}
async function updateSequenceSettings(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "updates", "operationId"]);
const updates = validateSettingsUpdates(args.updates), project = await activeProject(true), sequence = await resolveSequence(project, args.sequenceId), settings = await sequence.getSettings(), before = await settingsSnapshot(settings);
const setters = {
maximumBitDepth: ["setMaximumBitDepth", updates.maximumBitDepth], maxRenderQuality: ["setMaxRenderQuality", updates.maxRenderQuality],
compositeInLinearColor: ["setCompositeInLinearColor", updates.compositeInLinearColor], videoFieldType: ["setVideoFieldType", updates.videoFieldType],
videoPixelAspectRatio: ["setVideoPixelAspectRatio", updates.videoPixelAspectRatio], editingMode: ["setEditingMode", updates.editingMode],
previewFileFormat: ["setPreviewFileFormat", updates.previewFileFormat], previewCodec: ["setPreviewCodec", updates.previewCodec]
};
for (const key of Object.keys(setters)) {
const method = setters[key][0], value = setters[key][1];
if (value !== undefined && (typeof settings[method] !== "function" || await settings[method](value) === false)) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected sequence setting " + key);
}
if (updates.videoFrameRate !== undefined) {
const rate = createFrameRate(updates.videoFrameRate);
if (typeof settings.setVideoFrameRate !== "function" || settings.setVideoFrameRate(rate) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected videoFrameRate");
}
if (updates.audioSampleRate !== undefined) {
if (typeof settings.setAudioSampleRate !== "function" || await settings.setAudioSampleRate(createFrameRate(updates.audioSampleRate)) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected audioSampleRate");
}
if (updates.videoWidth !== undefined || updates.videoHeight !== undefined) {
if (typeof ppro.RectF !== "function" || typeof settings.setVideoFrameRect !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose video frame rectangle updates");
}
const rect = new ppro.RectF(), current = before.videoFrame || {};
rect.width = updates.videoWidth === undefined ? current.width : updates.videoWidth;
rect.height = updates.videoHeight === undefined ? current.height : updates.videoHeight;
if (await settings.setVideoFrameRect(rect) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected video frame dimensions");
}
project.lockedAccess(() => {
commitActions(project, "Update sequence settings", [sequence.createSetSettingsAction(settings)]);
});
const after = await settingsSnapshot(await sequence.getSettings()), verified = Object.keys(updates).every((key) => settingMatches(after, key, updates[key]));
return mutationResult(verified, { updated: true, sequence: await sequenceSnapshot(sequence), before, after, changedFields: Object.keys(updates) }, "sequence_settings_readback", "Update sequence settings");
}
async function inspectSequenceDisplayFormats(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId"]);
const project = await activeProject(false), sequence = await resolveSequence(project, args.sequenceId);
const state = await sequenceDisplayFormatState(await sequence.getSettings());
return {
sequence: await sequenceSnapshot(sequence),
displayFormats: state.values,
supportedDisplayFormats: supportedDisplayFormats(),
verificationBoundary: "sequence_display_format_snapshot"
};
}
async function updateSequenceDisplayFormats(args) {
const input = validateSequenceDisplayFormatUpdate(args), project = await activeProject(true);
return withAppendLock(appendLockKey(project, "sequence-display-format", input.expectedSequenceGuid), async () => {
const sequence = await resolveSequence(project, input.sequenceId);
assertExpected(guidString(sequence.guid), input.expectedSequenceGuid, "UXP_STALE_SEQUENCE", "Sequence GUID");
const supported = supportedDisplayFormats(), settings = await sequence.getSettings(), beforeState = await sequenceDisplayFormatState(settings);
assertExpectedNumber(beforeState.values.audioDisplayFormat, input.expectedDisplayFormats.audioDisplayFormat, "UXP_STALE_SEQUENCE_DISPLAY_FORMAT", "Audio display format");
assertExpectedNumber(beforeState.values.videoDisplayFormat, input.expectedDisplayFormats.videoDisplayFormat, "UXP_STALE_SEQUENCE_DISPLAY_FORMAT", "Video display format");
assertRequestedDisplayFormatsSupported(input.updates, supported);
const changedFields = Object.keys(input.updates);
if (changedFields.every((key) => beforeState.values[key] === input.updates[key])) {
return sequenceDisplayFormatNoop({
updated: false,
unchanged: true,
sequence: await sequenceSnapshot(sequence),
before: beforeState.values,
after: beforeState.values,
supportedDisplayFormats: supported,
changedFields
}, "sequence_display_format_noop_readback");
}
if (input.updates.audioDisplayFormat !== undefined) {
if (typeof settings.setAudioDisplayFormat !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose audio display-format updates");
beforeState.audioDisplay.type = input.updates.audioDisplayFormat;
if (await settings.setAudioDisplayFormat(beforeState.audioDisplay) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the audio display format");
}
if (input.updates.videoDisplayFormat !== undefined) {
if (typeof settings.setVideoDisplayFormat !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose video display-format updates");
beforeState.videoDisplay.type = input.updates.videoDisplayFormat;
if (await settings.setVideoDisplayFormat(beforeState.videoDisplay) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the video display format");
}
project.lockedAccess(() => {
commitActions(project, "Update sequence display formats", [sequence.createSetSettingsAction(settings)]);
});
const afterState = await sequenceDisplayFormatState(await sequence.getSettings());
const verified = changedFields.every((key) => afterState.values[key] === input.updates[key]);
return sequenceDisplayFormatResult(verified, {
updated: true,
sequence: await sequenceSnapshot(sequence),
before: beforeState.values,
after: afterState.values,
supportedDisplayFormats: supported,
changedFields
}, "sequence_display_format_readback");
});
}
async function importProjectMedia(args) {
assertObject(args); assertOnlyKeys(args, ["mode", "paths", "projectPath", "sequenceIds", "aepPath", "compNames", "targetBinId", "suppressUI", "asNumberedStills", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "Documented import APIs are direct host calls without an Action undo boundary");
const mode = enumValue(args.mode, "mode", ["files", "sequences", "aeComps", "allAEComps"]), project = await activeProject(false), targetBin = args.targetBinId ? await resolveFolder(project, args.targetBinId, "targetBinId") : undefined;
const readbackFolder = targetBin || asFolder(await project.getRootItem(), "project root");
const beforeItems = await binChildren(readbackFolder), beforeSequences = await listSequences(project);
let accepted = false, requested = 0;
if (mode === "files") {
const paths = await boundedPathArray(args.paths, "paths", 100, "file"); requested = paths.length;
accepted = await project.importFiles(paths, optionalBoolean(args.suppressUI, true, "suppressUI"), targetBin, optionalBoolean(args.asNumberedStills, false, "asNumberedStills"));
} else if (mode === "sequences") {
const projectPath = await allowedPath(args.projectPath, "projectPath", "file"), ids = args.sequenceIds == null ? undefined : boundedStringArray(args.sequenceIds, "sequenceIds", 64, 128).map((id) => guidFromString(id, "sequenceId"));
requested = ids ? ids.length : 0; accepted = await project.importSequences(projectPath, ids);
} else {
if (ppro.Utils && typeof ppro.Utils.isAEInstalled === "function" && !await ppro.Utils.isAEInstalled()) throw commandError("UXP_DEPENDENCY_UNAVAILABLE", "After Effects is not installed");
const aepPath = await allowedPath(args.aepPath, "aepPath", "file");
if (mode === "aeComps") {
const names = boundedStringArray(args.compNames, "compNames", 64, 255); requested = names.length; accepted = await project.importAEComps(aepPath, names, targetBin);
} else { requested = 1; accepted = await project.importAllAEComps(aepPath, targetBin); }
}
if (!accepted) throw commandError("UXP_HOST_REJECTED", "Premiere rejected the import request");
const afterItems = await binChildren(readbackFolder), afterSequences = await listSequences(project);
const addedItemIds = afterItems.filter((item) => !beforeItems.some((old) => old.id === item.id)).map((item) => item.id);
const addedSequenceIds = afterSequences.filter((item) => !beforeSequences.some((old) => old.id === item.id)).map((item) => item.id);
const observedAddedCount = addedItemIds.length + addedSequenceIds.length;
// The documented import calls return only acceptance and the bounded
// folder/sequence snapshots cannot prove that every requested identity
// was imported. Report the commit honestly without treating a partial
// positive delta as full verification.
return directMutationResult(false, { imported: true, mode, requested, observedAddedCount, addedItemIds, addedSequenceIds }, "import_host_return_and_bounded_post_state");
}
async function parameterContext(args, mutation) {
const allowed = ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName", "timeSeconds"];
if (mutation) allowed.push("value", "endSeconds", "interpolation", "operationId");
assertObject(args); assertOnlyKeys(args, allowed);
const mediaType = enumValue(args.mediaType, "mediaType", ["video", "audio"]), trackIndex = nonNegativeInt(args.trackIndex, "trackIndex"), clipIndex = nonNegativeInt(args.clipIndex, "clipIndex"), componentIndex = nonNegativeInt(args.componentIndex, "componentIndex"), paramIndex = nonNegativeInt(args.paramIndex, "paramIndex");
const context = await activeContext(mutation), item = await trackItemAt(context.sequence, mediaType, trackIndex, clipIndex), chain = await item.getComponentChain();
const count = chain.getComponentCount();
if (componentIndex >= count) throw commandError("UXP_TARGET_NOT_FOUND", "componentIndex is out of range");
const component = chain.getComponentAtIndex(componentIndex), componentId = await componentIdentifier(component);
assertExpected(componentId, args.expectedComponentId, "UXP_STALE_EFFECT_CHAIN", "Component identity");
if (paramIndex >= component.getParamCount()) throw commandError("UXP_TARGET_NOT_FOUND", "paramIndex is out of range");
const param = component.getParam(paramIndex), paramName = String(param.displayName || "");
assertExpected(paramName, args.expectedParamName, "UXP_STALE_PARAMETER", "Parameter name");
return { ...context, item, component, componentId, param, paramName, mediaType, trackIndex, clipIndex, componentIndex, paramIndex };
}
async function componentIdentifier(component) {
let matchName = "", displayName = "";
try { matchName = String(await component.getMatchName() || ""); } catch (_) {}
try { displayName = String(await component.getDisplayName() || ""); } catch (_) {}
return matchName || displayName;
}
async function parameterSnapshot(context, timeSeconds) {
const supported = typeof context.param.areKeyframesSupported === "function" ? !!await context.param.areKeyframesSupported() : false;
const varying = typeof context.param.isTimeVarying === "function" ? !!context.param.isTimeVarying() : false;
const rawTimes = typeof context.param.getKeyframeListAsTickTimes === "function" ? Array.from(context.param.getKeyframeListAsTickTimes() || []) : [];
const times = rawTimes.slice(0, MAX_KEYFRAMES).map(tickSeconds);
let value = null;
if (timeSeconds != null && typeof context.param.getValueAtTime === "function") value = await context.param.getValueAtTime(tick(finiteNumber(timeSeconds, "timeSeconds", 0, 86400), "timeSeconds"));
else if (typeof context.param.getStartValue === "function") value = keyframeValue(await context.param.getStartValue());
return {
mediaType: context.mediaType, trackIndex: context.trackIndex, clipIndex: context.clipIndex,
componentIndex: context.componentIndex, componentId: context.componentId, paramIndex: context.paramIndex,
paramName: context.paramName, keyframesSupported: supported, timeVarying: varying,
keyframeCount: rawTimes.length, keyframeTimesSeconds: times, keyframesLimited: rawTimes.length > MAX_KEYFRAMES, value
};
}
function completeKeyframeTimes(param) {
if (!param || typeof param.getKeyframeListAsTickTimes !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot enumerate keyframes for safe removal verification");
}
const rawTimes = Array.from(param.getKeyframeListAsTickTimes() || []);
if (rawTimes.length > MAX_KEYFRAMES) {
throw commandError("UXP_PROJECT_TOO_LARGE", "Keyframe removal verification exceeds " + MAX_KEYFRAMES + " entries");
}
return rawTimes.map(tickSeconds);
}
async function inspectParameter(args) {
const context = await parameterContext(args, false);
return parameterSnapshot(context, args.timeSeconds);
}
async function pointParameterContext(args, mutation) {
const allowed = ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName"];
if (mutation) allowed.push("expectedSnapshot", "point", "confirmSetPoint", "operationId");
assertObject(args); assertOnlyKeys(args, allowed);
const context = await parameterContext({
mediaType: args.mediaType, trackIndex: args.trackIndex, clipIndex: args.clipIndex,
componentIndex: args.componentIndex, paramIndex: args.paramIndex,
expectedComponentId: args.expectedComponentId, expectedParamName: args.expectedParamName,
}, mutation);
if (typeof context.param.getStartValue !== "function" || typeof context.param.isTimeVarying !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot read a stable PointF parameter snapshot");
}
return context;
}
async function pointParameterSnapshot(context) {
const projectId = guidString(context.project && context.project.guid), sequenceId = guidString(context.sequence && context.sequence.guid);
if (!projectId || !sequenceId) throw commandError("UXP_INVALID_HOST_STATE", "Premiere did not provide stable project and sequence identities for the PointF parameter");
const point = pointValue(keyframeValue(await context.param.getStartValue()), "Premiere parameter start value");
return {
projectId, sequenceId, mediaType: context.mediaType, trackIndex: context.trackIndex, clipIndex: context.clipIndex,
componentIndex: context.componentIndex, componentId: context.componentId, paramIndex: context.paramIndex,
paramName: context.paramName, timeVarying: !!context.param.isTimeVarying(), point,
};
}
function pointSnapshotMatches(left, right) {
return left && right && left.projectId === right.projectId && left.sequenceId === right.sequenceId
&& left.mediaType === right.mediaType && left.trackIndex === right.trackIndex && left.clipIndex === right.clipIndex
&& left.componentIndex === right.componentIndex && left.componentId === right.componentId && left.paramIndex === right.paramIndex
&& left.paramName === right.paramName && left.timeVarying === right.timeVarying
&& pointsEqual(left.point, right.point);
}
function assertPointSnapshot(current, expected) {
if (!pointSnapshotMatches(current, expected)) {
throw commandError("UXP_STALE_POINT_PARAMETER", "The PointF parameter changed; inspect it again before updating it");
}
}
function pointParameterLockKey(context) {
return appendLockKey(context.project, "parameter-point", [
guidString(context.sequence && context.sequence.guid), context.mediaType, context.trackIndex, context.clipIndex,
context.componentIndex, context.paramIndex
].join(":"));
}
async function withPointParameterLock(key, callback) {
const previous = pointParameterLocks.get(key) || Promise.resolve();
let release = function () {};
const current = new Promise((resolve) => { release = resolve; });
pointParameterLocks.set(key, current);
await previous;
try {
return await callback();
} finally {
release();
if (pointParameterLocks.get(key) === current) pointParameterLocks.delete(key);
}
}
async function inspectPointParameter(args) {
const firstContext = await pointParameterContext(args, false), first = await pointParameterSnapshot(firstContext);
const finalContext = await pointParameterContext(args, false), final = await pointParameterSnapshot(finalContext);
if (!pointSnapshotMatches(first, final)) {
throw commandError("UXP_STALE_POINT_PARAMETER", "The PointF parameter changed while it was being inspected; retry the inspection");
}
return final;
}
function pointDisplacementInput(args) {
assertObject(args);
assertOnlyKeys(args, ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName", "startSeconds", "endSeconds"]);
const startSeconds = finiteNumber(args.startSeconds, "startSeconds", 0, 86400);
const endSeconds = finiteNumber(args.endSeconds, "endSeconds", 0, 86400);
if (endSeconds <= startSeconds) {
throw commandError("UXP_INVALID_ARGUMENT", "endSeconds must be greater than startSeconds for PointF displacement inspection");
}
return {
mediaType: args.mediaType, trackIndex: args.trackIndex, clipIndex: args.clipIndex,
componentIndex: args.componentIndex, paramIndex: args.paramIndex,
expectedComponentId: args.expectedComponentId, expectedParamName: args.expectedParamName,
startSeconds, endSeconds,
};
}
async function pointDisplacementContext(input) {
const context = await parameterContext({
mediaType: input.mediaType, trackIndex: input.trackIndex, clipIndex: input.clipIndex,
componentIndex: input.componentIndex, paramIndex: input.paramIndex,
expectedComponentId: input.expectedComponentId, expectedParamName: input.expectedParamName,
}, false);
if (typeof context.param.getValueAtTime !== "function" || typeof context.param.isTimeVarying !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot read time-varying PointF parameter values");
}
return context;
}
async function pointDisplacementSnapshot(context, input) {
const projectId = guidString(context.project && context.project.guid), sequenceId = guidString(context.sequence && context.sequence.guid);
if (!projectId || !sequenceId) throw commandError("UXP_INVALID_HOST_STATE", "Premiere did not provide stable project and sequence identities for the PointF parameter");
const timeVarying = !!context.param.isTimeVarying();
if (!timeVarying) throw commandError("UXP_TARGET_UNSUPPORTED", "PointF displacement inspection requires a time-varying parameter");
const startValue = await context.param.getValueAtTime(tick(input.startSeconds, "startSeconds"));
const endValue = await context.param.getValueAtTime(tick(input.endSeconds, "endSeconds"));
if (!startValue || !endValue || typeof startValue.distanceTo !== "function" || typeof endValue.distanceTo !== "function") {
throw commandError("UXP_TARGET_UNSUPPORTED", "Premiere did not return native PointF values with distanceTo");
}
const startPoint = pointValue({ x: startValue.x, y: startValue.y }, "Premiere PointF start value");
const endPoint = pointValue({ x: endValue && endValue.x, y: endValue && endValue.y }, "Premiere PointF end value");
let distance;
try { distance = Number(startValue.distanceTo(endValue)); }
catch (_) { throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere rejected native PointF distance calculation"); }
if (!Number.isFinite(distance) || distance < 0) {
throw commandError("UXP_INVALID_HOST_STATE", "Premiere returned an invalid PointF distance");
}
return {
projectId, sequenceId, mediaType: context.mediaType, trackIndex: context.trackIndex, clipIndex: context.clipIndex,
componentIndex: context.componentIndex, componentId: context.componentId, paramIndex: context.paramIndex,
paramName: context.paramName, timeVarying, startSeconds: input.startSeconds, endSeconds: input.endSeconds,
startPoint, endPoint, straightLineDistance: distance,
};
}
function pointDisplacementSnapshotMatches(left, right) {
return left && right && left.projectId === right.projectId && left.sequenceId === right.sequenceId
&& left.mediaType === right.mediaType && left.trackIndex === right.trackIndex && left.clipIndex === right.clipIndex
&& left.componentIndex === right.componentIndex && left.componentId === right.componentId && left.paramIndex === right.paramIndex
&& left.paramName === right.paramName && left.timeVarying === right.timeVarying
&& numbersEqual(left.startSeconds, right.startSeconds) && numbersEqual(left.endSeconds, right.endSeconds)
&& pointsEqual(left.startPoint, right.startPoint) && pointsEqual(left.endPoint, right.endPoint)
&& numbersEqual(left.straightLineDistance, right.straightLineDistance);
}
async function inspectPointParameterDisplacement(args) {
const input = pointDisplacementInput(args);
const firstContext = await pointDisplacementContext(input), first = await pointDisplacementSnapshot(firstContext, input);
const finalContext = await pointDisplacementContext(input), final = await pointDisplacementSnapshot(finalContext, input);
if (!pointDisplacementSnapshotMatches(first, final)) {
throw commandError("UXP_STALE_POINT_PARAMETER", "The PointF parameter changed while its displacement was being inspected; retry the inspection");
}
return final;
}
function validatePointParameterSet(args) {
assertObject(args); assertOnlyKeys(args, ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName", "expectedSnapshot", "point", "confirmSetPoint", "operationId"]);
if (args.confirmSetPoint !== true) {
throw commandError("UXP_CONFIRMATION_REQUIRED", "Setting a PointF parameter requires confirmSetPoint=true after review");
}
if (typeof args.operationId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(args.operationId)) {
throw commandError("UXP_INVALID_ARGUMENT", "operationId is required and must be 1-128 safe characters");
}
return {
...args,
expectedSnapshot: expectedPointParameterSnapshot(args.expectedSnapshot),
point: pointValue(args.point, "point"),
};
}
function createPoint(value) {
if (typeof ppro.PointF !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose the PointF constructor");
try { return new ppro.PointF(value.x, value.y); }
catch (_) { throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere rejected PointF construction"); }
}
async function setPointParameter(args) {
const input = validatePointParameterSet(args), point = createPoint(input.point);
const initialContext = await pointParameterContext(input, true);
return withPointParameterLock(pointParameterLockKey(initialContext), async () => {
const context = await pointParameterContext(input, true), before = await pointParameterSnapshot(context);
assertPointSnapshot(before, input.expectedSnapshot);
if (before.timeVarying) throw commandError("UXP_TARGET_UNSUPPORTED", "PointF updates support only non-time-varying parameters; keyframed PointF edits are not exposed");
context.project.lockedAccess(() => {
const action = context.param.createSetValueAction(context.param.createKeyframe(point), true);
commitActions(context.project, "Set PointF effect parameter", [action]);
});
const afterContext = await pointParameterContext(input, true), after = await pointParameterSnapshot(afterContext);
const verified = !after.timeVarying && pointsEqual(after.point, input.point);
return mutationResult(verified, { updated: true, before, after }, "point_parameter_readback", "Set PointF effect parameter");
});
}
async function colorParameterContext(args, mutation) {
const allowed = ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName"];
if (mutation) allowed.push("expectedSnapshot", "color", "confirmSetColor", "operationId");
assertObject(args); assertOnlyKeys(args, allowed);
const context = await parameterContext({
mediaType: args.mediaType, trackIndex: args.trackIndex, clipIndex: args.clipIndex,
componentIndex: args.componentIndex, paramIndex: args.paramIndex,
expectedComponentId: args.expectedComponentId, expectedParamName: args.expectedParamName,
}, mutation);
if (typeof context.param.getStartValue !== "function" || typeof context.param.isTimeVarying !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot read a stable Color parameter snapshot");
}
return context;
}
async function colorParameterSnapshot(context) {
const projectId = guidString(context.project && context.project.guid), sequenceId = guidString(context.sequence && context.sequence.guid);
if (!projectId || !sequenceId) throw commandError("UXP_INVALID_HOST_STATE", "Premiere did not provide stable project and sequence identities for the Color parameter");
const color = colorValue(keyframeValue(await context.param.getStartValue()), "Premiere parameter start value");
return {
projectId, sequenceId, mediaType: context.mediaType, trackIndex: context.trackIndex, clipIndex: context.clipIndex,
componentIndex: context.componentIndex, componentId: context.componentId, paramIndex: context.paramIndex,
paramName: context.paramName, timeVarying: !!context.param.isTimeVarying(), color,
};
}
function colorSnapshotMatches(left, right) {
return left && right && left.projectId === right.projectId && left.sequenceId === right.sequenceId
&& left.mediaType === right.mediaType && left.trackIndex === right.trackIndex && left.clipIndex === right.clipIndex
&& left.componentIndex === right.componentIndex && left.componentId === right.componentId && left.paramIndex === right.paramIndex
&& left.paramName === right.paramName && left.timeVarying === right.timeVarying
&& colorsEqual(left.color, right.color);
}
function assertColorSnapshot(current, expected) {
if (!colorSnapshotMatches(current, expected)) {
throw commandError("UXP_STALE_COLOR_PARAMETER", "The Color parameter changed; inspect it again before updating it");
}
}
function colorParameterLockKey(context) {
return appendLockKey(context.project, "parameter-color", [
guidString(context.sequence && context.sequence.guid), context.mediaType, context.trackIndex, context.clipIndex,
context.componentIndex, context.paramIndex
].join(":"));
}
async function withColorParameterLock(key, callback) {
const previous = colorParameterLocks.get(key) || Promise.resolve();
let release = function () {};
const current = new Promise((resolve) => { release = resolve; });
colorParameterLocks.set(key, current);
await previous;
try {
return await callback();
} finally {
release();
if (colorParameterLocks.get(key) === current) colorParameterLocks.delete(key);
}
}
async function inspectColorParameter(args) {
const firstContext = await colorParameterContext(args, false), first = await colorParameterSnapshot(firstContext);
const finalContext = await colorParameterContext(args, false), final = await colorParameterSnapshot(finalContext);
if (!colorSnapshotMatches(first, final)) {
throw commandError("UXP_STALE_COLOR_PARAMETER", "The Color parameter changed while it was being inspected; retry the inspection");
}
return final;
}
function validateColorParameterSet(args) {
assertObject(args); assertOnlyKeys(args, ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName", "expectedSnapshot", "color", "confirmSetColor", "operationId"]);
if (args.confirmSetColor !== true) {
throw commandError("UXP_CONFIRMATION_REQUIRED", "Setting a Color parameter requires confirmSetColor=true after review");
}
if (typeof args.operationId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(args.operationId)) {
throw commandError("UXP_INVALID_ARGUMENT", "operationId is required and must be 1-128 safe characters");
}
return {
...args,
expectedSnapshot: expectedColorParameterSnapshot(args.expectedSnapshot),
color: colorValue(args.color, "color"),
};
}
function createColor(value) {
if (typeof ppro.Color !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose the Color constructor");
try { return new ppro.Color(value.red, value.green, value.blue, value.alpha); }
catch (_) { throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere rejected Color construction"); }
}
async function setColorParameter(args) {
const input = validateColorParameterSet(args), color = createColor(input.color);
const initialContext = await colorParameterContext(input, true);
return withColorParameterLock(colorParameterLockKey(initialContext), async () => {
const context = await colorParameterContext(input, true), before = await colorParameterSnapshot(context);
assertColorSnapshot(before, input.expectedSnapshot);
if (before.timeVarying) throw commandError("UXP_TARGET_UNSUPPORTED", "Color updates support only non-time-varying parameters; keyframed Color edits are not exposed");
context.project.lockedAccess(() => {
const action = context.param.createSetValueAction(context.param.createKeyframe(color), true);
commitActions(context.project, "Set Color effect parameter", [action]);
});
const afterContext = await colorParameterContext(input, true), after = await colorParameterSnapshot(afterContext);
const verified = !after.timeVarying && colorsEqual(after.color, input.color);
return mutationResult(verified, { updated: true, before, after }, "color_parameter_readback", "Set Color effect parameter");
});
}
async function inspectParameterKeyframe(args) {
assertObject(args);
assertOnlyKeys(args, ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName", "timeSeconds", "endSeconds", "direction"]);
const direction = enumValue(args.direction, "direction", ["at", "next", "previous", "nearest"]);
const timeSeconds = finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400);
const hasEndSeconds = Object.prototype.hasOwnProperty.call(args, "endSeconds");
if (direction !== "nearest" && hasEndSeconds) {
throw commandError("UXP_INVALID_ARGUMENT", "endSeconds is only supported when direction is nearest");
}
const endSeconds = direction === "nearest" ? finiteNumber(args.endSeconds, "endSeconds", 0, 86400) : undefined;
if (endSeconds != null && endSeconds < timeSeconds) {
throw commandError("UXP_INVALID_ARGUMENT", "endSeconds must be greater than or equal to timeSeconds for nearest keyframe lookup");
}
const context = await parameterContext({
mediaType: args.mediaType, trackIndex: args.trackIndex, clipIndex: args.clipIndex,
componentIndex: args.componentIndex, paramIndex: args.paramIndex,
expectedComponentId: args.expectedComponentId, expectedParamName: args.expectedParamName,
timeSeconds,
}, false);
if (typeof context.param.areKeyframesSupported !== "function" || !await context.param.areKeyframesSupported()) {
throw commandError("UXP_TARGET_UNSUPPORTED", "This parameter does not support keyframes");
}
const method = {
at: "getKeyframePtr", next: "findNextKeyframe", previous: "findPreviousKeyframe", nearest: "findNearestKeyframe"
}[direction];
if (typeof context.param[method] !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot locate parameter keyframes in the requested direction");
}
let keyframe;
try {
const inTime = tick(timeSeconds, "timeSeconds");
keyframe = direction === "nearest"
? context.param[method](inTime, tick(endSeconds, "endSeconds"))
: context.param[method](inTime);
} catch (_) {
throw commandError("UXP_KEYFRAME_LOOKUP_FAILED", "Premiere could not locate the requested parameter keyframe");
}
const base = {
projectId: guidString(context.project && context.project.guid), sequenceId: guidString(context.sequence && context.sequence.guid),
mediaType: context.mediaType, trackIndex: context.trackIndex, clipIndex: context.clipIndex,
componentIndex: context.componentIndex, componentId: context.componentId, paramIndex: context.paramIndex,
paramName: context.paramName, direction, referenceSeconds: timeSeconds,
...(direction === "nearest" ? { rangeEndSeconds: endSeconds } : {}),
};
if (!keyframe) return { ...base, found: false, keyframe: null };
const positionSeconds = tickSeconds(keyframe.position);
if (positionSeconds == null) throw commandError("UXP_INVALID_HOST_STATE", "Premiere returned a keyframe without a readable position");
if (typeof keyframe.getTemporalInterpolationMode !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot read the located keyframe interpolation mode");
}
let temporalInterpolationMode;
try { temporalInterpolationMode = await keyframe.getTemporalInterpolationMode(); } catch (_) {
throw commandError("UXP_KEYFRAME_LOOKUP_FAILED", "Premiere did not return the located keyframe interpolation mode");
}
if (!Number.isFinite(Number(temporalInterpolationMode))) {
throw commandError("UXP_INVALID_HOST_STATE", "Premiere returned an invalid keyframe interpolation mode");
}
return { ...base, found: true, keyframe: { positionSeconds, temporalInterpolationMode: Number(temporalInterpolationMode) } };
}
async function parameterTimeVaryingContext(args, mutation) {
const allowed = ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedComponentId", "expectedParamName"];
if (mutation) allowed.push("expectedSequenceId", "expectedTimeVarying", "expectedKeyframeTimesSeconds", "timeVarying", "confirmDisableTimeVarying", "operationId");
assertObject(args); assertOnlyKeys(args, allowed);
const mediaType = enumValue(args.mediaType, "mediaType", ["video", "audio"]), trackIndex = nonNegativeInt(args.trackIndex, "trackIndex"), clipIndex = nonNegativeInt(args.clipIndex, "clipIndex"), componentIndex = nonNegativeInt(args.componentIndex, "componentIndex"), paramIndex = nonNegativeInt(args.paramIndex, "paramIndex");
const context = await activeContext(mutation), sequenceId = guidString(context.sequence && context.sequence.guid);
if (mutation && sequenceId !== args.expectedSequenceId) {
throw commandError("UXP_STALE_SEQUENCE", "The active sequence changed; inspect the parameter animation mode again before updating it");
}
const item = await trackItemAt(context.sequence, mediaType, trackIndex, clipIndex), chain = await item.getComponentChain();
const count = chain.getComponentCount();
if (componentIndex >= count) throw commandError("UXP_TARGET_NOT_FOUND", "componentIndex is out of range");
const component = chain.getComponentAtIndex(componentIndex), componentId = await componentIdentifier(component);
assertExpected(componentId, args.expectedComponentId, "UXP_STALE_EFFECT_CHAIN", "Component identity");
if (paramIndex >= component.getParamCount()) throw commandError("UXP_TARGET_NOT_FOUND", "paramIndex is out of range");
const param = component.getParam(paramIndex), paramName = String(param.displayName || "");
assertExpected(paramName, args.expectedParamName, "UXP_STALE_PARAMETER", "Parameter name");
return { ...context, item, component, componentId, param, paramName, mediaType, trackIndex, clipIndex, componentIndex, paramIndex, sequenceId };
}
async function parameterTimeVaryingSnapshot(context, requireCompleteKeyframes) {
const snapshot = await parameterSnapshot(context);
if (requireCompleteKeyframes && snapshot.keyframesLimited) {
throw commandError("UXP_PROJECT_TOO_LARGE", "Parameter animation-mode verification exceeds " + MAX_KEYFRAMES + " keyframes");
}
if (requireCompleteKeyframes && snapshot.keyframeTimesSeconds.some((seconds) => !Number.isFinite(seconds))) {
throw commandError("UXP_INVALID_HOST_STATE", "Premiere returned an unreadable parameter keyframe time");
}
return { projectId: guidString(context.project && context.project.guid), sequenceId: context.sequenceId || guidString(context.sequence && context.sequence.guid), ...snapshot };
}
function validateParameterTimeVaryingSet(args) {
assertObject(args);
assertOnlyKeys(args, ["mediaType", "trackIndex", "clipIndex", "componentIndex", "paramIndex", "expectedSequenceId", "expectedComponentId", "expectedParamName", "expectedTimeVarying", "expectedKeyframeTimesSeconds", "timeVarying", "confirmDisableTimeVarying", "operationId"]);
const expectedSequenceId = boundedString(args.expectedSequenceId, "expectedSequenceId", 128);
if (typeof args.expectedTimeVarying !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "expectedTimeVarying must be boolean");
if (typeof args.timeVarying !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "timeVarying must be boolean");
if (!Array.isArray(args.expectedKeyframeTimesSeconds) || args.expectedKeyframeTimesSeconds.length > MAX_KEYFRAMES) {
throw commandError("UXP_INVALID_ARGUMENT", "expectedKeyframeTimesSeconds must contain at most " + MAX_KEYFRAMES + " entries");
}
let previous = -1;
const expectedKeyframeTimesSeconds = args.expectedKeyframeTimesSeconds.map((value, index) => {
const seconds = finiteNumber(value, "expectedKeyframeTimesSeconds[" + index + "]", 0, 86400);
if (seconds <= previous) throw commandError("UXP_INVALID_ARGUMENT", "expectedKeyframeTimesSeconds must be strictly increasing");
previous = seconds;
return seconds;
});
if (!args.timeVarying) requireConfirmation(args.confirmDisableTimeVarying, "Disabling a time-varying parameter can remove its editable animation state");
return { ...args, expectedSequenceId, expectedKeyframeTimesSeconds };
}
function assertExpectedParameterTimeVarying(snapshot, expectedTimeVarying, expectedKeyframeTimesSeconds) {
if (snapshot.timeVarying !== expectedTimeVarying || !sameNumberArrays(snapshot.keyframeTimesSeconds, expectedKeyframeTimesSeconds)) {
throw commandError("UXP_STALE_PARAMETER", "The parameter animation mode or keyframe timeline changed; inspect it again before updating it");
}
}
function nativeParameterTimeVaryingState(param) {
if (!param || typeof param.isTimeVarying !== "function" || typeof param.getKeyframeListAsTickTimes !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot read complete parameter animation state");
}
const rawTimes = Array.from(param.getKeyframeListAsTickTimes() || []);
if (rawTimes.length > MAX_KEYFRAMES) throw commandError("UXP_PROJECT_TOO_LARGE", "Parameter animation-mode verification exceeds " + MAX_KEYFRAMES + " keyframes");
const keyframeTimesSeconds = rawTimes.map(tickSeconds);
if (keyframeTimesSeconds.some((seconds) => !Number.isFinite(seconds))) throw commandError("UXP_INVALID_HOST_STATE", "Premiere returned an unreadable parameter keyframe time");
return { timeVarying: !!param.isTimeVarying(), keyframeTimesSeconds };
}
function parameterTimeVaryingLockKey(context) {
return appendLockKey(context.project, "parameter-time-varying", context.sequenceId + ":" + context.mediaType + ":" + context.trackIndex + ":" + context.clipIndex + ":" + context.componentIndex + ":" + context.paramIndex);
}
async function withParameterTimeVaryingLock(key, callback) {
const previous = parameterTimeVaryingLocks.get(key) || Promise.resolve();
let release = function () {};
const current = new Promise((resolve) => { release = resolve; });
parameterTimeVaryingLocks.set(key, current);
await previous;
try {
return await callback();
} finally {
release();
if (parameterTimeVaryingLocks.get(key) === current) parameterTimeVaryingLocks.delete(key);
}
}
async function inspectParameterTimeVarying(args) {
const context = await parameterTimeVaryingContext(args, false);
return parameterTimeVaryingSnapshot(context, false);
}
async function setParameterTimeVarying(args) {
const input = validateParameterTimeVaryingSet(args), initialContext = await parameterTimeVaryingContext(input, true);
return withParameterTimeVaryingLock(parameterTimeVaryingLockKey(initialContext), async () => {
const context = await parameterTimeVaryingContext(input, true), before = await parameterTimeVaryingSnapshot(context, true);
if (!before.keyframesSupported) throw commandError("UXP_TARGET_UNSUPPORTED", "This parameter does not support keyframes");
assertExpectedParameterTimeVarying(before, input.expectedTimeVarying, input.expectedKeyframeTimesSeconds);
if (before.timeVarying === input.timeVarying) {
return verifiedNoopResult({ updated: false, unchanged: true, before, after: before }, "parameter_time_varying_noop_readback");
}
context.project.lockedAccess(() => {
const locked = nativeParameterTimeVaryingState(context.param);
assertExpectedParameterTimeVarying(locked, input.expectedTimeVarying, input.expectedKeyframeTimesSeconds);
const action = context.param.createSetTimeVaryingAction(input.timeVarying);
if (!action) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the parameter animation-mode action");
commitActions(context.project, "Set effect parameter animation mode", [action]);
});
const afterContext = await parameterTimeVaryingContext(input, true), after = await parameterTimeVaryingSnapshot(afterContext, true);
return mutationResult(after.timeVarying === input.timeVarying, {
updated: true, requestedTimeVarying: input.timeVarying, before, after
}, "parameter_time_varying_readback", "Set effect parameter animation mode");
});
}
async function setParameterValue(args) {
const context = await parameterContext(args, true), value = scalarValue(args.value), before = await parameterSnapshot(context, args.timeSeconds);
if (before.timeVarying) throw commandError("UXP_TARGET_UNSUPPORTED", "Use add_keyframe to change a time-varying parameter");
context.project.lockedAccess(() => {
const keyframe = context.param.createKeyframe(value);
commitActions(context.project, "Set effect parameter", [context.param.createSetValueAction(keyframe, true)]);
});
const after = await parameterSnapshot(context, args.timeSeconds), verified = valuesEqual(after.value, value);
return mutationResult(verified, { updated: true, before, after }, "parameter_value_readback", "Set effect parameter");
}
async function addParameterKeyframe(args) {
const context = await parameterContext(args, true), value = scalarValue(args.value), timeSeconds = finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400), time = tick(timeSeconds, "timeSeconds"), before = await parameterSnapshot(context);
if (!before.keyframesSupported) throw commandError("UXP_TARGET_UNSUPPORTED", "This parameter does not support keyframes");
context.project.lockedAccess(() => {
const actions = [], keyframe = context.param.createKeyframe(value);
keyframe.position = time;
if (!before.timeVarying) actions.push(context.param.createSetTimeVaryingAction(true));
actions.push(context.param.createAddKeyframeAction(keyframe));
commitActions(context.project, "Add effect keyframe", actions);
});
const after = await parameterSnapshot(context, timeSeconds), verified = after.keyframeTimesSeconds.some((seconds) => numbersEqual(seconds, timeSeconds)) && valuesEqual(after.value, value);
return mutationResult(verified, { added: true, before, after }, "parameter_keyframe_readback", "Add effect keyframe");
}
async function removeParameterKeyframe(args) {
const context = await parameterContext(args, true), timeSeconds = finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400);
const beforeTimes = completeKeyframeTimes(context.param);
const existed = beforeTimes.some((seconds) => numbersEqual(seconds, timeSeconds));
if (!existed) return verifiedNoopResult({ removed: false, unchanged: true, timeSeconds, after: await parameterSnapshot(context) }, "parameter_keyframe_absence_preflight");
context.project.lockedAccess(() => {
commitActions(context.project, "Remove effect keyframe", [context.param.createRemoveKeyframeAction(tick(timeSeconds, "timeSeconds"), true)]);
});
const afterTimes = completeKeyframeTimes(context.param), after = await parameterSnapshot(context), verified = !afterTimes.some((seconds) => numbersEqual(seconds, timeSeconds));
return mutationResult(verified, { removed: verified, removalRequested: true, timeSeconds, after }, "complete_parameter_keyframe_absence_readback", "Remove effect keyframe");
}
async function removeParameterKeyframeRange(args) {
const context = await parameterContext(args, true), start = finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400), end = finiteNumber(args.endSeconds, "endSeconds", 0, 86400);
if (end < start) throw commandError("UXP_INVALID_ARGUMENT", "endSeconds must be greater than or equal to timeSeconds");
const beforeTimes = completeKeyframeTimes(context.param);
const existed = beforeTimes.some((seconds) => seconds != null && seconds >= start && seconds <= end);
if (!existed) return verifiedNoopResult({ removed: false, unchanged: true, startSeconds: start, endSeconds: end, after: await parameterSnapshot(context) }, "parameter_keyframe_range_absence_preflight");
context.project.lockedAccess(() => {
commitActions(context.project, "Remove effect keyframe range", [context.param.createRemoveKeyframeRangeAction(tick(start), tick(end), true)]);
});
const afterTimes = completeKeyframeTimes(context.param), after = await parameterSnapshot(context), verified = !afterTimes.some((seconds) => seconds != null && seconds >= start && seconds <= end);
return mutationResult(verified, { removed: verified, removalRequested: true, startSeconds: start, endSeconds: end, after }, "complete_parameter_keyframe_range_readback", "Remove effect keyframe range");
}
async function setParameterInterpolation(args) {
const context = await parameterContext(args, true), timeSeconds = finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400), modeName = enumValue(args.interpolation, "interpolation", ["linear", "hold", "bezier", "time"]), constants = ppro.Constants && ppro.Constants.InterpolationMode || {};
const mode = constants[modeName.toUpperCase()];
if (mode == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "Interpolation constants are unavailable");
context.project.lockedAccess(() => {
commitActions(context.project, "Set keyframe interpolation", [context.param.createSetInterpolationAtKeyframeAction(tick(timeSeconds, "timeSeconds"), mode, true)]);
});
let verified = false, readback = null;
try { const keyframe = context.param.getKeyframePtr(tick(timeSeconds)); readback = await keyframe.getTemporalInterpolationMode(); verified = readback === mode; } catch (_) {}
return mutationResult(verified, { updated: true, interpolation: modeName, interpolationValue: readback }, "keyframe_interpolation_readback", "Set keyframe interpolation");
}
async function trackItemContext(args, mutation) {
const allowed = ["mediaType", "trackIndex", "clipIndex", "expectedStartSeconds", "expectedEndSeconds"];
if (mutation) allowed.push("moveBySeconds", "startSeconds", "endSeconds", "inSeconds", "outSeconds", "disabled", "name", "operationId");
assertObject(args); assertOnlyKeys(args, allowed);
const mediaType = enumValue(args.mediaType, "mediaType", ["video", "audio"]), trackIndex = nonNegativeInt(args.trackIndex, "trackIndex"), clipIndex = nonNegativeInt(args.clipIndex, "clipIndex"), context = await activeContext(mutation), item = await trackItemAt(context.sequence, mediaType, trackIndex, clipIndex);
return { ...context, item, mediaType, trackIndex, clipIndex };
}
async function trackItemSnapshot(context) {
const snapshot = {
mediaType: context.mediaType, trackIndex: context.trackIndex, clipIndex: context.clipIndex,
name: await maybeCall(context.item, "getName"), startSeconds: tickSeconds(await context.item.getStartTime()), endSeconds: tickSeconds(await context.item.getEndTime()),
inSeconds: tickSeconds(await context.item.getInPoint()), outSeconds: tickSeconds(await context.item.getOutPoint()), durationSeconds: tickSeconds(await context.item.getDuration()),
speed: await maybeCall(context.item, "getSpeed"), reversed: await maybeCall(context.item, "isSpeedReversed"), adjustmentLayer: await maybeCall(context.item, "isAdjustmentLayer"), disabled: await maybeCall(context.item, "isDisabled")
};
if (context.includeSourceProjectItems) {
const source = await trackItemSourceProjectItemSnapshot(context.item, context.includeSourceProjectItemClassification, context.includeSourceProjectItemContentType, context.includeSourceNestedSequenceIdentity);
snapshot.sourceProjectItemId = source.id;
if (context.includeSourceProjectItemClassification) snapshot.sourceProjectItemClassification = source.classification;
if (context.includeSourceProjectItemContentType) snapshot.sourceProjectItemContentType = source.contentType;
if (context.includeSourceNestedSequenceIdentity) snapshot.sourceNestedSequenceId = source.nestedSequenceId;
}
return snapshot;
}
async function trackItemSourceProjectItemSnapshot(item, includeClassification, includeContentType, includeNestedSequenceIdentity) {
try {
if (!item || typeof item.getProjectItem !== "function") return { id: null, classification: null, contentType: null, nestedSequenceId: null };
const sourceItem = await item.getProjectItem();
let id = null;
try { id = await projectItemId(sourceItem) || null; } catch (_) {}
const details = (includeClassification || includeContentType)
? await sourceProjectItemDetails(sourceItem, includeClassification, includeContentType, includeNestedSequenceIdentity)
: { classification: null, contentType: null, nestedSequenceId: null };
return { id, classification: details.classification, contentType: details.contentType, nestedSequenceId: details.nestedSequenceId };
} catch (_) {
return { id: null, classification: null, contentType: null, nestedSequenceId: null };
}
}
async function sourceProjectItemDetails(item, includeClassification, includeContentType, includeNestedSequenceIdentity) {
if (!ppro.ClipProjectItem || typeof ppro.ClipProjectItem.cast !== "function") return { classification: null, contentType: null, nestedSequenceId: null };
let clip;
try { clip = ppro.ClipProjectItem.cast(item); } catch (_) { return { classification: null, contentType: null, nestedSequenceId: null }; }
if (!clip) return { classification: null, contentType: null, nestedSequenceId: null };
const classification = includeClassification ? {
isSequence: await sourceProjectItemBoolean(clip, "isSequence"),
isMergedClip: await sourceProjectItemBoolean(clip, "isMergedClip"),
isMulticamClip: await sourceProjectItemBoolean(clip, "isMulticamClip"),
isOffline: await sourceProjectItemBoolean(clip, "isOffline")
} : null;
return {
classification,
contentType: includeContentType ? await sourceProjectItemContentType(clip) : null,
nestedSequenceId: includeNestedSequenceIdentity ? await sourceProjectItemNestedSequenceId(clip, classification) : null
};
}
async function sourceProjectItemContentType(clip) {
if (!clip || typeof clip.getContentType !== "function") return null;
const contentTypes = ppro.Constants && ppro.Constants.ContentType;
if (!contentTypes) return null;
try {
const value = await clip.getContentType();
if (value === contentTypes.ANY) return "any";
if (value === contentTypes.SEQUENCE) return "sequence";
if (value === contentTypes.MEDIA) return "media";
} catch (_) {}
return null;
}
async function sourceProjectItemNestedSequenceId(clip, classification) {
if (!classification || classification.isSequence !== true || !clip || typeof clip.getSequence !== "function") return null;
try {
const sequence = await clip.getSequence(), id = guidString(sequence && sequence.guid);
return id && id !== "[object Object]" && id.length <= 128 ? id : null;
} catch (_) { return null; }
}
async function sourceProjectItemBoolean(item, method) {
const value = await maybeCall(item, method);
return typeof value === "boolean" ? value : null;
}
async function inspectTrackItem(args) {
const context = await trackItemContext(args, false);
return trackItemSnapshot(context);
}
async function updateTrackItem(args) {
const context = await trackItemContext(args, true), before = await trackItemSnapshot(context);
assertExpectedNumber(before.startSeconds, args.expectedStartSeconds, "UXP_STALE_TRACK_ITEM", "Track item start");
assertExpectedNumber(before.endSeconds, args.expectedEndSeconds, "UXP_STALE_TRACK_ITEM", "Track item end");
if (args.moveBySeconds != null && (args.startSeconds != null || args.endSeconds != null)) throw commandError("UXP_INVALID_ARGUMENT", "moveBySeconds cannot be combined with startSeconds or endSeconds");
const requested = [args.moveBySeconds, args.startSeconds, args.endSeconds, args.inSeconds, args.outSeconds, args.disabled, args.name].filter((value) => value != null);
if (!requested.length) throw commandError("UXP_INVALID_ARGUMENT", "Provide at least one track-item field to update");
context.project.lockedAccess(() => {
const actions = [];
if (args.moveBySeconds != null) actions.push(context.item.createMoveAction(tick(args.moveBySeconds, "moveBySeconds")));
if (args.startSeconds != null) actions.push(context.item.createSetStartAction(tick(finiteNumber(args.startSeconds, "startSeconds", 0, 86400), "startSeconds")));
if (args.endSeconds != null) actions.push(context.item.createSetEndAction(tick(finiteNumber(args.endSeconds, "endSeconds", 0, 86400), "endSeconds")));
if (args.inSeconds != null) actions.push(context.item.createSetInPointAction(tick(finiteNumber(args.inSeconds, "inSeconds", 0, 86400), "inSeconds")));
if (args.outSeconds != null) actions.push(context.item.createSetOutPointAction(tick(finiteNumber(args.outSeconds, "outSeconds", 0, 86400), "outSeconds")));
if (args.disabled != null) actions.push(context.item.createSetDisabledAction(requiredBoolean(args.disabled, "disabled")));
if (args.name != null) actions.push(context.item.createSetNameAction(boundedString(args.name, "name", 255)));
commitActions(context.project, "Update timeline item", actions);
});
const after = await trackItemSnapshot(context), verified = trackItemUpdateMatches(before, after, args);
return mutationResult(verified, { updated: true, before, after, changedFields: requested.length }, "track_item_readback", "Update timeline item");
}
async function makeSplitEdit(args) {
assertObject(args); assertOnlyKeys(args, ["kind", "audioTrackIndex", "audioClipIndex", "videoTrackIndex", "videoClipIndex", "extensionSeconds", "operationId"]);
const kind = enumValue(args.kind, "kind", ["j_cut", "l_cut"]), extension = finiteNumber(args.extensionSeconds, "extensionSeconds", 0.001, 60);
const context = await activeContext(true);
const audioContext = { ...context, item: await trackItemAt(context.sequence, "audio", nonNegativeInt(args.audioTrackIndex, "audioTrackIndex"), nonNegativeInt(args.audioClipIndex, "audioClipIndex")), mediaType: "audio", trackIndex: args.audioTrackIndex, clipIndex: args.audioClipIndex };
const videoContext = { ...context, item: await trackItemAt(context.sequence, "video", nonNegativeInt(args.videoTrackIndex, "videoTrackIndex"), nonNegativeInt(args.videoClipIndex, "videoClipIndex")), mediaType: "video", trackIndex: args.videoTrackIndex, clipIndex: args.videoClipIndex };
const before = { audio: await trackItemSnapshot(audioContext), video: await trackItemSnapshot(videoContext) };
if (!numbersEqual(before.audio.speed, 1) || before.audio.reversed) throw commandError("UXP_TARGET_UNSUPPORTED", "Split edits require forward 1x audio so source sync can be preserved");
const edge = kind === "j_cut" ? "startSeconds" : "endSeconds";
if (!numbersEqual(before.audio[edge], before.video[edge])) throw commandError("UXP_STALE_TRACK_ITEM", "Audio and video " + (kind === "j_cut" ? "start" : "end") + " edges are not aligned");
const timelineValue = Number(before.audio[edge]) + (kind === "j_cut" ? -extension : extension);
const sourceField = kind === "j_cut" ? "inSeconds" : "outSeconds", sourceValue = Number(before.audio[sourceField]) + (kind === "j_cut" ? -extension : extension);
if (timelineValue < 0 || sourceValue < 0) throw commandError("UXP_TARGET_UNSUPPORTED", "The requested J-cut exceeds the available leading timeline or source handle");
context.project.lockedAccess(() => {
const actions = kind === "j_cut"
? [audioContext.item.createSetStartAction(tick(timelineValue)), audioContext.item.createSetInPointAction(tick(sourceValue))]
: [audioContext.item.createSetEndAction(tick(timelineValue)), audioContext.item.createSetOutPointAction(tick(sourceValue))];
commitActions(context.project, kind === "j_cut" ? "Create J-cut" : "Create L-cut", actions);
});
const after = await trackItemSnapshot(audioContext), verified = numbersEqual(after[edge], timelineValue) && numbersEqual(after[sourceField], sourceValue);
return mutationResult(verified, { splitEdit: kind, extensionSeconds: extension, before, after }, "split_edit_audio_edge_and_source_readback", kind === "j_cut" ? "Create J-cut" : "Create L-cut");
}
async function editorContext(requireTransactions) {
const context = await activeContext(requireTransactions), editor = ppro.SequenceEditor.getEditor(context.sequence);
if (!editor) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere did not return a SequenceEditor");
return { ...context, editor };
}
async function insertTimelineItem(args) {
assertObject(args); assertOnlyKeys(args, ["projectItemId", "timeSeconds", "videoTrackIndex", "audioTrackIndex", "limitShift", "operationId"]);
const context = await editorContext(true), item = await resolveProjectItem(context.project, args.projectItemId, true), time = tick(finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400), "timeSeconds"), video = nonNegativeInt(args.videoTrackIndex, "videoTrackIndex"), audio = nonNegativeInt(args.audioTrackIndex, "audioTrackIndex"), limitShift = optionalBoolean(args.limitShift, false, "limitShift");
context.project.lockedAccess(() => {
commitActions(context.project, "Insert timeline item", [context.editor.createInsertProjectItemAction(item, time, video, audio, limitShift)]);
});
return mutationResult(false, { inserted: true, projectItemId: await projectItemId(item), timeSeconds: args.timeSeconds, videoTrackIndex: video, audioTrackIndex: audio }, "sequence_editor_transaction", "Insert timeline item");
}
async function overwriteTimelineItem(args) {
assertObject(args); assertOnlyKeys(args, ["projectItemId", "timeSeconds", "videoTrackIndex", "audioTrackIndex", "operationId"]);
const context = await editorContext(true), item = await resolveProjectItem(context.project, args.projectItemId, true), time = tick(finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400), "timeSeconds"), video = nonNegativeInt(args.videoTrackIndex, "videoTrackIndex"), audio = nonNegativeInt(args.audioTrackIndex, "audioTrackIndex");
context.project.lockedAccess(() => {
commitActions(context.project, "Overwrite timeline item", [context.editor.createOverwriteItemAction(item, time, video, audio)]);
});
return mutationResult(false, { overwritten: true, projectItemId: await projectItemId(item), timeSeconds: args.timeSeconds, videoTrackIndex: video, audioTrackIndex: audio }, "sequence_editor_transaction", "Overwrite timeline item");
}
async function cloneTimelineSelection(args) {
assertObject(args); assertOnlyKeys(args, ["timeOffsetSeconds", "videoTrackOffset", "audioTrackOffset", "alignToVideo", "insert", "operationId"]);
const context = await editorContext(true), selected = await selectedTrackItems(context.sequence), offset = tick(args.timeOffsetSeconds, "timeOffsetSeconds"), videoOffset = boundedInt(args.videoTrackOffset == null ? 0 : args.videoTrackOffset, "videoTrackOffset", -128, 128), audioOffset = boundedInt(args.audioTrackOffset == null ? 0 : args.audioTrackOffset, "audioTrackOffset", -128, 128), align = optionalBoolean(args.alignToVideo, true, "alignToVideo"), insert = optionalBoolean(args.insert, false, "insert");
context.project.lockedAccess(() => {
const actions = selected.items.map((item) => context.editor.createCloneTrackItemAction(item, offset, videoOffset, audioOffset, align, insert));
commitActions(context.project, "Clone selected timeline items", actions);
});
return mutationResult(false, { cloned: selected.items.length, timeOffsetSeconds: args.timeOffsetSeconds, videoTrackOffset: videoOffset, audioTrackOffset: audioOffset }, "sequence_editor_transaction", "Clone selected timeline items");
}
async function removeTimelineSelection(args) {
assertObject(args); assertOnlyKeys(args, ["ripple", "mediaType", "shiftOverlapping", "operationId"]);
const context = await editorContext(true), selected = await selectedTrackItems(context.sequence), mediaType = enumValue(args.mediaType == null ? "any" : args.mediaType, "mediaType", ["any", "video", "audio"]), constants = ppro.Constants && ppro.Constants.MediaType || {}, value = constants[mediaType.toUpperCase()];
if (value == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "MediaType constants are unavailable");
context.project.lockedAccess(() => {
commitActions(context.project, "Remove selected timeline items", [context.editor.createRemoveItemsAction(selected.selection, optionalBoolean(args.ripple, false, "ripple"), value, optionalBoolean(args.shiftOverlapping, false, "shiftOverlapping"))]);
});
return mutationResult(false, { removed: selected.items.length, mediaType }, "sequence_editor_transaction", "Remove selected timeline items");
}
async function insertMogrtPath(args) {
assertObject(args); assertOnlyKeys(args, ["filePath", "timeSeconds", "videoTrackIndex", "audioTrackIndex", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "MOGRT insertion is a direct SequenceEditor call without an Action boundary");
const context = await editorContext(false), path = await allowedPath(args.filePath, "filePath", "file"), values = Array.from(await context.editor.insertMogrtFromPath(path, tick(finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400), "timeSeconds"), nonNegativeInt(args.videoTrackIndex, "videoTrackIndex"), nonNegativeInt(args.audioTrackIndex, "audioTrackIndex")) || []);
return directMutationResult(false, { inserted: values.length, source: "path" }, "sequence_editor_host_return");
}
async function insertMogrtLibrary(args) {
assertObject(args); assertOnlyKeys(args, ["libraryName", "elementName", "timeSeconds", "videoTrackIndex", "audioTrackIndex", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "MOGRT insertion is a direct SequenceEditor call without an Action boundary");
const context = await editorContext(false), values = Array.from(await context.editor.insertMogrtFromLibrary(boundedString(args.libraryName, "libraryName", 255), boundedString(args.elementName, "elementName", 255), tick(finiteNumber(args.timeSeconds, "timeSeconds", 0, 86400), "timeSeconds"), nonNegativeInt(args.videoTrackIndex, "videoTrackIndex"), nonNegativeInt(args.audioTrackIndex, "audioTrackIndex")) || []);
return directMutationResult(false, { inserted: values.length, source: "library" }, "sequence_editor_host_return");
}
async function inspectSequences(args) {
assertObject(args); assertOnlyKeys(args, []);
const project = await activeProject(false), active = await project.getActiveSequence(), sequences = await listSequences(project);
return { activeSequenceId: guidString(active && active.guid), count: sequences.length, sequences };
}
async function createSequenceFromMedia(args) {
assertObject(args); assertOnlyKeys(args, ["name", "projectItemIds", "targetBinId", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "Creating a sequence from media is a direct Project call without an Action boundary");
const project = await activeProject(false), ids = boundedStringArray(args.projectItemIds, "projectItemIds", 64, 512), clips = [];
for (const id of ids) clips.push(asClip(await findProjectItem(project, id), "projectItemId"));
const target = args.targetBinId ? await resolveFolder(project, args.targetBinId, "targetBinId") : undefined, name = boundedString(args.name, "name", 255);
return withAppendLock(appendLockKey(project, "sequences", "all"), async () => {
const before = await listSequences(project);
assertAppendCapacity(before, MAX_SEQUENCES, "Sequence creation");
const reconciledSequence = async () => {
try {
const after = await listSequences(project);
return { readable: true, sequence: after.find((item) => !before.some((old) => old.id === item.id)) || null };
} catch (_) { return { readable: false, sequence: null }; }
};
const partialReceipt = async (boundary, sequence) => directMutationResult(false, {
created: !!sequence, partial: true, sequence: await safeSequenceSnapshot(sequence)
}, boundary);
let sequence;
try { sequence = await project.createSequenceFromMedia(name, clips, target); }
catch (error) {
const reconciliation = await reconciledSequence();
if (reconciliation.sequence) return partialReceipt("create_sequence_host_reconciliation", reconciliation.sequence);
if (!reconciliation.readable) return partialReceipt("create_sequence_reconciliation_readback_failed", null);
throw commandError("UXP_HOST_REJECTED", "Premiere rejected sequence creation: " + error.message);
}
if (!sequence) {
const reconciliation = await reconciledSequence();
if (reconciliation.sequence) return partialReceipt("create_sequence_host_return", reconciliation.sequence);
if (!reconciliation.readable) return partialReceipt("create_sequence_reconciliation_readback_failed", null);
throw commandError("UXP_HOST_REJECTED", "Premiere did not create a sequence");
}
const snapshot = await safeSequenceSnapshot(sequence);
if (!snapshot || !snapshot.id) return partialReceipt("create_sequence_identity_readback", sequence);
return directMutationResult(false, { created: true, partial: false, sequence: snapshot }, "create_sequence_host_return");
});
}
async function createEmptySequence(args) {
assertObject(args); assertOnlyKeys(args, ["name", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "Creating an empty sequence is a direct Project call without an Action boundary");
if (typeof args.operationId !== "string" || !args.operationId) {
throw commandError("UXP_INVALID_ARGUMENT", "operationId is required for non-undoable empty sequence creation");
}
const name = boundedString(args.name, "name", 255), project = await activeProject(false);
if (typeof project.createSequence !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose documented empty sequence creation");
return withAppendLock(appendLockKey(project, "sequences", "all"), async () => {
const before = await listSequences(project);
assertAppendCapacity(before, MAX_SEQUENCES, "Empty sequence creation");
if (before.some((item) => !item.id)) throw commandError("UXP_SEQUENCE_ID_UNAVAILABLE", "Sequence creation requires stable IDs for every preflight sequence");
const beforeIds = new Set(before.map((item) => item.id));
const reconcile = async () => {
try {
const after = await listSequences(project);
if (after.some((item) => !item.id)) return { readable: false, added: [], matchingName: [] };
const added = after.filter((item) => !beforeIds.has(item.id));
return { readable: true, added, matchingName: added.filter((item) => item.name === name) };
} catch (_) { return { readable: false, added: [], matchingName: [] }; }
};
const receipt = (verified, values, boundary) => directSequenceCreationResult(verified, values, boundary);
let created;
try { created = await project.createSequence(name); }
catch (error) {
const reconciliation = await reconcile();
if (!reconciliation.readable) return receipt(false, { created: false, partial: true, sequence: null }, "create_empty_sequence_reconciliation_readback_failed");
if (reconciliation.matchingName.length === 1) return receipt(false, { created: true, partial: true, sequence: reconciliation.matchingName[0] }, "create_empty_sequence_host_reconciliation");
throw commandError("UXP_HOST_REJECTED", "Premiere rejected empty sequence creation: " + error.message);
}
const returned = await safeSequenceSnapshot(created), reconciliation = await reconcile();
if (!reconciliation.readable) return receipt(false, { created: !!returned, partial: true, sequence: returned }, "create_empty_sequence_readback_failed");
const returnedMatch = returned && returned.id
? reconciliation.added.find((item) => item.id === returned.id) || null
: null;
if (returnedMatch && returnedMatch.name === name) {
return receipt(true, { created: true, partial: false, sequence: returnedMatch }, "create_empty_sequence_identity_readback");
}
if (!returned?.id && reconciliation.matchingName.length === 1 && reconciliation.added.length === 1) {
return receipt(true, { created: true, partial: false, sequence: reconciliation.matchingName[0] }, "create_empty_sequence_identity_readback");
}
return receipt(false, {
created: !!(returned || reconciliation.matchingName.length), partial: true,
sequence: returned || reconciliation.matchingName[0] || null
}, "create_empty_sequence_identity_readback");
});
}
async function deriveSilenceSequence(args) {
assertObject(args); assertOnlyKeys(args, ["sourceProjectItemId", "name", "keepRanges", "targetBinId", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "Derived silence removal creates subclips and a new sequence; the original source is not changed");
if (!Array.isArray(args.keepRanges) || !args.keepRanges.length || args.keepRanges.length > 64) throw commandError("UXP_INVALID_ARGUMENT", "keepRanges must contain 1-64 ranges");
const ranges = args.keepRanges.map((range, index) => {
assertObject(range); assertOnlyKeys(range, ["startSeconds", "endSeconds", "startFrame", "endFrame"]);
const startSeconds = finiteNumber(range.startSeconds, "keepRanges[" + index + "].startSeconds", 0, 86400);
const endSeconds = finiteNumber(range.endSeconds, "keepRanges[" + index + "].endSeconds", 0, 86400);
if (endSeconds <= startSeconds) throw commandError("UXP_INVALID_ARGUMENT", "keepRanges must have positive duration");
if (index && startSeconds < args.keepRanges[index - 1].endSeconds) throw commandError("UXP_INVALID_ARGUMENT", "keepRanges must be ordered and non-overlapping");
const startFrame = boundedInt(range.startFrame, "startFrame", 0, 100000000), endFrame = boundedInt(range.endFrame, "endFrame", 1, 100000000);
if (endFrame <= startFrame) throw commandError("UXP_INVALID_ARGUMENT", "keepRanges frame bounds must have positive duration");
return { startSeconds, endSeconds, startFrame, endFrame };
});
const project = await activeProject(true), source = asClip(await findProjectItem(project, args.sourceProjectItemId), "sourceProjectItemId");
if (typeof source.createSubClipAction !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot create documented subclips for silence removal");
const parent = asFolder(await source.getParentBin(), "source parent");
const target = args.targetBinId ? await resolveFolder(project, args.targetBinId, "targetBinId") : undefined;
const name = boundedString(args.name, "name", 255), prefix = name + " Keep";
return withAppendLock(appendLockKey(project, "bin", await projectItemId(parent)), async () => {
const lockedBefore = Array.from(await parent.getItems() || []), beforeIds = new Set();
if (lockedBefore.length + ranges.length > MAX_BIN_CHILDREN) throw commandError("UXP_PROJECT_TOO_LARGE", "Silence-removal subclips would exceed the " + MAX_BIN_CHILDREN + " item bin limit");
for (const item of lockedBefore) beforeIds.add(await projectItemId(item));
let subclipCommitError = false, mutationAttempted = false;
try {
project.lockedAccess(() => {
const actions = ranges.map((range, index) => source.createSubClipAction(prefix + " " + (index + 1), tick(range.startSeconds, "startSeconds"), tick(range.endSeconds, "endSeconds"), true, { takeVideo: true, takeAudio: true }));
mutationAttempted = true;
commitActions(project, "Create silence-removal subclips", actions);
});
} catch (error) { if (!mutationAttempted) throw error; subclipCommitError = true; }
let createdItems;
try {
const after = Array.from(await parent.getItems() || []), added = [];
for (const item of after) if (!beforeIds.has(await projectItemId(item))) added.push(item);
createdItems = ranges.map((_, index) => added.find((item) => String(item.name || "") === prefix + " " + (index + 1))).filter(Boolean);
} catch (_) {
return directMutationResult(false, { partial: true, createdSubclips: [], sequence: null, originalChanged: false }, "derived_subclip_readback_failed");
}
const partialReceipt = async (boundary, sequence, insertedProjectItemIds) => directMutationResult(false, {
partial: true,
createdSubclips: await safeProjectItemSnapshots(createdItems),
sequence: await safeSequenceSnapshot(sequence),
insertedProjectItemIds: insertedProjectItemIds || [],
originalChanged: false
}, boundary);
if (subclipCommitError || createdItems.length !== ranges.length) return partialReceipt(subclipCommitError ? "derived_subclip_partial_transaction_receipt" : "derived_subclip_count_readback", null);
return withAppendLock(appendLockKey(project, "sequences", "all"), async () => {
let beforeSequences;
try { beforeSequences = await listSequences(project); }
catch (_) { return partialReceipt("derived_sequence_preflight_readback_failed", null); }
if (beforeSequences.length >= MAX_SEQUENCES) return partialReceipt("derived_sequence_capacity_preflight", null);
const reconciledSequence = async () => {
try {
const afterSequences = await listSequences(project);
return afterSequences.find((item) => !beforeSequences.some((old) => old.id === item.id)) || null;
} catch (_) { return null; }
};
let sequence;
try { sequence = await project.createSequenceFromMedia(name, [createdItems[0]], target); } catch (_) { return partialReceipt("derived_sequence_host_reconciliation", await reconciledSequence()); }
if (!sequence) return partialReceipt("derived_sequence_host_return", await reconciledSequence());
const inserted = await safeProjectItemIds([createdItems[0]]);
try {
const editor = ppro.SequenceEditor.getEditor(sequence);
if (!editor || typeof editor.createInsertProjectItemAction !== "function") throw new Error("editor unavailable");
let offset = ranges[0].endSeconds - ranges[0].startSeconds;
for (let index = 1; index < createdItems.length; index++) {
project.lockedAccess(() => commitActions(project, "Insert silence-removal segment", [editor.createInsertProjectItemAction(createdItems[index], tick(offset, "timeline offset"), 0, 0, false)]));
inserted.push(...await safeProjectItemIds([createdItems[index]])); offset += ranges[index].endSeconds - ranges[index].startSeconds;
}
} catch (_) {
return partialReceipt("derived_sequence_partial_insert_receipt", sequence, inserted);
}
const createdSubclips = await safeProjectItemSnapshots(createdItems), sequenceSnapshot = await safeSequenceSnapshot(sequence);
if (createdSubclips.length !== createdItems.length || createdSubclips.some((item) => !item.id)
|| !sequenceSnapshot || !sequenceSnapshot.id || inserted.length !== createdItems.length || inserted.some((id) => !id)) {
return partialReceipt("derived_sequence_final_identity_readback", sequence, inserted);
}
return directMutationResult(false, { created: true, partial: false, createdSubclips: createdSubclips, sequence: sequenceSnapshot, insertedProjectItemIds: inserted, originalChanged: false }, "derived_sequence_structure_unverified");
});
});
}
async function cloneSequence(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "operationId"]);
const project = await activeProject(true), sequence = await resolveSequence(project, args.sequenceId);
return withAppendLock(appendLockKey(project, "sequences", "all"), async () => {
const before = await listSequences(project);
assertAppendCapacity(before, MAX_SEQUENCES, "Sequence cloning");
project.lockedAccess(() => {
commitActions(project, "Clone sequence", [sequence.createCloneAction()]);
});
const after = await listSequences(project), added = after.filter((value) => !before.some((old) => old.id === value.id));
return mutationResult(added.length === 1, { cloned: true, source: await sequenceSnapshot(sequence), sequence: added[0] || null }, "sequence_identity_readback", "Clone sequence");
});
}
async function createSubsequence(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "ignoreTrackTargeting", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "Creating a subsequence is a direct Sequence call without an Action boundary");
const project = await activeProject(false), sequence = await resolveSequence(project, args.sequenceId), ignoreTrackTargeting = optionalBoolean(args.ignoreTrackTargeting, false, "ignoreTrackTargeting");
return withAppendLock(appendLockKey(project, "sequences", "all"), async () => {
const before = await listSequences(project);
assertAppendCapacity(before, MAX_SEQUENCES, "Subsequence creation");
const reconciledSequence = async () => {
try {
const after = await listSequences(project);
return { readable: true, sequence: after.find((item) => !before.some((old) => old.id === item.id)) || null };
} catch (_) { return { readable: false, sequence: null }; }
};
const partialReceipt = async (boundary, created) => directMutationResult(false, {
created: !!created, partial: true, sequence: await safeSequenceSnapshot(created)
}, boundary);
let created;
try { created = await sequence.createSubsequence(ignoreTrackTargeting); }
catch (error) {
const reconciliation = await reconciledSequence();
if (reconciliation.sequence) return partialReceipt("create_subsequence_host_reconciliation", reconciliation.sequence);
if (!reconciliation.readable) return partialReceipt("create_subsequence_reconciliation_readback_failed", null);
throw commandError("UXP_HOST_REJECTED", "Premiere rejected subsequence creation: " + error.message);
}
if (!created) {
const reconciliation = await reconciledSequence();
if (reconciliation.sequence) return partialReceipt("create_subsequence_host_return", reconciliation.sequence);
if (!reconciliation.readable) return partialReceipt("create_subsequence_reconciliation_readback_failed", null);
throw commandError("UXP_HOST_REJECTED", "Premiere did not create a subsequence");
}
const snapshot = await safeSequenceSnapshot(created);
if (!snapshot || !snapshot.id) return partialReceipt("create_subsequence_identity_readback", created);
return directMutationResult(false, { created: true, partial: false, sequence: snapshot }, "create_subsequence_host_return");
});
}
async function activateSequence(args) { return sequenceDirectAction(args, "activate", "setActiveSequence"); }
async function openSequence(args) { return sequenceDirectAction(args, "open", "openSequence"); }
async function closeSequence(args) { return sequenceDirectAction(args, "close", "closeSequence"); }
async function sequenceDirectAction(args, action, method) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "operationId"]);
const project = await activeProject(false), sequence = await resolveSequence(project, args.sequenceId), accepted = await project[method](sequence);
if (!accepted) throw commandError("UXP_HOST_REJECTED", "Premiere rejected sequence " + action);
const resultField = { activate: "activated", open: "opened", close: "closed" }[action];
return { [resultField]: true, outcome: "committed_unverified", verified: false, sequence: await sequenceSnapshot(sequence), verificationBoundary: "host_return",
operation: operationSemantics({ mutatesProject: false, verificationStatus: "not_verified", verificationBoundary: "host_return", verificationEvidence: [{ type: "host_return", accepted: true }] }) };
}
async function deleteSequence(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "expectedName", "confirmNonUndoable", "operationId"]);
requireConfirmation(args.confirmNonUndoable, "Deleting a sequence is not exposed as an undoable Action");
const project = await activeProject(false);
await listSequences(project);
const sequence = await resolveSequence(project, args.sequenceId), snapshot = await sequenceSnapshot(sequence);
assertExpected(snapshot.name, args.expectedName, "UXP_STALE_SEQUENCE", "Sequence name");
if (!await project.deleteSequence(sequence)) throw commandError("UXP_HOST_REJECTED", "Premiere rejected sequence deletion");
const remaining = await listSequences(project), verified = !remaining.some((value) => value.id === snapshot.id);
return directMutationResult(verified, { deleted: true, sequence: snapshot }, "sequence_absence_readback");
}
function encoderManager() {
const manager = ppro.EncoderManager.getManager();
if (!manager) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere did not return EncoderManager");
if (manager.isAMEInstalled === false) throw commandError("UXP_DEPENDENCY_UNAVAILABLE", "Adobe Media Encoder is not installed");
return manager;
}
async function encoderPreflight(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "presetFile"]);
const manager = encoderManager();
let extension = null;
if (args.presetFile != null) {
const project = await activeProject(false);
const sequence = await resolveSequence(project, args.sequenceId), preset = await allowedPath(args.presetFile, "presetFile", "file");
extension = await ppro.EncoderManager.getExportFileExtension(sequence, preset);
}
return { ameInstalled: manager.isAMEInstalled !== false, extension, sequenceId: args.sequenceId || null };
}
function exportType(value) {
const name = enumValue(value, "exportType", ["queueToAme", "queueToApp", "immediately"]), constants = ppro.Constants && ppro.Constants.ExportType || {};
const map = { queueToAme: "QUEUE_TO_AME", queueToApp: "QUEUE_TO_APP", immediately: "IMMEDIATELY" }, resolved = constants[map[name]];
if (resolved != null) return resolved;
const fallback = { queueToAme: ppro.EncoderManager.EXPORT_QUEUE_TO_AME, queueToApp: ppro.EncoderManager.EXPORT_QUEUE_TO_APP, immediately: ppro.EncoderManager.EXPORT_IMMEDIATELY }[name];
if (fallback == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "Encoder export type constants are unavailable");
return fallback;
}
async function encodeSequence(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "exportType", "outputFile", "presetFile", "exportFull", "confirmExternalWrite", "operationId"]);
requireExternalWrite(args.confirmExternalWrite);
const project = await activeProject(false), sequence = await resolveSequence(project, args.sequenceId), manager = encoderManager(), output = await allowedPath(args.outputFile, "outputFile", "file"), preset = await allowedPath(args.presetFile, "presetFile", "file");
const job = beginEncoderJob("sequence", args.operationId);
const accepted = await runTrackedEncode(job, function () {
return manager.exportSequence(sequence, exportType(args.exportType), output, preset, optionalBoolean(args.exportFull, true, "exportFull"));
}, "Premiere rejected sequence export");
return externalWriteResult({ queued: true, kind: "sequence", sequence: await sequenceSnapshot(sequence), outputFile: output, encodeJob: accepted });
}
async function encodeProjectItem(args) {
assertObject(args); assertOnlyKeys(args, ["projectItemId", "outputFile", "presetFile", "workArea", "removeUponCompletion", "startQueueImmediately", "confirmExternalWrite", "operationId"]);
requireExternalWrite(args.confirmExternalWrite);
const project = await activeProject(false), clip = asClip(await resolveProjectItem(project, args.projectItemId, true), "projectItemId"), manager = encoderManager(), output = await allowedPath(args.outputFile, "outputFile", "file"), preset = await allowedPath(args.presetFile, "presetFile", "file");
const job = beginEncoderJob("projectItem", args.operationId);
const accepted = await runTrackedEncode(job, function () {
return manager.encodeProjectItem(clip, output, preset, boundedInt(args.workArea == null ? 0 : args.workArea, "workArea", 0, 16), optionalBoolean(args.removeUponCompletion, false, "removeUponCompletion"), optionalBoolean(args.startQueueImmediately, true, "startQueueImmediately"));
}, "Premiere rejected project-item encode");
return externalWriteResult({ queued: true, kind: "projectItem", projectItemId: await projectItemId(clip), outputFile: output, encodeJob: accepted });
}
async function encodeFile(args) {
assertObject(args); assertOnlyKeys(args, ["filePath", "outputFile", "presetFile", "inSeconds", "outSeconds", "workArea", "removeUponCompletion", "startQueueImmediately", "confirmExternalWrite", "operationId"]);
requireExternalWrite(args.confirmExternalWrite);
const manager = encoderManager(), input = await allowedPath(args.filePath, "filePath", "file"), output = await allowedPath(args.outputFile, "outputFile", "file"), preset = await allowedPath(args.presetFile, "presetFile", "file"), start = finiteNumber(args.inSeconds, "inSeconds", 0, 86400), end = finiteNumber(args.outSeconds, "outSeconds", 0, 86400);
if (end <= start) throw commandError("UXP_INVALID_ARGUMENT", "outSeconds must be greater than inSeconds");
const job = beginEncoderJob("file", args.operationId);
const accepted = await runTrackedEncode(job, function () {
return manager.encodeFile(input, output, preset, tick(start), tick(end), boundedInt(args.workArea == null ? 0 : args.workArea, "workArea", 0, 16), optionalBoolean(args.removeUponCompletion, false, "removeUponCompletion"), optionalBoolean(args.startQueueImmediately, true, "startQueueImmediately"));
}, "Premiere rejected file encode");
return externalWriteResult({ queued: true, kind: "file", outputFile: output, encodeJob: accepted });
}
function inspectEncoderJobs(args) {
assertObject(args); assertOnlyKeys(args, ["jobId", "limit"]);
return events.listEncodeJobs({
jobId: args.jobId == null ? undefined : boundedString(args.jobId, "jobId", 128),
limit: args.limit == null ? undefined : boundedInt(args.limit, "limit", 1, 64)
});
}
function waitForEncoderJob(args) {
assertObject(args); assertOnlyKeys(args, ["jobId", "timeoutMs"]);
return events.waitForEncodeJob({
jobId: boundedString(args.jobId, "jobId", 128),
timeoutMs: args.timeoutMs == null ? 0 : boundedInt(args.timeoutMs, "timeoutMs", 0, 60000)
});
}
function beginEncoderJob(kind, operationId) {
if (!canTrackEncoderJobs()) return null;
return events.beginEncodeJob({ kind, operationId: operationId || undefined });
}
async function runTrackedEncode(job, callback, rejectionMessage) {
let accepted;
try {
accepted = await callback();
} catch (error) {
if (job) events.markEncodeRejected(job.jobId, "host_error");
throw error;
}
if (!accepted) {
if (job) events.markEncodeRejected(job.jobId, "host_rejected");
throw commandError("UXP_HOST_REJECTED", rejectionMessage);
}
return job ? events.markEncodeAccepted(job.jobId) : null;
}
function operationSemantics(options) { return Protocol && typeof Protocol.operationSemantics === "function" ? Protocol.operationSemantics(options) : undefined; }
function mutationResult(verified, values, boundary, undoLabel) {
return { ...values, outcome: verified ? "verified" : "committed_unverified", verified, verificationBoundary: boundary,
operation: operationSemantics({ mutatesProject: true, verificationStatus: verified ? "verified" : "not_verified", verificationBoundary: boundary, verificationEvidence: [{ type: boundary, verified }], undoSupported: true, undoLabel, transactionActionGroup: true, cancellationSupported: true }) };
}
function sequenceDisplayFormatResult(verified, values, boundary) {
return { ...values, outcome: verified ? "verified" : "committed_unverified", verified, verificationBoundary: boundary,
operation: operationSemantics({ mutatesProject: true, verificationStatus: verified ? "verified" : "not_verified", verificationBoundary: boundary, verificationEvidence: [{ type: boundary, verified }], undoSupported: true, undoLabel: "Update sequence display formats", transactionActionGroup: true, cancellationSupported: false }) };
}
function sequenceDisplayFormatNoop(values, boundary) {
return { ...values, outcome: "verified", verified: true, verificationBoundary: boundary,
operation: operationSemantics({ mutatesProject: false, verificationStatus: "verified", verificationBoundary: boundary, verificationEvidence: [{ type: boundary, verified: true }], cancellationSupported: false }) };
}
function verifiedNoopResult(values, boundary) {
return { ...values, outcome: "verified", verified: true, verificationBoundary: boundary,
operation: operationSemantics({ mutatesProject: false, verificationStatus: "verified", verificationBoundary: boundary, verificationEvidence: [{ type: boundary, verified: true }], cancellationSupported: true }) };
}
function directMutationResult(verified, values, boundary) {
return { ...values, outcome: verified ? "verified" : "committed_unverified", verified, verificationBoundary: boundary,
operation: operationSemantics({ mutatesProject: true, verificationStatus: verified ? "verified" : "not_verified", verificationBoundary: boundary, verificationEvidence: [{ type: boundary, verified }], undoSupported: false, cancellationSupported: true }) };
}
function directSequenceCreationResult(verified, values, boundary) {
return { ...values, outcome: verified ? "verified" : "committed_unverified", verified, verificationBoundary: boundary,
operation: operationSemantics({ mutatesProject: true, verificationStatus: verified ? "verified" : "not_verified", verificationBoundary: boundary, verificationEvidence: [{ type: boundary, verified }], undoSupported: false, cancellationSupported: false }) };
}
function externalWriteResult(values) {
return { ...values, outcome: "committed_unverified", verified: false, verificationBoundary: "encoder_host_return",
operation: operationSemantics({ mutatesProject: false, externalSideEffect: true, verificationStatus: "not_verified", verificationBoundary: "encoder_host_return", verificationEvidence: [{ type: "host_return", accepted: true }], undoSupported: false, cancellationSupported: true }) };
}
async function allowedPath(value, label, kind) {
const path = boundedString(value, label, 4096);
return workspace && typeof workspace.assertPathAllowed === "function" ? await workspace.assertPathAllowed(path, { label, kind }) : path;
}
async function boundedPathArray(value, name, maximum, kind) {
if (!Array.isArray(value) || !value.length || value.length > maximum) throw commandError("UXP_INVALID_ARGUMENT", name + " must contain 1-" + maximum + " paths");
return Promise.all(value.map((path, index) => allowedPath(path, name + "[" + index + "]", kind)));
}
function boundedMarkers(collection) {
const values = Array.from(collection.getMarkers() || []);
if (values.length > MAX_MARKERS) throw commandError("UXP_PROJECT_TOO_LARGE", "Marker lookup exceeds " + MAX_MARKERS + " entries");
return values;
}
function assertAppendCapacity(values, maximum, operation) {
if (values.length >= maximum) {
throw commandError("UXP_PROJECT_TOO_LARGE", operation + " requires readback capacity below " + maximum + " entries");
}
}
function appendLockKey(project, collectionType, targetId) {
return guidString(project && project.guid) + ":" + collectionType + ":" + targetId;
}
async function markerLockKey(context) {
const ownerId = context.ownerType === "sequence" ? guidString(context.owner && context.owner.guid) : await projectItemId(context.owner);
return appendLockKey(context.project, "markers", context.ownerType + ":" + ownerId);
}
async function withAppendLock(key, callback) {
const previous = appendLocks.get(key) || Promise.resolve();
let release = function () {};
const current = new Promise((resolve) => { release = resolve; });
appendLocks.set(key, current);
await previous;
try {
return await callback();
} finally {
release();
if (appendLocks.get(key) === current) appendLocks.delete(key);
}
}
async function withColorLabelLock(key, callback) {
const previous = colorLabelFallbackTails.get(key) || Promise.resolve();
let release = function () {};
const current = new Promise((resolve) => { release = resolve; });
colorLabelFallbackTails.set(key, current);
await previous;
try {
return await callback();
} finally {
release();
if (colorLabelFallbackTails.get(key) === current) colorLabelFallbackTails.delete(key);
}
}
function displayFormatCode(value, label) {
const code = value && value.type;
if (!Number.isSafeInteger(code) || code < 0 || code > MAX_DISPLAY_FORMAT_CODE) {
throw commandError("UXP_INVALID_HOST_STATE", label + " did not return a bounded display-format code");
}
return code;
}
async function sequenceDisplayFormatState(settings) {
if (!settings || typeof settings.getAudioDisplayFormat !== "function" || typeof settings.getVideoDisplayFormat !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose sequence audio/video display formats");
}
const audioDisplay = await settings.getAudioDisplayFormat(), videoDisplay = await settings.getVideoDisplayFormat();
return {
audioDisplay,
videoDisplay,
values: {
audioDisplayFormat: displayFormatCode(audioDisplay, "Audio display format"),
videoDisplayFormat: displayFormatCode(videoDisplay, "Video display format")
}
};
}
function supportedDisplayFormats() {
const settings = ppro.SequenceSettings;
if (!settings || (typeof settings !== "object" && typeof settings !== "function")) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose SequenceSettings display-format constants");
const collect = function (keys, label) {
const values = [];
for (const key of keys) {
const code = settings[key];
if (!Number.isSafeInteger(code) || code < 0 || code > MAX_DISPLAY_FORMAT_CODE) continue;
values.push({ constant: key, code });
}
if (!values.length) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose " + label + " display-format constants");
return values.sort((left, right) => left.constant.localeCompare(right.constant));
};
return {
audio: collect(["AUDIO_DISPLAY_FORMAT_SAMPLE_RATE", "AUDIO_DISPLAY_FORMAT_MILISECONDS"], "audio"),
video: collect(["VIDEO_DISPLAY_FORMAT_23976", "VIDEO_DISPLAY_FORMAT_25", "VIDEO_DISPLAY_FORMAT_2997", "VIDEO_DISPLAY_FORMAT_2997_NON_DROP", "VIDEO_DISPLAY_FORMAT_16mm", "VIDEO_DISPLAY_FORMAT_35mm", "VIDEO_DISPLAY_FORMAT_FRAMES"], "video")
};
}
function validateDisplayFormatCode(value, label) {
return boundedInt(value, label, 0, MAX_DISPLAY_FORMAT_CODE);
}
function validateExpectedDisplayFormats(value) {
assertObject(value); assertOnlyKeys(value, ["audioDisplayFormat", "videoDisplayFormat"]);
if (!Object.prototype.hasOwnProperty.call(value, "audioDisplayFormat") || !Object.prototype.hasOwnProperty.call(value, "videoDisplayFormat")) {
throw commandError("UXP_INVALID_ARGUMENT", "expectedDisplayFormats must include audioDisplayFormat and videoDisplayFormat");
}
return {
audioDisplayFormat: validateDisplayFormatCode(value.audioDisplayFormat, "expectedDisplayFormats.audioDisplayFormat"),
videoDisplayFormat: validateDisplayFormatCode(value.videoDisplayFormat, "expectedDisplayFormats.videoDisplayFormat")
};
}
function validateDisplayFormatUpdates(value) {
assertObject(value); assertOnlyKeys(value, ["audioDisplayFormat", "videoDisplayFormat"]);
const updates = {};
if (Object.prototype.hasOwnProperty.call(value, "audioDisplayFormat")) updates.audioDisplayFormat = validateDisplayFormatCode(value.audioDisplayFormat, "updates.audioDisplayFormat");
if (Object.prototype.hasOwnProperty.call(value, "videoDisplayFormat")) updates.videoDisplayFormat = validateDisplayFormatCode(value.videoDisplayFormat, "updates.videoDisplayFormat");
if (!Object.keys(updates).length) throw commandError("UXP_INVALID_ARGUMENT", "updates must include audioDisplayFormat or videoDisplayFormat");
return updates;
}
function validateSequenceDisplayFormatUpdate(args) {
assertObject(args); assertOnlyKeys(args, ["sequenceId", "expectedSequenceGuid", "expectedDisplayFormats", "updates", "operationId"]);
const expectedSequenceGuid = boundedString(args.expectedSequenceGuid, "expectedSequenceGuid", 128);
return {
sequenceId: args.sequenceId == null ? expectedSequenceGuid : boundedString(args.sequenceId, "sequenceId", 128),
expectedSequenceGuid,
expectedDisplayFormats: validateExpectedDisplayFormats(args.expectedDisplayFormats),
updates: validateDisplayFormatUpdates(args.updates)
};
}
function assertRequestedDisplayFormatsSupported(updates, supported) {
const audioCodes = new Set(supported.audio.map((value) => value.code)), videoCodes = new Set(supported.video.map((value) => value.code));
if (updates.audioDisplayFormat !== undefined && !audioCodes.has(updates.audioDisplayFormat)) {
throw commandError("UXP_INVALID_ARGUMENT", "updates.audioDisplayFormat is not advertised by this Premiere host");
}
if (updates.videoDisplayFormat !== undefined && !videoCodes.has(updates.videoDisplayFormat)) {
throw commandError("UXP_INVALID_ARGUMENT", "updates.videoDisplayFormat is not advertised by this Premiere host");
}
}
function validateSettingsUpdates(value) {
assertObject(value);
const allowed = ["maximumBitDepth", "maxRenderQuality", "compositeInLinearColor", "audioSampleRate", "videoFrameRate", "videoFieldType", "videoPixelAspectRatio", "editingMode", "previewFileFormat", "previewCodec", "videoWidth", "videoHeight"];
assertOnlyKeys(value, allowed);
if (!Object.keys(value).length) throw commandError("UXP_INVALID_ARGUMENT", "updates must contain at least one sequence setting");
const result = {};
for (const key of ["maximumBitDepth", "maxRenderQuality", "compositeInLinearColor"]) if (value[key] !== undefined) result[key] = requiredBoolean(value[key], key);
if (value.audioSampleRate !== undefined) result.audioSampleRate = finiteNumber(value.audioSampleRate, "audioSampleRate", 1, 384000);
if (value.videoFrameRate !== undefined) result.videoFrameRate = finiteNumber(value.videoFrameRate, "videoFrameRate", 1, 240);
if (value.videoFieldType !== undefined) result.videoFieldType = boundedInt(value.videoFieldType, "videoFieldType", 0, 2);
if (value.videoPixelAspectRatio !== undefined) result.videoPixelAspectRatio = boundedString(value.videoPixelAspectRatio, "videoPixelAspectRatio", 64);
for (const key of ["editingMode", "previewFileFormat", "previewCodec"]) if (value[key] !== undefined) result[key] = boundedString(value[key], key, 255);
for (const key of ["videoWidth", "videoHeight"]) if (value[key] !== undefined) result[key] = boundedInt(value[key], key, 16, 32768);
return result;
}
function createFrameRate(value) {
if (!ppro.FrameRate || typeof ppro.FrameRate.createWithValue !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "FrameRate factory is unavailable");
return ppro.FrameRate.createWithValue(value);
}
function rateValue(value) { const result = value && Number(value.value); return Number.isFinite(result) ? result : null; }
function rectValue(value) { return value ? { width: Number(value.width), height: Number(value.height) } : null; }
async function maybeCall(target, method) { try { return typeof target[method] === "function" ? await target[method]() : null; } catch (_) { return null; } }
function settingMatches(after, key, value) { if (key === "videoWidth") return after.videoFrame && numbersEqual(after.videoFrame.width, value); if (key === "videoHeight") return after.videoFrame && numbersEqual(after.videoFrame.height, value); return valuesEqual(after[key], value); }
function keyframeValue(value) { return value && value.value && Object.prototype.hasOwnProperty.call(value.value, "value") ? value.value.value : value && value.value !== undefined ? value.value : null; }
function scalarValue(value) { if (typeof value !== "number" && typeof value !== "string" && typeof value !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "value must be a number, string, or boolean"); if (typeof value === "number" && !Number.isFinite(value)) throw commandError("UXP_INVALID_ARGUMENT", "value must be finite"); if (typeof value === "string" && value.length > 4000) throw commandError("UXP_INVALID_ARGUMENT", "value string exceeds 4000 characters"); return value; }
function pointValue(value, label) {
assertObject(value); assertOnlyKeys(value, ["x", "y"]);
return {
x: finiteNumber(value.x, (label || "point") + ".x", -1000000, 1000000),
y: finiteNumber(value.y, (label || "point") + ".y", -1000000, 1000000),
};
}
function colorValue(value, label) {
assertObject(value); assertOnlyKeys(value, ["red", "green", "blue", "alpha"]);
return {
red: finiteNumber(value.red, (label || "color") + ".red", -1000000, 1000000),
green: finiteNumber(value.green, (label || "color") + ".green", -1000000, 1000000),
blue: finiteNumber(value.blue, (label || "color") + ".blue", -1000000, 1000000),
alpha: finiteNumber(value.alpha, (label || "color") + ".alpha", -1000000, 1000000),
};
}
function expectedPointParameterSnapshot(value) {
assertObject(value);
assertOnlyKeys(value, ["projectId", "sequenceId", "mediaType", "trackIndex", "clipIndex", "componentIndex", "componentId", "paramIndex", "paramName", "timeVarying", "point"]);
if (typeof value.timeVarying !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "expectedSnapshot.timeVarying must be boolean");
return {
projectId: boundedString(value.projectId, "expectedSnapshot.projectId", 128),
sequenceId: boundedString(value.sequenceId, "expectedSnapshot.sequenceId", 128),
mediaType: enumValue(value.mediaType, "expectedSnapshot.mediaType", ["video", "audio"]),
trackIndex: nonNegativeInt(value.trackIndex, "expectedSnapshot.trackIndex"),
clipIndex: nonNegativeInt(value.clipIndex, "expectedSnapshot.clipIndex"),
componentIndex: nonNegativeInt(value.componentIndex, "expectedSnapshot.componentIndex"),
componentId: boundedString(value.componentId, "expectedSnapshot.componentId", 256),
paramIndex: nonNegativeInt(value.paramIndex, "expectedSnapshot.paramIndex"),
paramName: boundedStringAllowEmpty(value.paramName, "expectedSnapshot.paramName", 255),
timeVarying: value.timeVarying,
point: pointValue(value.point, "expectedSnapshot.point"),
};
}
function expectedColorParameterSnapshot(value) {
assertObject(value);
assertOnlyKeys(value, ["projectId", "sequenceId", "mediaType", "trackIndex", "clipIndex", "componentIndex", "componentId", "paramIndex", "paramName", "timeVarying", "color"]);
if (typeof value.timeVarying !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "expectedSnapshot.timeVarying must be boolean");
return {
projectId: boundedString(value.projectId, "expectedSnapshot.projectId", 128),
sequenceId: boundedString(value.sequenceId, "expectedSnapshot.sequenceId", 128),
mediaType: enumValue(value.mediaType, "expectedSnapshot.mediaType", ["video", "audio"]),
trackIndex: nonNegativeInt(value.trackIndex, "expectedSnapshot.trackIndex"),
clipIndex: nonNegativeInt(value.clipIndex, "expectedSnapshot.clipIndex"),
componentIndex: nonNegativeInt(value.componentIndex, "expectedSnapshot.componentIndex"),
componentId: boundedString(value.componentId, "expectedSnapshot.componentId", 256),
paramIndex: nonNegativeInt(value.paramIndex, "expectedSnapshot.paramIndex"),
paramName: boundedStringAllowEmpty(value.paramName, "expectedSnapshot.paramName", 255),
timeVarying: value.timeVarying,
color: colorValue(value.color, "expectedSnapshot.color"),
};
}
function trackItemUpdateMatches(before, after, args) {
if (args.startSeconds != null && !numbersEqual(after.startSeconds, args.startSeconds)) return false;
if (args.endSeconds != null && !numbersEqual(after.endSeconds, args.endSeconds)) return false;
if (args.inSeconds != null && !numbersEqual(after.inSeconds, args.inSeconds)) return false;
if (args.outSeconds != null && !numbersEqual(after.outSeconds, args.outSeconds)) return false;
if (args.disabled != null && after.disabled !== args.disabled) return false;
if (args.name != null && after.name !== args.name) return false;
if (args.moveBySeconds != null && (!numbersEqual(after.startSeconds, Number(before.startSeconds) + args.moveBySeconds) || !numbersEqual(after.endSeconds, Number(before.endSeconds) + args.moveBySeconds))) return false;
return true;
}
function canInspectProject() { return !!(ppro.Project && typeof ppro.Project.getActiveProject === "function"); }
function canUseProjectViews() { return canInspectProject() && !!(ppro.ProjectUtils && typeof ppro.ProjectUtils.getSelection === "function" && typeof ppro.ProjectUtils.getProjectViewIds === "function"); }
function canUseProjectTree() { return canUseBins(); }
function canUseMarkers() { return canInspectProject() && !!(ppro.Markers && typeof ppro.Markers.getMarkers === "function"); }
function canUseBins() { return canInspectProject() && !!(ppro.FolderItem && typeof ppro.FolderItem.cast === "function"); }
function canUseSequenceSettings() { return canInspectProject(); }
function canUseSequenceDisplayFormats() {
try { return canUseSequenceSettings() && !!supportedDisplayFormats(); }
catch (_) { return false; }
}
async function canImportProjectMedia() {
if (!canInspectProject()) return false;
const project = await ppro.Project.getActiveProject();
return !!project && ["importFiles", "importSequences", "importAEComps", "importAllAEComps"].every((method) => typeof project[method] === "function");
}
function canUseParameters() { return canInspectProject() && !!(ppro.Constants && ppro.Constants.TrackItemType); }
function canUsePointParameters() { return canUseParameters() && typeof ppro.PointF === "function"; }
function canUseColorParameters() { return canUseParameters() && typeof ppro.Color === "function"; }
function canUseTrackItems() { return canUseParameters(); }
function canInspectTimelineStructure() { return canUseTrackItems(); }
function canUseSequenceEditor() { return canInspectProject() && !!(ppro.SequenceEditor && typeof ppro.SequenceEditor.getEditor === "function"); }
function canUseMogrtPath() { return canUseSequenceEditor(); }
function canUseMogrtLibrary() { return canUseSequenceEditor(); }
function canUseSequences() { return canInspectProject(); }
async function canCreateEmptySequence() {
if (!canInspectProject()) return false;
try {
const project = await ppro.Project.getActiveProject();
return !!project && typeof project.createSequence === "function";
} catch (_) { return false; }
}
async function canDeriveSilenceSequence() {
if (!canUseSequences() || !canUseSequenceEditor() || !ppro.ClipProjectItem || typeof ppro.ClipProjectItem.cast !== "function"
|| !ppro.FolderItem || typeof ppro.FolderItem.cast !== "function" || !ppro.TickTime || typeof ppro.TickTime.createWithSeconds !== "function") return false;
const project = await ppro.Project.getActiveProject();
if (!project) return true;
return typeof project.lockedAccess === "function" && typeof project.executeTransaction === "function"
&& typeof project.getRootItem === "function" && typeof project.createSequenceFromMedia === "function";
}
async function canCloseSequence() {
if (!canInspectProject()) return false;
const project = await ppro.Project.getActiveProject();
return !!project && typeof project.closeSequence === "function";
}
function canUseEncoder() { return !!(ppro.EncoderManager && typeof ppro.EncoderManager.getManager === "function"); }
function canTrackEncoderJobs() { return !!(events && typeof events.beginEncodeJob === "function" && typeof events.listEncodeJobs === "function" && typeof events.waitForEncodeJob === "function"); }
return definitions;
}
function assertObject(value) { if (!value || typeof value !== "object" || Array.isArray(value)) throw commandError("UXP_INVALID_ARGUMENT", "args must be an object"); }
function assertOnlyKeys(value, allowed) { const unknown = Object.keys(value).filter((key) => !allowed.includes(key)); if (unknown.length) throw commandError("UXP_INVALID_ARGUMENT", "Unknown argument: " + unknown[0]); }
function boundedString(value, name, maximum) { if (typeof value !== "string" || !value.trim() || value.length > maximum) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-empty string of at most " + maximum + " characters"); return value; }
function boundedStringAllowEmpty(value, name, maximum) { if (typeof value !== "string" || value.length > maximum) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a string of at most " + maximum + " characters"); return value; }
function boundedStringArray(value, name, maximum, itemMaximum) { if (!Array.isArray(value) || !value.length || value.length > maximum) throw commandError("UXP_INVALID_ARGUMENT", name + " must contain 1-" + maximum + " values"); return value.map((item, index) => boundedString(item, name + "[" + index + "]", itemMaximum)); }
function nonNegativeInt(value, name) { if (!Number.isInteger(value) || value < 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-negative integer"); return value; }
function boundedInt(value, name, minimum, maximum) { if (!Number.isInteger(value) || value < minimum || value > maximum) throw commandError("UXP_INVALID_ARGUMENT", name + " must be an integer from " + minimum + " to " + maximum); return value; }
function finiteNumber(value, name, minimum, maximum) { const number = Number(value); if (!Number.isFinite(number) || number < minimum || number > maximum) throw commandError("UXP_INVALID_ARGUMENT", name + " must be from " + minimum + " to " + maximum); return number; }
function requiredBoolean(value, name) { if (typeof value !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", name + " must be a boolean"); return value; }
function optionalBoolean(value, fallback, name) { return value == null ? fallback : requiredBoolean(value, name); }
function enumValue(value, name, allowed) { if (!allowed.includes(value)) throw commandError("UXP_INVALID_ARGUMENT", name + " must be one of " + allowed.join(", ")); return value; }
function requireConfirmation(value, message) { if (value !== true) throw commandError("UXP_CONFIRMATION_REQUIRED", message + "; pass confirmNonUndoable=true after review"); }
function requireDestructiveConfirmation(value) { if (value !== true) throw commandError("UXP_CONFIRMATION_REQUIRED", "Removing reviewed marker snapshots is destructive; pass confirmDestructive=true after review"); }
function reviewedMarkerSnapshots(value) {
if (!Array.isArray(value) || !value.length || value.length > 128) throw commandError("UXP_INVALID_ARGUMENT", "markerSnapshots must contain 1-128 reviewed marker snapshots");
const seen = new Set(), snapshots = [];
for (let index = 0; index < value.length; index += 1) {
const snapshot = value[index];
assertObject(snapshot); assertOnlyKeys(snapshot, ["markerGuid", "expectedName", "expectedStartSeconds", "expectedDurationSeconds"]);
const markerGuid = boundedString(snapshot.markerGuid, "markerSnapshots[" + index + "].markerGuid", 128);
if (seen.has(markerGuid)) throw commandError("UXP_INVALID_ARGUMENT", "markerSnapshots must not contain duplicate markerGuid values");
seen.add(markerGuid);
snapshots.push({
markerGuid,
expectedName: boundedStringAllowEmpty(snapshot.expectedName, "markerSnapshots[" + index + "].expectedName", 255),
expectedStartSeconds: finiteNumber(snapshot.expectedStartSeconds, "markerSnapshots[" + index + "].expectedStartSeconds", 0, 86400),
expectedDurationSeconds: finiteNumber(snapshot.expectedDurationSeconds, "markerSnapshots[" + index + "].expectedDurationSeconds", 0, 86400),
});
}
return snapshots;
}
function assertExpected(actual, expected, code, label) { if (expected != null && actual !== expected) throw commandError(code, label + " no longer matches the expected value"); }
function assertExpectedNumber(actual, expected, code, label) { if (expected != null && !numbersEqual(actual, expected)) throw commandError(code, label + " no longer matches the expected value"); }
function requireExternalWrite(value) { if (value !== true) throw commandError("UXP_CONFIRMATION_REQUIRED", "Encoding writes external files and may overwrite an existing output; pass confirmExternalWrite=true after review"); }
function sameNumberArrays(left, right) { return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => numbersEqual(value, right[index])); }
function numbersEqual(left, right) { return Number.isFinite(Number(left)) && Number.isFinite(Number(right)) && Math.abs(Number(left) - Number(right)) < 0.000001; }
function pointsEqual(left, right) { return !!left && !!right && numbersEqual(left.x, right.x) && numbersEqual(left.y, right.y); }
function colorsEqual(left, right) { return !!left && !!right && numbersEqual(left.red, right.red) && numbersEqual(left.green, right.green) && numbersEqual(left.blue, right.blue) && numbersEqual(left.alpha, right.alpha); }
function valuesEqual(left, right) { return typeof right === "number" ? numbersEqual(left, right) : left === right; }
function commandError(code, message) { const error = new Error(message); error.code = code; return error; }
return { createAdvancedWorkflowDefinitions };
});