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

1728 lines
113 KiB
JavaScript
Executable File

(function (root, factory) {
const api = factory();
if (typeof module === "object" && module.exports) module.exports = api;
root.PremiereMcpCommands = api;
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
"use strict";
function createCommandRegistry(deps) {
const ppro = deps.ppro, Protocol = deps.Protocol, workspace = deps.workspace;
const completedOperations = new Map();
const inFlightOperations = new Map();
const sequenceRangeUpdateTails = new Map();
const transitionUpdateTails = new Map();
const sequencePlayheadSetTails = new Map();
const appPreferenceSetTails = new Map();
const listedVideoTransitionMatchNames = new Set();
const APP_PREFERENCE_KEYS = Object.freeze({
auto_peak_generation: "KEY_AUTO_PEAK_GENERATION",
import_workspace: "KEY_IMPORT_WORKSPACE",
show_quickstart_dialog: "KEY_SHOW_QUICKSTART_DIALOG"
});
const MAX_APP_PREFERENCE_VALUE_CHARS = 1024;
// Adobe's current Sequence Settings reference caps a sequence at 10,240 x
// 8,192 pixels. These bounds make the read-only bridge reject malformed
// host data instead of serializing an implausible frame size.
const MAX_SEQUENCE_FRAME_WIDTH = 10240;
const MAX_SEQUENCE_FRAME_HEIGHT = 8192;
const MAX_TIME_ALIGNMENT_SECONDS = 86400;
const MAX_TIME_ALIGNMENT_FRAME_COUNT = MAX_TIME_ALIGNMENT_SECONDS * 240;
// Preserve the native decimal ticks value as a string. Eighteen digits is a
// generous bounded protocol representation while avoiding an unbounded
// value from the host.
const MAX_SEQUENCE_TIMEBASE_DIGITS = 18;
const definitions = {
"capabilities.get": { readOnly: true, handler: capabilities },
"state.get": { readOnly: true, handler: stateSnapshot },
"project.snapshot": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectProject, handler: projectSnapshot },
"project.insertionBin.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectProjectInsertionBin, handler: inspectProjectInsertionBin },
"graphics.mogrtPath.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectInstalledMogrtDirectory, handler: inspectInstalledMogrtDirectory },
"project.save": { idempotent: true, minHostVersion: "25.6.0", probe: canSaveProject, handler: saveProject },
"sequence.createPreset": { destructive: true, undoable: false, requiresWorkspace: true, minHostVersion: "26.3.0", probe: canCreatePresetSequence, handler: createPresetSequence },
"sequence.range.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectSequenceRange, handler: inspectSequenceRange },
"sequence.range.update": { destructive: true, undoable: true, idempotent: true, minHostVersion: "25.6.0", probe: canUpdateSequenceRange, handler: updateSequenceRange },
"sequence.playhead.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectSequencePlayhead, handler: inspectSequencePlayhead },
"sequence.playhead.set": { idempotent: true, minHostVersion: "25.6.0", probe: canSetSequencePlayhead, handler: setSequencePlayhead },
"time.frameAlignment.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectFrameAlignment, handler: inspectFrameAlignment },
"sequence.timing.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectSequenceTiming, handler: inspectSequenceTiming },
"sequence.timingByGuid.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canInspectSequenceTimingByGuid, handler: inspectSequenceTimingByGuid },
"preferences.inspect": { readOnly: true, minHostVersion: "25.6.0", probe: canInspectAppPreferences, handler: inspectAppPreferences },
"preferences.set": { idempotent: true, minHostVersion: "25.6.0", probe: canSetAppPreferences, handler: setAppPreference },
"interchange.export": { requiresWorkspace: true, minHostVersion: "26.2.0", probe: canExportInterchange, handler: exportInterchange },
"interchange.aaf.export": { destructive: true, undoable: false, idempotent: true, requiresWorkspace: true, minHostVersion: "26.3.0", probe: canExportAaf, handler: exportAaf },
"track.rename": { destructive: true, undoable: true, idempotent: true, minHostVersion: "26.3.0", probe: canRenameTracks, handler: renameTrack },
"subclip.create": { destructive: true, undoable: true, minHostVersion: "26.3.0", probe: canCreateSubclips, handler: createSubclip },
"marker.list": { readOnly: true, minHostVersion: "26.3.0", probe: canListMarkers, handler: listMarkers },
"sourceMonitor.position.set": { idempotent: true, minHostVersion: "26.3.0", probe: canSetSourceMonitorPosition, handler: setSourceMonitorPosition },
"transcript.languages": { readOnly: true, minHostVersion: "26.3.0", probe: canQueryTranscriptLanguages, handler: transcriptLanguages },
"objectMask.has": { readOnly: true, minHostVersion: "26.3.0", probe: canInspectObjectMasks, handler: hasObjectMask },
"encoder.configure": { minHostVersion: "26.3.0", probe: canConfigureEncoder, handler: configureEncoder },
"frame.export": { requiresWorkspace: true, minHostVersion: "25.6.0", probe: canExportFrame, handler: exportFrame },
"timeline.selection.lift": { destructive: true, undoable: true, minHostVersion: "25.6.0", probe: canLiftSelection, handler: liftSelection },
"transition.video.list": { readOnly: true, minHostVersion: "25.6.0", probe: canListTransitions, handler: listTransitions },
"transition.video.inspect": { readOnly: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canInspectTransitions, handler: inspectTransition },
"transition.video.add": { destructive: true, undoable: true, idempotent: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canMutateTransitions, handler: addTransition },
"transition.video.remove": { destructive: true, undoable: true, idempotent: true, targetCapabilityProbe: true, minHostVersion: "25.6.0", probe: canMutateTransitions, handler: removeTransition }
};
let workflowApi = deps.Workflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpWorkflows);
if (!workflowApi && typeof require === "function") workflowApi = require("./workflows.cjs");
if (workflowApi && typeof workflowApi.createWorkflowDefinitions === "function") {
Object.assign(definitions, workflowApi.createWorkflowDefinitions({ ppro, Protocol, workspace }));
}
let projectItemColorLabelLocksApi = deps.ProjectItemColorLabelLocks || (typeof globalThis !== "undefined" && globalThis.PremiereMcpProjectItemColorLabelLocks);
if (!projectItemColorLabelLocksApi && typeof require === "function") projectItemColorLabelLocksApi = require("./project-item-color-label-locks.cjs");
const projectItemColorLabelLocks = projectItemColorLabelLocksApi && typeof projectItemColorLabelLocksApi.createProjectItemColorLabelLocks === "function"
? projectItemColorLabelLocksApi.createProjectItemColorLabelLocks()
: null;
let advancedWorkflowApi = deps.AdvancedWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpAdvancedWorkflows);
if (!advancedWorkflowApi && typeof require === "function") advancedWorkflowApi = require("./advanced-workflows.cjs");
if (advancedWorkflowApi && typeof advancedWorkflowApi.createAdvancedWorkflowDefinitions === "function") {
Object.assign(definitions, advancedWorkflowApi.createAdvancedWorkflowDefinitions({
ppro, Protocol, workspace, events: deps.events, colorLabelLocks: projectItemColorLabelLocks
}));
}
let dialogueWorkflowApi = deps.DialogueWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpDialogueWorkflows);
if (!dialogueWorkflowApi && typeof require === "function") dialogueWorkflowApi = require("./dialogue-workflows.cjs");
if (dialogueWorkflowApi && typeof dialogueWorkflowApi.createDialogueWorkflowDefinitions === "function") {
Object.assign(definitions, dialogueWorkflowApi.createDialogueWorkflowDefinitions({ ppro }));
}
let timelineSourceLabelWorkflowApi = deps.TimelineSourceLabelWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpTimelineSourceLabelWorkflows);
if (!timelineSourceLabelWorkflowApi && typeof require === "function") timelineSourceLabelWorkflowApi = require("./timeline-source-label-workflows.cjs");
if (timelineSourceLabelWorkflowApi && typeof timelineSourceLabelWorkflowApi.createTimelineSourceLabelWorkflowDefinitions === "function") {
Object.assign(definitions, timelineSourceLabelWorkflowApi.createTimelineSourceLabelWorkflowDefinitions({
ppro, colorLabelLocks: projectItemColorLabelLocks
}));
}
let sequencePreviewFrameWorkflowApi = deps.SequencePreviewFrameWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpSequencePreviewFrameWorkflows);
if (!sequencePreviewFrameWorkflowApi && typeof require === "function") sequencePreviewFrameWorkflowApi = require("./sequence-preview-frame-workflows.cjs");
if (sequencePreviewFrameWorkflowApi && typeof sequencePreviewFrameWorkflowApi.createSequencePreviewFrameWorkflowDefinitions === "function") {
Object.assign(definitions, sequencePreviewFrameWorkflowApi.createSequencePreviewFrameWorkflowDefinitions({ ppro }));
}
let sourceMediaProvenanceWorkflowApi = deps.SourceMediaProvenanceWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpSourceMediaProvenanceWorkflows);
if (!sourceMediaProvenanceWorkflowApi && typeof require === "function") sourceMediaProvenanceWorkflowApi = require("./source-media-provenance-workflows.cjs");
if (sourceMediaProvenanceWorkflowApi && typeof sourceMediaProvenanceWorkflowApi.createSourceMediaProvenanceWorkflowDefinitions === "function") {
Object.assign(definitions, sourceMediaProvenanceWorkflowApi.createSourceMediaProvenanceWorkflowDefinitions({ ppro }));
}
let sourceProxyWorkflowApi = deps.SourceProxyWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpSourceProxyWorkflows);
if (!sourceProxyWorkflowApi && typeof require === "function") sourceProxyWorkflowApi = require("./source-proxy-workflows.cjs");
if (sourceProxyWorkflowApi && typeof sourceProxyWorkflowApi.createSourceProxyWorkflowDefinitions === "function") {
Object.assign(definitions, sourceProxyWorkflowApi.createSourceProxyWorkflowDefinitions({ ppro }));
}
let tickTimeArithmeticWorkflowApi = deps.TickTimeArithmeticWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpTickTimeArithmeticWorkflows);
if (!tickTimeArithmeticWorkflowApi && typeof require === "function") tickTimeArithmeticWorkflowApi = require("./tick-time-arithmetic-workflows.cjs");
if (tickTimeArithmeticWorkflowApi && typeof tickTimeArithmeticWorkflowApi.createTickTimeArithmeticWorkflowDefinitions === "function") {
Object.assign(definitions, tickTimeArithmeticWorkflowApi.createTickTimeArithmeticWorkflowDefinitions({ ppro }));
}
let objectMaskAuditWorkflowApi = deps.ObjectMaskAuditWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpObjectMaskAuditWorkflows);
if (!objectMaskAuditWorkflowApi && typeof require === "function") objectMaskAuditWorkflowApi = require("./object-mask-audit-workflows.cjs");
if (objectMaskAuditWorkflowApi && typeof objectMaskAuditWorkflowApi.createObjectMaskAuditWorkflowDefinitions === "function") {
Object.assign(definitions, objectMaskAuditWorkflowApi.createObjectMaskAuditWorkflowDefinitions({ ppro }));
}
let uniqueIdentityWorkflowApi = deps.UniqueIdentityWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpUniqueIdentityWorkflows);
if (!uniqueIdentityWorkflowApi && typeof require === "function") uniqueIdentityWorkflowApi = require("./unique-identity-workflows.cjs");
if (uniqueIdentityWorkflowApi && typeof uniqueIdentityWorkflowApi.createUniqueIdentityWorkflowDefinitions === "function") {
Object.assign(definitions, uniqueIdentityWorkflowApi.createUniqueIdentityWorkflowDefinitions({ ppro }));
}
let effectParameterCatalogWorkflowApi = deps.EffectParameterCatalogWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpEffectParameterCatalogWorkflows);
if (!effectParameterCatalogWorkflowApi && typeof require === "function") effectParameterCatalogWorkflowApi = require("./effect-parameter-catalog-workflows.cjs");
if (effectParameterCatalogWorkflowApi && typeof effectParameterCatalogWorkflowApi.createEffectParameterCatalogWorkflowDefinitions === "function") {
Object.assign(definitions, effectParameterCatalogWorkflowApi.createEffectParameterCatalogWorkflowDefinitions({ ppro }));
}
let trackItemLocksApi = deps.TrackItemMutationLocks || (typeof globalThis !== "undefined" && globalThis.PremiereMcpTrackItemMutationLocks);
if (!trackItemLocksApi && typeof require === "function") trackItemLocksApi = require("./track-item-mutation-locks.cjs");
const trackItemLocks = trackItemLocksApi && typeof trackItemLocksApi.createTrackItemMutationLocks === "function"
? trackItemLocksApi.createTrackItemMutationLocks()
: null;
let slipWorkflowApi = deps.SlipWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpSlipWorkflows);
if (!slipWorkflowApi && typeof require === "function") slipWorkflowApi = require("./slip-workflows.cjs");
if (slipWorkflowApi && typeof slipWorkflowApi.createSlipWorkflowDefinitions === "function") {
Object.assign(definitions, slipWorkflowApi.createSlipWorkflowDefinitions({ ppro, locks: trackItemLocks }));
}
let slideWorkflowApi = deps.SlideWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpSlideWorkflows);
if (!slideWorkflowApi && typeof require === "function") slideWorkflowApi = require("./slide-workflows.cjs");
if (slideWorkflowApi && typeof slideWorkflowApi.createSlideWorkflowDefinitions === "function") {
Object.assign(definitions, slideWorkflowApi.createSlideWorkflowDefinitions({ ppro, locks: trackItemLocks }));
}
let cloneWorkflowApi = deps.CloneWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpCloneWorkflows);
if (!cloneWorkflowApi && typeof require === "function") cloneWorkflowApi = require("./clone-workflows.cjs");
if (cloneWorkflowApi && typeof cloneWorkflowApi.createCloneWorkflowDefinitions === "function") {
Object.assign(definitions, cloneWorkflowApi.createCloneWorkflowDefinitions({ ppro, locks: trackItemLocks }));
}
let rippleDeleteWorkflowApi = deps.RippleDeleteWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpRippleDeleteWorkflows);
if (!rippleDeleteWorkflowApi && typeof require === "function") rippleDeleteWorkflowApi = require("./ripple-delete-workflows.cjs");
if (rippleDeleteWorkflowApi && typeof rippleDeleteWorkflowApi.createRippleDeleteWorkflowDefinitions === "function") {
Object.assign(definitions, rippleDeleteWorkflowApi.createRippleDeleteWorkflowDefinitions({ ppro, locks: trackItemLocks }));
}
let nextWorkflowApi = deps.NextWorkflows || (typeof globalThis !== "undefined" && globalThis.PremiereMcpNextWorkflows);
if (!nextWorkflowApi && typeof require === "function") nextWorkflowApi = require("./next-workflows.cjs");
let nextWorkflowRuntime = null;
if (nextWorkflowApi && typeof nextWorkflowApi.createNextWorkflowRuntime === "function") {
nextWorkflowRuntime = nextWorkflowApi.createNextWorkflowRuntime({
ppro, Protocol, workspace, events: deps.events, storage: deps.storage,
now: deps.now, sleep: deps.sleep, setTimer: deps.setTimer, clearTimer: deps.clearTimer
});
Object.assign(definitions, nextWorkflowRuntime.definitions);
} else if (nextWorkflowApi && typeof nextWorkflowApi.createNextWorkflowDefinitions === "function") {
Object.assign(definitions, nextWorkflowApi.createNextWorkflowDefinitions({
ppro, Protocol, workspace, events: deps.events
}));
}
if (typeof deps.transcriptImportHandler === "function") {
definitions["transcript.import"] = {
destructive: true,
undoable: true,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "26.3.0",
probe: typeof deps.transcriptImportProbe === "function" ? deps.transcriptImportProbe : null,
handler: deps.transcriptImportHandler
};
}
async function dispatch(command, args) {
const definition = definitions[command];
if (!definition) throw commandError("UXP_UNSUPPORTED_COMMAND", "Unsupported UXP command: " + command);
if (definition.probe && !await definition.probe()) throw commandError("UXP_COMMAND_UNAVAILABLE", command + " is not supported by this Premiere build");
const input = args || {};
const operationId = validateOperationId(input.operationId);
const operationKey = operationId ? command + ":" + operationId : null;
if (!definition.readOnly && operationKey && completedOperations.has(operationKey)) {
return { ...completedOperations.get(operationKey), replayed: true };
}
if (!definition.readOnly && operationKey && inFlightOperations.has(operationKey)) {
return { ...await inFlightOperations.get(operationKey), replayed: true };
}
const execute = async () => {
const result = await definition.handler(input);
return definition.readOnly ? result : {
operationId: operationId || null,
outcome: result && result.outcome ? result.outcome : "verified",
...result
};
};
if (definition.readOnly || !operationKey) return execute();
// Reserve the idempotency key before the handler starts so concurrent
// WebSocket dispatches share one host mutation instead of racing it.
const pending = Promise.resolve().then(execute);
inFlightOperations.set(operationKey, pending);
try {
const envelope = await pending;
completedOperations.set(operationKey, envelope);
if (completedOperations.size > 256) completedOperations.delete(completedOperations.keys().next().value);
return envelope;
} finally {
if (inFlightOperations.get(operationKey) === pending) inFlightOperations.delete(operationKey);
}
}
async function capabilities() {
let project = null, sequence = null;
try { project = await ppro.Project.getActiveProject(); sequence = project && await project.getActiveSequence(); } catch (_) {}
const workspaceState = workspace && typeof workspace.status === "function" ? workspace.status() : {
configured: false, accessMode: "unavailable", rootName: null, persistent: false,
pathDisclosure: "redacted", canonicalPathValidation: "unavailable"
};
const commands = {};
for (const name of Object.keys(definitions)) {
const definition = definitions[name], apiSupported = !definition.probe || await definition.probe();
const pathValidationSupported = !definition.requiresWorkspace || workspaceState.canonicalPathValidation === "available";
const supported = apiSupported && pathValidationSupported;
commands[name] = {
supported, backend: "uxp", documented: true,
readOnly: !!definition.readOnly, destructive: !!definition.destructive,
undoable: !!definition.undoable, idempotent: !!definition.idempotent
};
if (definition.minHostVersion) commands[name].minHostVersion = definition.minHostVersion;
if (definition.requiresWorkspace) commands[name].workspaceRequired = true;
if (definition.conditionalWorkspace) commands[name].workspaceRequired = "path_variant_only";
if (definition.targetCapabilityProbe) commands[name].targetCapabilityProbe = "invocation";
if (!apiSupported) commands[name].reason = "Required Premiere UXP API is unavailable in this host";
else if (!pathValidationSupported) commands[name].reason = "This UXP host cannot canonically validate native paths; use the CEP fallback for path-based workflows";
}
return {
backend: "uxp", protocolVersion: Protocol.PROTOCOL_VERSION, hostMinVersion: "25.6.0",
activeProject: !!project, activeSequence: !!sequence, workspace: workspaceState, commands,
fallback: { backend: "cep", reason: "Use CEP/QE only when a command is absent or reports unsupported; never silently retry a failed UXP mutation." }
};
}
async function stateSnapshot() {
const project = await ppro.Project.getActiveProject();
const sequence = project && await project.getActiveSequence();
const position = sequence && await sequence.getPlayerPosition();
return { projectOpen: !!project, sequenceOpen: !!sequence, playheadSeconds: position ? position.seconds : null };
}
async function projectSnapshot() {
const project = await ppro.Project.getActiveProject();
if (!project) return { revision: "no-project", project: null, sequences: [] };
const sequences = Array.from(await project.getSequences() || []).map((sequence) => ({
guid: String(sequence.guid || ""), name: String(sequence.name || "")
}));
const active = await project.getActiveSequence();
const revision = simpleRevision([project.guid, project.name, project.path, active && active.guid, sequences.map((item) => item.guid).join(",")].join("|"));
return {
revision,
project: { guid: String(project.guid || ""), name: String(project.name || ""), path: String(project.path || "") },
activeSequenceGuid: active ? String(active.guid || "") : null,
sequences
};
}
async function inspectProjectInsertionBin(args) {
assertOnlyKeys(args, []);
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
const projectGuid = requiredProjectGuid(project);
const before = await insertionBinSnapshot(project);
// The insertion target is panel state that can change while its item
// properties are read. Resolve both the active project and target again
// before returning so callers never receive a mixed-project snapshot.
const currentProject = await ppro.Project.getActiveProject();
if (!currentProject || requiredProjectGuid(currentProject) !== projectGuid) {
throw commandError("UXP_STALE_PROJECT", "The active project changed while reading the insertion bin; retry the inspection");
}
const insertionBin = await insertionBinSnapshot(currentProject);
if (before.projectItemId !== insertionBin.projectItemId) {
throw commandError("UXP_STALE_INSERTION_BIN", "The Project-panel insertion bin changed while reading it; retry the inspection");
}
return { projectGuid, insertionBin, verificationBoundary: "project_insertion_bin_identity_readback" };
}
async function inspectInstalledMogrtDirectory(args) {
assertOnlyKeys(args, ["includePath"]);
const includePath = optionalBoolean(args.includePath, false, "includePath");
const path = await ppro.SequenceEditor.getInstalledMogrtPath();
// This bridge deliberately does not turn the returned host path into a
// filesystem capability. It only validates the bounded native value, and
// returns it when the caller explicitly requested disclosure.
if (typeof path !== "string" || !path.trim() || path.length > 4096 || path.includes("\0")) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid installed MOGRT directory path");
}
return {
available: true,
installedMogrtPath: includePath ? path : null,
pathDisclosure: includePath ? "requested" : "redacted",
verificationBoundary: "documented_sequence_editor_installation_directory_readback"
};
}
async function insertionBinSnapshot(project) {
if (typeof project.getInsertionBin !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose the Project-panel insertion bin");
}
const item = await project.getInsertionBin();
if (!item || typeof item.getId !== "function") {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return an identifiable insertion bin");
}
const projectItemId = await item.getId();
if (typeof projectItemId !== "string" || !projectItemId.trim() || projectItemId.length > 512) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid insertion-bin project-item ID");
}
const name = String(item.name == null ? "" : item.name);
if (name.length > 1024) throw commandError("UXP_VERIFICATION_FAILED", "Premiere returned an oversized insertion-bin name");
const type = Number(item.type);
if (!Number.isInteger(type) || type < 0 || type > 1024) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid insertion-bin item type");
}
return { projectItemId, name, type };
}
function requiredProjectGuid(project) {
const guid = stringifyGuid(project && project.guid);
if (!guid || guid.length > 512) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid active-project GUID");
return guid;
}
async function saveProject() {
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
const saved = await project.save();
if (!saved) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the project save");
return { saved: true, outcome: "verified", projectGuid: String(project.guid || "") };
}
async function createPresetSequence(args) {
assertOnlyKeys(args, ["name", "presetPath", "operationId"]);
const name = requiredString(args.name, "name"), presetPath = await allowedPath(args.presetPath, "presetPath", "file");
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
const sequence = await project.createSequenceWithPresetPath(name, presetPath);
if (!sequence) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return the created sequence");
const verified = Array.from(await project.getSequences() || []).some((item) => String(item.guid || "") === String(sequence.guid || ""));
if (!verified) throw commandError("UXP_VERIFICATION_FAILED", "Created sequence was not present in the project");
return { created: true, outcome: "verified", sequence: { guid: String(sequence.guid || ""), name: String(sequence.name || name) } };
}
async function inspectSequenceRange(args) {
assertOnlyKeys(args, []);
const context = await activeContext(false);
return {
...await sequenceRangeSnapshot(context.sequence),
verificationBoundary: "sequence_range_readback"
};
}
async function updateSequenceRange(args) {
const input = validateSequenceRangeUpdateArgs(args);
// TickTime construction can cross the host boundary. Do it before the
// per-sequence exclusion scope so the guarded snapshot is the final
// asynchronous preflight step before action creation.
const ticks = {
inPoint: input.updates.inSeconds == null ? null : await tickTime(input.updates.inSeconds, "updates.inSeconds"),
outPoint: input.updates.outSeconds == null ? null : await tickTime(input.updates.outSeconds, "updates.outSeconds"),
zeroPoint: input.updates.zeroPointSeconds == null ? null : await tickTime(input.updates.zeroPointSeconds, "updates.zeroPointSeconds")
};
return withSequenceRangeUpdateLock(input.expectedSequenceGuid, async () => {
const context = await activeContext(true);
const before = await sequenceRangeSnapshot(context.sequence);
if (before.sequenceGuid !== input.expectedSequenceGuid) {
throw commandError("UXP_STALE_SEQUENCE", "The active sequence changed before the range update; inspect the current range and retry");
}
assertExpectedSequenceRange(before.range, input.expectedRange);
const desired = { ...before.range, ...input.updates };
assertValidSequenceRange(desired, "requested sequence range");
let committed = false;
context.project.lockedAccess(() => {
committed = context.project.executeTransaction((compoundAction) => {
if (ticks.inPoint) {
const action = context.sequence.createSetInPointAction(ticks.inPoint);
if (!action || compoundAction.addAction(action) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the sequence in point action");
}
}
if (ticks.outPoint) {
const action = context.sequence.createSetOutPointAction(ticks.outPoint);
if (!action || compoundAction.addAction(action) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the sequence out point action");
}
}
if (ticks.zeroPoint) {
const action = context.sequence.createSetZeroPointAction(ticks.zeroPoint);
if (!action || compoundAction.addAction(action) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the sequence zero point action");
}
}
}, "Update sequence range");
});
assertTransactionCommitted(committed, "sequence range update");
const after = await sequenceRangeSnapshot(context.sequence);
if (after.sequenceGuid !== before.sequenceGuid || !sameSequenceRange(after.range, desired)) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not retain the requested sequence range");
}
return {
updated: true,
outcome: "verified",
sequenceGuid: after.sequenceGuid,
range: after.range,
verified: "sequence_range_readback",
operation: operationSemantics({
mutatesProject: true,
verificationStatus: "verified",
verificationBoundary: "sequence_range_readback",
verificationEvidence: [{ type: "sequence_range", sequenceGuid: after.sequenceGuid, range: after.range }],
undoSupported: true,
undoLabel: "Update sequence range",
transactionActionGroup: true,
cancellationSupported: false
})
};
});
}
function withSequenceRangeUpdateLock(sequenceGuid, operation) {
const previous = sequenceRangeUpdateTails.get(sequenceGuid) || Promise.resolve();
let release;
const gate = new Promise((resolve) => { release = resolve; });
const tail = previous.catch(() => undefined).then(() => gate);
sequenceRangeUpdateTails.set(sequenceGuid, tail);
return previous.catch(() => undefined).then(operation).finally(() => {
release();
if (sequenceRangeUpdateTails.get(sequenceGuid) === tail) sequenceRangeUpdateTails.delete(sequenceGuid);
});
}
async function inspectSequencePlayhead(args) {
assertOnlyKeys(args, []);
const context = await activeContext(false);
return {
...await sequencePlayheadSnapshot(context.sequence),
verificationBoundary: "sequence_playhead_readback"
};
}
function inspectFrameAlignment(args) {
assertObject(args);
assertOnlyKeys(args, ["action", "frameRate", "seconds", "frameCount"]);
const action = args.action;
if (action !== "align" && action !== "frame") {
throw commandError("UXP_INVALID_ARGUMENT", "action must be align or frame");
}
const requestedFrameRate = boundedFiniteNumber(args.frameRate, "frameRate", 1, 240);
if (!ppro.FrameRate || typeof ppro.FrameRate.createWithValue !== "function" ||
!ppro.TickTime || typeof ppro.TickTime.createWithSeconds !== "function" ||
typeof ppro.TickTime.createWithFrameAndFrameRate !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot construct native frame-aligned TickTime values");
}
let frameRate;
try { frameRate = ppro.FrameRate.createWithValue(requestedFrameRate); } catch (_) {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere rejected the requested frame rate");
}
const frameRateSnapshot = nativeFrameRateSnapshot(frameRate, requestedFrameRate);
if (action === "align") {
if (args.frameCount !== undefined || args.seconds === undefined) {
throw commandError("UXP_INVALID_ARGUMENT", "align requires seconds and does not accept frameCount");
}
const seconds = boundedFiniteNumber(args.seconds, "seconds", 0, MAX_TIME_ALIGNMENT_SECONDS);
let value, alignedDown, alignedNearest;
try {
value = ppro.TickTime.createWithSeconds(seconds);
if (!value || typeof value.alignToFrame !== "function" || typeof value.alignToNearestFrame !== "function") {
throw new Error("missing frame-alignment methods");
}
alignedDown = value.alignToFrame(frameRate);
alignedNearest = value.alignToNearestFrame(frameRate);
} catch (_) {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere could not align the requested TickTime to the supplied frame rate");
}
return {
action,
requested: { seconds, frameRate: requestedFrameRate },
frameRate: frameRateSnapshot,
input: nativeTickTimeSnapshot(value, "input TickTime"),
aligned: {
down: nativeTickTimeSnapshot(alignedDown, "frame-aligned TickTime"),
nearest: nativeTickTimeSnapshot(alignedNearest, "nearest-frame TickTime")
},
verificationBoundary: "native_ticktime_value_readback"
};
}
if (args.seconds !== undefined || args.frameCount === undefined) {
throw commandError("UXP_INVALID_ARGUMENT", "frame requires frameCount and does not accept seconds");
}
const frameCount = boundedInteger(args.frameCount, "frameCount", 0, MAX_TIME_ALIGNMENT_FRAME_COUNT);
let value;
try { value = ppro.TickTime.createWithFrameAndFrameRate(frameCount, frameRate); } catch (_) {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere could not construct the requested frame TickTime");
}
return {
action,
requested: { frameCount, frameRate: requestedFrameRate },
frameRate: frameRateSnapshot,
value: nativeTickTimeSnapshot(value, "frame TickTime"),
verificationBoundary: "native_ticktime_value_readback"
};
}
async function inspectSequenceTiming(args) {
assertOnlyKeys(args, []);
const context = await activeContext(false);
const snapshot = await sequenceTimingSnapshot(context.sequence);
// Every timing field is a native asynchronous read. Re-resolve the active
// sequence after that read set so the returned snapshot belongs to the
// same active sequence at both the start and end of this request. The
// documented API does not provide an atomic snapshot or an activation
// revision, so a transient switch back to the same sequence is not
// distinguishable from an unchanged active sequence.
const current = await activeContext(false);
if (sequenceGuidRequired(current.sequence) !== snapshot.sequenceGuid) {
throw commandError("UXP_STALE_SEQUENCE", "The active sequence no longer matches the timing snapshot; retry the inspection");
}
return { ...snapshot, verificationBoundary: "sequence_timing_readback" };
}
async function inspectSequenceTimingByGuid(args) {
assertOnlyKeys(args, ["sequenceGuid"]);
const requestedSequenceGuid = sequenceGuidArgument(args.sequenceGuid, "sequenceGuid");
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
if (typeof project.getSequence !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot resolve a sequence by GUID");
}
const projectGuid = requiredProjectGuid(project);
const firstSequence = await project.getSequence(requestedSequenceGuid.native);
if (!firstSequence) throw commandError("UXP_TARGET_NOT_FOUND", "sequenceGuid was not found in the active project");
const first = await sequenceTimingSnapshot(firstSequence);
if (first.sequenceGuid !== requestedSequenceGuid.text) {
throw commandError("UXP_STALE_SEQUENCE", "The requested sequence GUID did not match Premiere's resolved sequence");
}
// Re-resolve the active project and the requested sequence after every
// asynchronous timing read. Project.getSequence() is the documented
// targeted lookup; it avoids activating a different timeline just to
// inspect it. Premiere exposes no atomic sequence-timing snapshot, so
// require a complete second equal read rather than returning a mixed
// first snapshot when the target changes during inspection.
const currentProject = await ppro.Project.getActiveProject();
if (!currentProject || requiredProjectGuid(currentProject) !== projectGuid ||
typeof currentProject.getSequence !== "function") {
throw commandError("UXP_STALE_PROJECT", "The active project changed while reading the target sequence; retry the inspection");
}
const finalSequence = await currentProject.getSequence(requestedSequenceGuid.native);
if (!finalSequence) throw commandError("UXP_STALE_SEQUENCE", "The requested sequence changed or was removed while reading it; retry the inspection");
const final = await sequenceTimingSnapshot(finalSequence);
if (final.sequenceGuid !== requestedSequenceGuid.text || !sameSequenceTimingSnapshot(first, final)) {
throw commandError("UXP_STALE_SEQUENCE", "The requested sequence timing changed while reading it; retry the inspection");
}
return {
...final,
projectGuid,
requestedSequenceGuid: requestedSequenceGuid.text,
verificationBoundary: "targeted_sequence_timing_double_readback"
};
}
async function setSequencePlayhead(args) {
const input = validateSequencePlayheadSetArgs(args);
// TickTime construction can cross the host boundary. Construct it before
// the per-sequence exclusion scope so the guarded snapshot is the final
// asynchronous preflight step before the host setter is invoked.
const position = await tickTime(input.positionSeconds, "positionSeconds");
return withSequencePlayheadSetLock(input.expectedSequenceGuid, async () => {
const context = await activeContext(false);
const before = await sequencePlayheadSnapshot(context.sequence);
if (before.sequenceGuid !== input.expectedSequenceGuid) {
throw commandError("UXP_STALE_SEQUENCE", "The active sequence changed before the player position was set; inspect the current playhead and retry");
}
if (!sameSeconds(before.positionSeconds, input.expectedPositionSeconds)) {
throw commandError("UXP_STALE_PLAYHEAD", "The sequence player position changed before it was set; inspect the current playhead and retry");
}
const accepted = await context.sequence.setPlayerPosition(position);
if (accepted !== true) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the sequence player position");
// Re-resolve the active sequence for postcondition readback: a user can
// switch sequences while Premiere awaits the host setter.
const after = await sequencePlayheadSnapshot((await activeContext(false)).sequence);
if (after.sequenceGuid !== before.sequenceGuid || !sameSeconds(after.positionSeconds, input.positionSeconds)) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not retain the requested sequence player position");
}
return {
positioned: true,
outcome: "verified",
sequenceGuid: after.sequenceGuid,
positionSeconds: after.positionSeconds,
verified: "sequence_playhead_readback",
operation: operationSemantics({
mutatesProject: false,
verificationStatus: "verified",
verificationBoundary: "sequence_playhead_readback",
verificationEvidence: [{ type: "sequence_playhead", sequenceGuid: after.sequenceGuid, positionSeconds: after.positionSeconds }],
undoSupported: false,
cancellationSupported: false
})
};
});
}
function withSequencePlayheadSetLock(sequenceGuid, operation) {
const previous = sequencePlayheadSetTails.get(sequenceGuid) || Promise.resolve();
let release;
const gate = new Promise((resolve) => { release = resolve; });
const tail = previous.catch(() => undefined).then(() => gate);
sequencePlayheadSetTails.set(sequenceGuid, tail);
return previous.catch(() => undefined).then(operation).finally(() => {
release();
if (sequencePlayheadSetTails.get(sequenceGuid) === tail) sequencePlayheadSetTails.delete(sequenceGuid);
});
}
function inspectAppPreferences(args) {
assertOnlyKeys(args, []);
return {
preferences: Object.keys(APP_PREFERENCE_KEYS).map((preference) => appPreferenceSnapshot(preference)),
verificationBoundary: "app_preference_native_string_readback"
};
}
function setAppPreference(args) {
const input = validateAppPreferenceSetArgs(args);
return withAppPreferenceSetLock(input.preference, () => {
const before = appPreferenceSnapshot(input.preference);
if (before.value !== input.expectedValue) {
throw commandError("UXP_STALE_APP_PREFERENCE", "The app preference changed before it was set; inspect current preferences and retry");
}
const persistenceFlag = ppro.AppPreference[input.persistence === "persistent" ? "PROPERTY_PERSISTENT" : "PROPERTY_NON_PERSISTENT"];
const accepted = ppro.AppPreference.setValue(preferenceNativeKey(input.preference), input.value, persistenceFlag);
if (accepted !== true) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the app preference update");
const after = appPreferenceSnapshot(input.preference);
if (after.value !== input.value) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not retain the requested app preference value");
}
return {
updated: true,
outcome: "verified",
preference: input.preference,
beforeValue: before.value,
value: after.value,
persistence: input.persistence,
verified: "app_preference_native_string_readback",
operation: operationSemantics({
// Adobe exposes AppPreference as application state, not a project
// action or transaction. Do not claim project mutation or Undo.
mutatesProject: false,
verificationStatus: "verified",
verificationBoundary: "app_preference_native_string_readback",
verificationEvidence: [{ type: "app_preference", preference: input.preference, beforeValue: before.value, value: after.value, persistence: input.persistence }],
undoSupported: false,
cancellationSupported: false
})
};
});
}
function appPreferenceSnapshot(preference) {
const value = ppro.AppPreference.getValue(preferenceNativeKey(preference));
if (typeof value !== "string" || value.length > MAX_APP_PREFERENCE_VALUE_CHARS) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a bounded native string app preference value");
}
return { preference, value };
}
function preferenceNativeKey(preference) {
const property = APP_PREFERENCE_KEYS[preference], value = property && ppro.AppPreference && ppro.AppPreference[property];
if (typeof value !== "string" || !value) throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose the requested app preference key");
return value;
}
function withAppPreferenceSetLock(preference, operation) {
const previous = appPreferenceSetTails.get(preference) || Promise.resolve();
let release;
const gate = new Promise((resolve) => { release = resolve; });
const tail = previous.catch(() => undefined).then(() => gate);
appPreferenceSetTails.set(preference, tail);
return previous.catch(() => undefined).then(operation).finally(() => {
release();
if (appPreferenceSetTails.get(preference) === tail) appPreferenceSetTails.delete(preference);
});
}
async function exportInterchange(args) {
assertOnlyKeys(args, ["format", "outputFilePath", "suppressUI", "operationId"]);
const format = requiredString(args.format, "format"), outputFilePath = await allowedPath(args.outputFilePath, "outputFilePath", "file");
if (format !== "otio" && format !== "fcpxml") throw commandError("UXP_INVALID_ARGUMENT", "format must be otio or fcpxml");
const context = await activeContext(false);
const method = format === "otio" ? "exportAsOpenTimelineIO" : "exportAsFinalCutProXML";
const exported = await ppro.ProjectConverter[method](context.sequence, outputFilePath, args.suppressUI !== false);
if (!exported) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the interchange export");
return { exported: true, outcome: "verified", format, outputFilePath };
}
async function exportAaf(args) {
const input = validateAafArgs(args);
input.outputFilePath = await allowedPath(input.outputFilePath, "outputFilePath", "file");
if (input.options.videoMixdownPresetPath != null) {
input.options.videoMixdownPresetPath = await allowedPath(input.options.videoMixdownPresetPath, "videoMixdownPresetPath", "file");
}
const context = await activeContext(false);
const options = buildAafExportOptions(ppro, input.options);
const exported = await ppro.ProjectConverter.exportAAF(context.sequence, input.outputFilePath, options);
if (!exported) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the AAF export");
// ProjectConverter confirms the export request, but UXP cannot reliably stat arbitrary
// native paths after Premiere returns. Keep that evidence boundary explicit.
return {
exported: true, outcome: "committed_unverified", format: "aaf", outputFilePath: input.outputFilePath,
options: input.options, outputVerified: false, verificationBoundary: "projectConverter_exportAAF_return",
operation: operationSemantics({
mutatesProject: false, verificationStatus: "not_verified", verificationBoundary: "projectConverter_exportAAF_return",
verificationEvidence: [{ type: "host_return", value: true }], cancellationSupported: true
})
};
}
async function renameTrack(args) {
const input = validateRenameTrackArgs(args), context = await activeContext(true);
const track = await trackAt(context.sequence, input.trackType, input.trackIndex);
if (typeof track.createSetNameAction !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This " + input.trackType + " track cannot be renamed by this Premiere build");
let committed = false;
context.project.lockedAccess(() => {
const action = track.createSetNameAction(input.name);
committed = context.project.executeTransaction((compoundAction) => {
if (!action || compoundAction.addAction(action) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the track rename action");
}, "Rename " + trackLabel(input.trackType));
});
assertTransactionCommitted(committed, "track rename");
const verifiedTrack = await trackAt(context.sequence, input.trackType, input.trackIndex);
if (String(verifiedTrack.name || "") !== input.name) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not retain the requested track name");
return {
renamed: true, outcome: "verified", trackType: input.trackType, trackIndex: input.trackIndex,
name: String(verifiedTrack.name || ""), verified: "track_name_readback",
operation: operationSemantics({
mutatesProject: true, verificationStatus: "verified", verificationBoundary: "track_name_readback",
verificationEvidence: [{ type: "track", trackType: input.trackType, trackIndex: input.trackIndex, name: input.name }],
undoSupported: true, undoLabel: "Rename " + trackLabel(input.trackType), transactionActionGroup: true, cancellationSupported: true
})
};
}
async function createSubclip(args) {
const input = validateSubclipArgs(args);
const context = await activeContext(true);
const clip = await resolveClipProjectItem(context.project, input);
if (typeof clip.createSubClipAction !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot create subclips through UXP");
const before = await projectItemInventory(context.project);
const startTime = await tickTime(input.startSeconds, "startSeconds"), endTime = await tickTime(input.endSeconds, "endSeconds");
let committed = false;
context.project.lockedAccess(() => {
const action = clip.createSubClipAction(
input.name, startTime, endTime, input.hasHardBoundaries,
{ takeVideo: input.takeVideo, takeAudio: input.takeAudio }
);
committed = context.project.executeTransaction((compoundAction) => {
if (!action || compoundAction.addAction(action) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the subclip creation action");
}, "Create subclip");
});
assertTransactionCommitted(committed, "subclip creation");
const created = await findCreatedSubclip(context.project, before, input.name);
if (!created) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not expose the created subclip in the project inventory");
return {
created: true, outcome: "verified", subclip: created,
sourceProjectItemId: await projectItemIdentifier(clip), sourceProjectItemName: String(clip.name || ""),
startSeconds: input.startSeconds, endSeconds: input.endSeconds,
hasHardBoundaries: input.hasHardBoundaries, takeVideo: input.takeVideo, takeAudio: input.takeAudio,
verified: "new_project_item_readback",
operation: operationSemantics({
mutatesProject: true, verificationStatus: "verified", verificationBoundary: "new_project_item_readback",
verificationEvidence: [{ type: "project_item", id: created.projectItemId, name: created.name }],
undoSupported: true, undoLabel: "Create subclip", transactionActionGroup: true, cancellationSupported: true
})
};
}
async function listMarkers(args) {
const input = validateMarkerListArgs(args);
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
const owner = input.scope === "sequence"
? await activeSequence(project)
: await resolveClipProjectItem(project, input);
if (!ppro.Markers || typeof ppro.Markers.getMarkers !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose documented marker APIs");
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");
const source = Array.from(await collection.getMarkers(input.filters) || []);
const markers = [];
for (let i = 0; i < source.length; i += 1) markers.push(await markerSnapshot(source[i], input.includeWebLinks, input.includeColorValues));
return {
scope: input.scope, ownerGuid: input.scope === "sequence" ? stringifyGuid(owner.guid) : null,
ownerProjectItemId: input.scope === "projectItem" ? await projectItemIdentifier(owner) : null,
filters: input.filters, count: markers.length, markers
};
}
async function setSourceMonitorPosition(args) {
assertOnlyKeys(args, ["seconds", "operationId"]);
const seconds = nonNegativeNumber(args.seconds, "seconds");
const position = await tickTime(seconds, "seconds");
const sourceMonitor = ppro.SourceMonitor;
if (!sourceMonitor || typeof sourceMonitor.setPosition !== "function" || typeof sourceMonitor.getPosition !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose Source Monitor positioning");
}
const set = await sourceMonitor.setPosition(position);
if (!set) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the Source Monitor position update");
const readback = await sourceMonitor.getPosition();
const readbackSeconds = tickSeconds(readback);
if (readbackSeconds == null || Math.abs(readbackSeconds - seconds) > 0.000001) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not retain the requested Source Monitor position");
}
return {
positioned: true, outcome: "verified", seconds: readbackSeconds, verified: "source_monitor_position_readback",
operation: operationSemantics({
mutatesProject: false, verificationStatus: "verified", verificationBoundary: "source_monitor_position_readback",
verificationEvidence: [{ type: "source_monitor_position", seconds: readbackSeconds }], cancellationSupported: true
})
};
}
async function transcriptLanguages() {
const languages = Array.from(await ppro.Transcript.querySupportedLanguages() || []);
return { languages, count: languages.length };
}
async function hasObjectMask(args) {
assertOnlyKeys(args, ["scope"]);
const scope = args.scope == null ? "sequence" : args.scope;
if (scope !== "sequence" && scope !== "project") throw commandError("UXP_INVALID_ARGUMENT", "scope must be sequence or project");
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
const target = scope === "project" ? project : await project.getActiveSequence();
if (!target) throw commandError("UXP_NO_ACTIVE_SEQUENCE", "No active sequence");
return { scope, hasObjectMask: !!ppro.ObjectMaskUtils.hasObjectMask(target) };
}
async function configureEncoder(args) {
assertOnlyKeys(args, ["launch", "embeddedXmp", "sidecarXmp", "startBatch", "operationId"]);
const performed = [];
if (args.launch) { if (!await ppro.EncoderManager.launchEncoder()) throw commandError("UXP_VERIFICATION_FAILED", "AME did not launch"); performed.push("launch"); }
if (args.embeddedXmp != null) { await ppro.EncoderManager.setEmbeddedXMPEnabled(!!args.embeddedXmp); performed.push("embeddedXmp"); }
if (args.sidecarXmp != null) { await ppro.EncoderManager.setSidecarXMPEnabled(!!args.sidecarXmp); performed.push("sidecarXmp"); }
if (args.startBatch) { if (!await ppro.EncoderManager.startBatchEncode()) throw commandError("UXP_VERIFICATION_FAILED", "AME batch queue did not start"); performed.push("startBatch"); }
if (!performed.length) throw commandError("UXP_INVALID_ARGUMENT", "At least one encoder operation is required");
return { configured: true, outcome: "committed_unverified", performed };
}
async function tickTime(seconds, name) {
const value = Number(seconds);
if (!Number.isFinite(value) || value < 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-negative number");
if (ppro.TickTime && typeof ppro.TickTime.createWithSeconds === "function") return ppro.TickTime.createWithSeconds(value);
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot create TickTime");
}
async function sequenceRangeSnapshot(sequence) {
const sequenceGuid = String(sequence && sequence.guid || "");
if (!sequenceGuid) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not provide a stable active-sequence GUID");
const [inPoint, outPoint, zeroPoint, endPoint] = await Promise.all([
sequence.getInPoint(), sequence.getOutPoint(), sequence.getZeroPoint(), sequence.getEndTime()
]);
const range = {
inSeconds: tickSecondsRequired(inPoint, "sequence in point"),
outSeconds: tickSecondsRequired(outPoint, "sequence out point"),
zeroPointSeconds: tickSecondsRequired(zeroPoint, "sequence zero point"),
endSeconds: tickSecondsRequired(endPoint, "sequence end point")
};
assertValidSequenceRange(range, "Premiere sequence range");
return { sequenceGuid, range };
}
async function sequencePlayheadSnapshot(sequence) {
const sequenceGuid = sequenceGuidRequired(sequence);
return {
sequenceGuid,
positionSeconds: tickSecondsRequired(await sequence.getPlayerPosition(), "sequence player position")
};
}
async function sequenceTimingSnapshot(sequence) {
const sequenceGuid = sequenceGuidRequired(sequence);
const [frameSize, timebase, audioDisplay, videoDisplay, projectItem] = await Promise.all([
sequence.getFrameSize(),
sequence.getTimebase(),
sequence.getSequenceAudioTimeDisplayFormat(),
sequence.getSequenceVideoTimeDisplayFormat(),
sequence.getProjectItem()
]);
if (!projectItem || typeof projectItem.getId !== "function") {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return an identifiable sequence project item");
}
const projectItemId = await projectItem.getId();
if (typeof projectItemId !== "string" || !projectItemId.trim() || projectItemId.length > 512) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid sequence project-item ID");
}
return {
sequenceGuid,
sequenceName: snapshotString(sequence.name, "sequence name", 255),
frameSize: frameSizeSnapshot(frameSize),
timebase: timebaseSnapshot(timebase),
audioTimeDisplayFormat: timeDisplaySnapshot(audioDisplay, "sequence audio time display format"),
videoTimeDisplayFormat: timeDisplaySnapshot(videoDisplay, "sequence video time display format"),
projectItem: {
id: projectItemId,
name: snapshotString(projectItem.name, "sequence project-item name", 255)
}
};
}
function sequenceGuidRequired(sequence) {
const rawGuid = sequence && sequence.guid;
const isStringGuid = typeof rawGuid === "string";
const isGuidObject = rawGuid && typeof rawGuid === "object" &&
typeof rawGuid.toString === "function" && rawGuid.toString !== Object.prototype.toString;
if (!isStringGuid && !isGuidObject) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not provide a stable active-sequence GUID");
}
let sequenceGuid;
try { sequenceGuid = isStringGuid ? rawGuid : rawGuid.toString(); } catch (_) { sequenceGuid = ""; }
if (typeof sequenceGuid !== "string" || !sequenceGuid.trim() || sequenceGuid !== sequenceGuid.trim() || sequenceGuid.length > 512) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not provide a stable active-sequence GUID");
}
return sequenceGuid;
}
function sequenceGuidArgument(value, name) {
if (typeof value !== "string" || !value.trim() || value !== value.trim() || value.length > 512) {
throw commandError("UXP_INVALID_ARGUMENT", name + " is required and must be a bounded GUID string");
}
if (!ppro.Guid || typeof ppro.Guid.fromString !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere cannot parse sequence GUIDs");
}
let native;
try { native = ppro.Guid.fromString(value); } catch (_) {
throw commandError("UXP_INVALID_ARGUMENT", name + " is not a valid Premiere GUID");
}
if (!native) throw commandError("UXP_INVALID_ARGUMENT", name + " is not a valid Premiere GUID");
return { text: value, native };
}
function sameSequenceTimingSnapshot(left, right) {
return left && right &&
left.sequenceGuid === right.sequenceGuid &&
left.sequenceName === right.sequenceName &&
left.timebase === right.timebase &&
left.frameSize && right.frameSize &&
left.frameSize.width === right.frameSize.width && left.frameSize.height === right.frameSize.height &&
left.audioTimeDisplayFormat && right.audioTimeDisplayFormat &&
left.audioTimeDisplayFormat.type === right.audioTimeDisplayFormat.type &&
left.videoTimeDisplayFormat && right.videoTimeDisplayFormat &&
left.videoTimeDisplayFormat.type === right.videoTimeDisplayFormat.type &&
left.projectItem && right.projectItem &&
left.projectItem.id === right.projectItem.id && left.projectItem.name === right.projectItem.name;
}
function frameSizeSnapshot(value) {
if (!value || typeof value.width !== "number" || typeof value.height !== "number" ||
!Number.isSafeInteger(value.width) || !Number.isSafeInteger(value.height) ||
value.width < 1 || value.height < 1 ||
value.width > MAX_SEQUENCE_FRAME_WIDTH || value.height > MAX_SEQUENCE_FRAME_HEIGHT) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid sequence frame size");
}
return { width: value.width, height: value.height };
}
function timebaseSnapshot(value) {
if (typeof value !== "string" || !new RegExp("^[1-9]\\d{0," + (MAX_SEQUENCE_TIMEBASE_DIGITS - 1) + "}$").test(value)) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid sequence timebase");
}
return value;
}
function timeDisplaySnapshot(value, name) {
if (!value || typeof value.type !== "number" || !Number.isSafeInteger(value.type) || value.type < 0) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid " + name);
}
return { type: value.type };
}
function nativeFrameRateSnapshot(value, requested) {
if (!value || !Number.isFinite(Number(value.value)) || Number(value.value) < 1 || Number(value.value) > 240 ||
Math.abs(Number(value.value) - requested) > 0.000001 || !Number.isFinite(Number(value.ticksPerFrame)) || Number(value.ticksPerFrame) <= 0) {
throw commandError("UXP_INVALID_HOST_STATE", "Premiere did not return the requested valid native frame rate");
}
return { value: Number(value.value), ticksPerFrame: Number(value.ticksPerFrame) };
}
function nativeTickTimeSnapshot(value, name) {
if (!value || !Number.isFinite(Number(value.seconds)) || Number(value.seconds) < 0 || Number(value.seconds) > MAX_TIME_ALIGNMENT_SECONDS ||
typeof value.ticks !== "string" || !/^\d{1,18}$/.test(value.ticks)) {
throw commandError("UXP_INVALID_HOST_STATE", "Premiere did not return a valid " + name);
}
return { seconds: Number(value.seconds), ticks: value.ticks };
}
function boundedFiniteNumber(value, name, minimum, maximum) {
const number = Number(value);
if (!Number.isFinite(number) || number < minimum || number > maximum) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be a finite number from " + minimum + " through " + maximum);
}
return number;
}
function boundedInteger(value, name, minimum, maximum) {
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be an integer from " + minimum + " through " + maximum);
}
return value;
}
function snapshotString(value, name, maximum) {
if (typeof value !== "string" || !value.trim() || value.length > maximum) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid " + name);
}
return value;
}
function validateSequenceRangeUpdateArgs(args) {
assertObject(args);
assertOnlyKeys(args, ["expectedSequenceGuid", "expectedRange", "updates", "operationId"]);
if (typeof args.expectedSequenceGuid !== "string" || !args.expectedSequenceGuid || args.expectedSequenceGuid.length > 512) {
throw commandError("UXP_INVALID_ARGUMENT", "expectedSequenceGuid is required and must be at most 512 characters");
}
const expectedRange = validateExpectedSequenceRange(args.expectedRange, "expectedRange");
const updates = validateSequenceRangeUpdates(args.updates);
if (updates.inSeconds == null && updates.outSeconds == null && updates.zeroPointSeconds == null) {
throw commandError("UXP_INVALID_ARGUMENT", "updates must include at least one sequence range field");
}
return { expectedSequenceGuid: args.expectedSequenceGuid, expectedRange, updates };
}
function validateSequencePlayheadSetArgs(args) {
assertObject(args);
assertOnlyKeys(args, ["expectedSequenceGuid", "expectedPositionSeconds", "positionSeconds", "operationId"]);
if (typeof args.expectedSequenceGuid !== "string" || !args.expectedSequenceGuid || args.expectedSequenceGuid.length > 512) {
throw commandError("UXP_INVALID_ARGUMENT", "expectedSequenceGuid is required and must be at most 512 characters");
}
return {
expectedSequenceGuid: args.expectedSequenceGuid,
expectedPositionSeconds: boundedSeconds(args.expectedPositionSeconds, "expectedPositionSeconds"),
positionSeconds: boundedSeconds(args.positionSeconds, "positionSeconds")
};
}
function validateExpectedSequenceRange(value, name) {
assertObject(value);
assertOnlyKeys(value, ["inSeconds", "outSeconds", "zeroPointSeconds", "endSeconds"]);
const result = {};
for (const key of ["inSeconds", "outSeconds", "zeroPointSeconds", "endSeconds"]) {
if (value[key] == null) {
throw commandError("UXP_INVALID_ARGUMENT", name + "." + key + " is required");
}
result[key] = boundedSeconds(value[key], name + "." + key);
}
return result;
}
function validateSequenceRangeUpdates(value) {
assertObject(value);
assertOnlyKeys(value, ["inSeconds", "outSeconds", "zeroPointSeconds"]);
const result = {};
for (const key of ["inSeconds", "outSeconds", "zeroPointSeconds"]) {
if (value[key] != null) result[key] = boundedSeconds(value[key], "updates." + key);
}
return result;
}
function assertExpectedSequenceRange(actual, expected) {
if (!sameSeconds(actual.inSeconds, expected.inSeconds) || !sameSeconds(actual.outSeconds, expected.outSeconds) ||
!sameSeconds(actual.zeroPointSeconds, expected.zeroPointSeconds) || !sameSeconds(actual.endSeconds, expected.endSeconds)) {
throw commandError("UXP_STALE_RANGE", "The sequence range changed before the update; inspect the current range and retry");
}
}
function assertValidSequenceRange(range, name) {
const inSeconds = boundedSeconds(range.inSeconds, name + ".inSeconds");
const outSeconds = boundedSeconds(range.outSeconds, name + ".outSeconds");
const endSeconds = boundedSeconds(range.endSeconds, name + ".endSeconds");
boundedSeconds(range.zeroPointSeconds, name + ".zeroPointSeconds");
if (inSeconds > outSeconds || outSeconds > endSeconds) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must satisfy inSeconds <= outSeconds <= endSeconds");
}
}
function sameSequenceRange(actual, expected) {
return sameSeconds(actual.inSeconds, expected.inSeconds) && sameSeconds(actual.outSeconds, expected.outSeconds) &&
sameSeconds(actual.zeroPointSeconds, expected.zeroPointSeconds) && sameSeconds(actual.endSeconds, expected.endSeconds);
}
function sameSeconds(left, right) {
return typeof left === "number" && typeof right === "number" && Math.abs(left - right) <= 0.000001;
}
function tickSecondsRequired(value, name) {
if (!value || typeof value.seconds !== "number" || !Number.isFinite(value.seconds) || value.seconds < 0) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a valid " + name);
}
return value.seconds;
}
function boundedSeconds(value, name) {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 86400) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be a finite number from 0 through 86400");
}
return value;
}
async function activeContext(requireMutationApis) {
const project = await ppro.Project.getActiveProject();
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
const sequence = await project.getActiveSequence();
if (!sequence) throw commandError("UXP_NO_ACTIVE_SEQUENCE", "No active sequence");
if (requireMutationApis && (typeof project.lockedAccess !== "function" || typeof project.executeTransaction !== "function")) {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose locked undoable transactions");
}
return { project, sequence };
}
async function exportFrame(args) {
const context = await activeContext(false);
if (!args.outputDirectory) throw commandError("UXP_INVALID_ARGUMENT", "outputDirectory is required");
const outputDirectory = await allowedPath(args.outputDirectory, "outputDirectory", "directory");
const filename = Protocol.safeFilename(args.filename);
const exporterFilename = Protocol.exporterFrameName(filename);
const position = args.seconds == null ? await context.sequence.getPlayerPosition() : await tickTime(args.seconds, "seconds");
const size = await context.sequence.getFrameSize();
const width = positiveInt(args.width, size.width, "width"), height = positiveInt(args.height, size.height, "height");
const returned = await ppro.Exporter.exportSequenceFrame(context.sequence, position, exporterFilename, outputDirectory, width, height);
if (returned !== true) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm frame export; no output path is reported");
const path = Protocol.joinPath(outputDirectory, filename);
return { path, width, height, seconds: position.seconds, exporterResult: returned };
}
async function liftSelection(args) {
const input = validateLiftArgs(args), context = await activeContext(true);
const sequenceGuid = String(context.sequence.guid || "");
if (input.expectedSequenceGuid && input.expectedSequenceGuid !== sequenceGuid) {
throw commandError("UXP_STALE_SEQUENCE", "The active sequence changed before the lift request; inspect the project and retry with its current sequence GUID");
}
const selection = await context.sequence.getSelection();
if (!selection || typeof selection.getTrackItems !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose timeline item selection for UXP lift");
}
const selectedItems = Array.from(await selection.getTrackItems() || []);
if (!selectedItems.length) throw commandError("UXP_INVALID_ARGUMENT", "Select one or more timeline items before requesting a lift");
const editor = await ppro.SequenceEditor.getEditor(context.sequence);
const mediaType = ppro.Constants && ppro.Constants.MediaType && ppro.Constants.MediaType.ANY;
if (!editor || typeof editor.createRemoveItemsAction !== "function" || mediaType == null) {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose the documented SequenceEditor lift action");
}
let committed = false;
context.project.lockedAccess(() => {
const action = editor.createRemoveItemsAction(selection, false, mediaType, false);
committed = context.project.executeTransaction((compoundAction) => {
if (!action || compoundAction.addAction(action) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the selection lift action");
}, "Lift selected timeline items");
});
assertTransactionCommitted(committed, "selection lift");
return {
lifted: true, selectedItemCount: selectedItems.length, ripple: false, outcome: "committed_unverified",
verificationBoundary: "project_executeTransaction_return",
operation: operationSemantics({
mutatesProject: true, verificationStatus: "not_verified", verificationBoundary: "project_executeTransaction_return",
verificationEvidence: [{ type: "transaction", accepted: true, selectedItemCount: selectedItems.length }],
undoSupported: true, undoLabel: "Lift selected timeline items", transactionActionGroup: true, cancellationSupported: true
})
};
}
async function listTransitions() {
const matchNames = await currentVideoTransitionMatchNames();
return { matchNames, count: matchNames.length };
}
async function inspectTransition(args) {
const input = validateInspectTransitionArgs(args), context = await activeContext(false);
return {
target: await transitionTargetSnapshot(context.sequence, input.videoTrackIndex, input.clipIndex, input.position),
verificationBoundary: "video_transition_target_readback"
};
}
async function addTransition(args) {
const input = validateAddArgs(args);
const duration = input.durationSeconds == null ? null : await tickTime(input.durationSeconds, "durationSeconds");
return withTransitionUpdateLock(input.expectedTarget.sequenceGuid, async () => {
const context = await guardedTransitionContext(input), target = context.target;
if (context.before.transitionPresent) {
throw commandError("UXP_STALE_TRANSITION_TARGET", "A transition is already present at the requested edge; inspect and retry");
}
const available = await currentVideoTransitionMatchNames();
if (!available.includes(input.matchName) && !listedVideoTransitionMatchNames.has(input.matchName)) {
throw commandError("UXP_TRANSITION_NOT_FOUND", "Unknown video transition matchName: " + input.matchName);
}
const transition = await ppro.TransitionFactory.createVideoTransition(input.matchName);
if (!transition) throw commandError("UXP_TRANSITION_NOT_FOUND", "Premiere could not create video transition: " + input.matchName);
const options = ppro.AddTransitionOptions();
options.setApplyToStart(input.position === "start");
if (duration) options.setDuration(duration);
if (input.forceSingleSided != null) options.setForceSingleSided(input.forceSingleSided);
if (input.transitionAlignment != null) options.setTransitionAlignment(input.transitionAlignment);
let committed = false;
context.project.lockedAccess(() => {
const action = target.createAddVideoTransitionAction(transition, options);
committed = context.project.executeTransaction((compoundAction) => {
if (!action || compoundAction.addAction(action) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the transition action");
}, "Add video transition");
});
assertTransactionCommitted(committed, "transition addition");
const after = await transitionTargetSnapshot(context.sequence, input.videoTrackIndex, input.clipIndex, input.position);
if (!after.transitionPresent) throw commandError("UXP_VERIFICATION_FAILED", "Premiere committed the transition transaction but no transition is present at the requested edge");
return verifiedTransitionResult(true, input, after, "Add video transition");
});
}
async function currentVideoTransitionMatchNames() {
const matchNames = Array.from(await ppro.TransitionFactory.getVideoTransitionMatchNames() || []);
for (let i = 0; i < matchNames.length; i += 1) {
if (typeof matchNames[i] === "string" && matchNames[i]) listedVideoTransitionMatchNames.add(matchNames[i]);
}
return matchNames;
}
async function removeTransition(args) {
const input = validateRemoveArgs(args);
return withTransitionUpdateLock(input.expectedTarget.sequenceGuid, async () => {
const context = await guardedTransitionContext(input), target = context.target;
if (!context.before.transitionPresent) {
throw commandError("UXP_STALE_TRANSITION_TARGET", "No transition is present at the requested edge; inspect and retry");
}
let committed = false;
context.project.lockedAccess(() => {
const action = target.createRemoveVideoTransitionAction(context.positionValue);
committed = context.project.executeTransaction((compoundAction) => {
if (!action || compoundAction.addAction(action) === false) throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the transition action");
}, "Remove video transition");
});
assertTransactionCommitted(committed, "transition removal");
const after = await transitionTargetSnapshot(context.sequence, input.videoTrackIndex, input.clipIndex, input.position);
if (after.transitionPresent) throw commandError("UXP_VERIFICATION_FAILED", "Premiere committed the transition transaction but the requested edge is still occupied");
return verifiedTransitionResult(false, input, after, "Remove video transition");
});
}
async function guardedTransitionContext(input) {
const context = await activeContext(true), sequenceGuid = String(context.sequence.guid || "");
if (!sequenceGuid || sequenceGuid !== input.expectedTarget.sequenceGuid) {
throw commandError("UXP_STALE_SEQUENCE", "The active sequence changed before the transition update; inspect and retry");
}
const positionValue = transitionPositionValue(input.position);
const target = await videoClipAt(context.sequence, input.videoTrackIndex, input.clipIndex);
const before = await transitionTargetSnapshot(context.sequence, input.videoTrackIndex, input.clipIndex, input.position, target, positionValue);
assertExpectedTransitionTarget(before, input.expectedTarget);
return { ...context, target, before, positionValue };
}
async function transitionTargetSnapshot(sequence, videoTrackIndex, clipIndex, position, target, positionValue) {
const item = target || await videoClipAt(sequence, videoTrackIndex, clipIndex);
const edge = positionValue == null ? transitionPositionValue(position) : positionValue;
if (typeof item.getProjectItem !== "function" || typeof item.getStartTime !== "function" || typeof item.getEndTime !== "function" || typeof item.hasVideoTransition !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot read a complete video-transition target snapshot");
}
const projectItem = await item.getProjectItem();
if (!projectItem || typeof projectItem.getId !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot identify the selected video clip");
const projectItemId = await projectItem.getId();
const startSeconds = tickSecondsRequired(await item.getStartTime(), "video transition target start"), endSeconds = tickSecondsRequired(await item.getEndTime(), "video transition target end");
if (typeof projectItemId !== "string" || !projectItemId || endSeconds < startSeconds) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a complete video-transition target snapshot");
}
const transitionPresent = await item.hasVideoTransition(edge);
if (typeof transitionPresent !== "boolean") throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a boolean video-transition edge state");
const sequenceGuid = String(sequence && sequence.guid || "");
if (!sequenceGuid) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not provide a stable active-sequence GUID");
return { sequenceGuid, videoTrackIndex, clipIndex, projectItemId, startSeconds, endSeconds, position, transitionPresent };
}
function transitionPositionValue(position) {
const positions = ppro.Constants && ppro.Constants.TransitionPosition, value = positions && positions[String(position).toUpperCase()];
if (value == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere transition position constants are unavailable");
return value;
}
function assertExpectedTransitionTarget(actual, expected) {
if (actual.sequenceGuid !== expected.sequenceGuid || actual.videoTrackIndex !== expected.videoTrackIndex || actual.clipIndex !== expected.clipIndex ||
actual.projectItemId !== expected.projectItemId || !sameSeconds(actual.startSeconds, expected.startSeconds) || !sameSeconds(actual.endSeconds, expected.endSeconds) ||
actual.position !== expected.position || actual.transitionPresent !== expected.transitionPresent) {
throw commandError("UXP_STALE_TRANSITION_TARGET", "The video-transition target changed before the update; inspect and retry");
}
}
function verifiedTransitionResult(applied, input, target, undoLabel) {
return {
...(applied ? { applied: true, matchName: input.matchName } : { removed: true }),
outcome: "verified", verified: "video_transition_edge_readback", target,
operation: operationSemantics({
mutatesProject: true, verificationStatus: "verified", verificationBoundary: "video_transition_edge_readback",
verificationEvidence: [{ type: "video_transition_edge", target }], undoSupported: true,
undoLabel, transactionActionGroup: true, cancellationSupported: false
})
};
}
function withTransitionUpdateLock(sequenceGuid, operation) {
const previous = transitionUpdateTails.get(sequenceGuid) || Promise.resolve();
let release;
const gate = new Promise((resolve) => { release = resolve; });
const tail = previous.catch(() => undefined).then(() => gate);
transitionUpdateTails.set(sequenceGuid, tail);
return previous.catch(() => undefined).then(operation).finally(() => {
release();
if (transitionUpdateTails.get(sequenceGuid) === tail) transitionUpdateTails.delete(sequenceGuid);
});
}
async function activeSequence(project) {
const sequence = await project.getActiveSequence();
if (!sequence) throw commandError("UXP_NO_ACTIVE_SEQUENCE", "No active sequence");
return sequence;
}
async function trackAt(sequence, trackType, trackIndex) {
const title = trackLabel(trackType), countMethod = "get" + title + "Count", itemMethod = "get" + title;
if (typeof sequence[countMethod] !== "function" || typeof sequence[itemMethod] !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose " + title + " APIs");
}
const count = await sequence[countMethod]();
if (trackIndex >= count) throw commandError("UXP_TARGET_NOT_FOUND", trackType + " trackIndex " + trackIndex + " is out of range");
const track = await sequence[itemMethod](trackIndex);
if (!track) throw commandError("UXP_TARGET_NOT_FOUND", trackType + " trackIndex " + trackIndex + " was not found");
return track;
}
function trackLabel(trackType) {
return trackType === "audio" ? "AudioTrack" : trackType === "video" ? "VideoTrack" : "CaptionTrack";
}
async function resolveClipProjectItem(project, input) {
const wantedId = input.projectItemId || "", wantedName = input.projectItemName || "";
if (!wantedId && !wantedName) return selectedClipProjectItem(project);
if (typeof project.getRootItem !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot enumerate project items");
const root = await project.getRootItem();
const queue = root ? [root] : [], nameMatches = [];
while (queue.length) {
const folder = queue.shift();
if (!folder || typeof folder.getItems !== "function") continue;
const children = Array.from(await folder.getItems() || []);
for (let i = 0; i < children.length; i += 1) {
const item = children[i], itemId = await projectItemIdentifier(item);
if (wantedId && itemId === wantedId) return castClipProjectItem(item);
if (!wantedId && wantedName && String(item.name || "") === wantedName) {
try { nameMatches.push(castClipProjectItem(item)); } catch (_) {}
}
if (isFolderItem(item)) queue.push(item);
}
}
if (wantedId) throw commandError("UXP_TARGET_NOT_FOUND", "projectItemId was not found or is not a media clip");
if (nameMatches.length > 1) throw commandError("UXP_AMBIGUOUS_TARGET", "projectItemName matched multiple media clips; use projectItemId");
if (nameMatches.length === 1) return nameMatches[0];
throw commandError("UXP_TARGET_NOT_FOUND", "projectItemName was not found or is not a media clip");
}
async function selectedClipProjectItem(project) {
if (!ppro.ProjectUtils || typeof ppro.ProjectUtils.getSelection !== "function") {
throw commandError("UXP_INVALID_ARGUMENT", "Pass projectItemId or projectItemName because Project panel selection is unavailable");
}
const selection = await ppro.ProjectUtils.getSelection(project);
const items = selection && await selection.getItems();
if (!items || items.length !== 1) {
throw commandError("UXP_INVALID_ARGUMENT", "Select exactly one media project item, or pass projectItemId/projectItemName");
}
return castClipProjectItem(items[0]);
}
function castClipProjectItem(item) {
if (!ppro.ClipProjectItem || typeof ppro.ClipProjectItem.cast !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot cast project items to media clips");
}
try {
const clip = ppro.ClipProjectItem.cast(item);
if (clip) return clip;
} catch (_) {}
throw commandError("UXP_TARGET_NOT_FOUND", "Resolved project item is not a media clip");
}
function isFolderItem(item) {
if (!ppro.FolderItem || typeof ppro.FolderItem.cast !== "function") return false;
try { return !!ppro.FolderItem.cast(item); } catch (_) { return false; }
}
async function projectItemIdentifier(item) {
let projectItem = item;
if (ppro.ProjectItem && typeof ppro.ProjectItem.cast === "function") {
try { projectItem = ppro.ProjectItem.cast(item) || item; } catch (_) {}
}
if (!projectItem || typeof projectItem.getId !== "function") return "";
const id = await projectItem.getId();
return id == null ? "" : String(id);
}
async function projectItemInventory(project) {
if (typeof project.getRootItem !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot enumerate project items for subclip verification");
const root = await project.getRootItem(), queue = root ? [root] : [], items = [];
while (queue.length) {
const folder = queue.shift();
if (!folder || typeof folder.getItems !== "function") continue;
const children = Array.from(await folder.getItems() || []);
for (let i = 0; i < children.length; i += 1) {
const item = children[i];
items.push({ id: await projectItemIdentifier(item), name: String(item.name || ""), item });
if (isFolderItem(item)) queue.push(item);
}
}
return items;
}
async function findCreatedSubclip(project, before, name) {
const known = new Set(before.map((item) => item.id).filter(Boolean));
const after = await projectItemInventory(project);
for (let i = 0; i < after.length; i += 1) {
const item = after[i];
if (item.name !== name || !item.id || known.has(item.id)) continue;
try {
const clip = castClipProjectItem(item.item);
return { projectItemId: item.id, name: String(clip.name || item.name) };
} catch (_) {}
}
return null;
}
async function markerSnapshot(marker, includeWebLinks, includeColorValues) {
const start = await marker.getStart(), duration = await marker.getDuration();
const snapshot = {
guid: stringifyGuid(marker.guid), name: String(await marker.getName() || ""),
comments: String(await marker.getComments() || ""), type: String(await marker.getType() || ""),
colorIndex: await marker.getColorIndex(), startSeconds: tickSeconds(start), durationSeconds: tickSeconds(duration)
};
if (includeWebLinks) {
snapshot.url = await optionalMarkerString(marker, "getUrl");
snapshot.target = await optionalMarkerString(marker, "getTarget");
}
if (includeColorValues) snapshot.color = await optionalMarkerColor(marker);
return snapshot;
}
async function optionalMarkerString(marker, method) {
if (!marker || typeof marker[method] !== "function") return null;
const value = await marker[method]();
return value == null ? "" : String(value);
}
async function optionalMarkerColor(marker) {
if (!marker || typeof marker.getColor !== "function") return null;
const color = await marker.getColor();
if (color == null) return null;
const snapshot = { red: color.red, green: color.green, blue: color.blue, alpha: color.alpha };
if (!Object.values(snapshot).every((value) => typeof value === "number" && Number.isFinite(value))) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return finite marker color components");
}
return snapshot;
}
function stringifyGuid(value) {
if (value == null) return "";
try { return String(typeof value.toString === "function" ? value.toString() : value); } catch (_) { return ""; }
}
function tickSeconds(value) {
const seconds = value && Number(value.seconds);
return Number.isFinite(seconds) ? seconds : null;
}
async function videoClipAt(sequence, trackIndex, clipIndex) {
const trackCount = await sequence.getVideoTrackCount();
if (trackIndex >= trackCount) throw commandError("UXP_TARGET_NOT_FOUND", "videoTrackIndex " + trackIndex + " is out of range");
const track = await sequence.getVideoTrack(trackIndex), types = ppro.Constants && ppro.Constants.TrackItemType;
if (!track || !types || types.CLIP == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere video track APIs are unavailable");
const clips = await track.getTrackItems(types.CLIP, false);
if (!clips || !clips[clipIndex]) throw commandError("UXP_TARGET_NOT_FOUND", "clipIndex " + clipIndex + " is out of range on video track " + trackIndex);
return clips[clipIndex];
}
function assertTransactionCommitted(committed, operation) {
if (!committed) throw commandError("UXP_TRANSACTION_FAILED", "Premiere did not commit the " + operation + " transaction");
}
function operationSemantics(options) {
return Protocol && typeof Protocol.operationSemantics === "function" ? Protocol.operationSemantics(options) : undefined;
}
async function allowedPath(value, label, kind) {
const path = requiredString(value, label);
return workspace && typeof workspace.assertPathAllowed === "function"
? await workspace.assertPathAllowed(path, { label, kind })
: path;
}
function canExportFrame() { return !!(ppro.Exporter && typeof ppro.Exporter.exportSequenceFrame === "function"); }
function canLiftSelection() {
return !!(ppro.SequenceEditor && typeof ppro.SequenceEditor.getEditor === "function" &&
ppro.Constants && ppro.Constants.MediaType && ppro.Constants.MediaType.ANY != null) && activeSequenceHas(["getSelection"]);
}
function canInspectProject() { return !!(ppro.Project && typeof ppro.Project.getActiveProject === "function"); }
async function activeProjectHas(name) {
if (!canInspectProject()) return false;
const project = await ppro.Project.getActiveProject();
return !project || typeof project[name] === "function";
}
function canSaveProject() { return activeProjectHas("save"); }
function canInspectProjectInsertionBin() { return activeProjectHas("getInsertionBin"); }
function canInspectInstalledMogrtDirectory() {
return !!(ppro.SequenceEditor && typeof ppro.SequenceEditor.getInstalledMogrtPath === "function");
}
function canCreatePresetSequence() { return activeProjectHas("createSequenceWithPresetPath"); }
function canInspectSequenceRange() {
return activeSequenceHas(["getInPoint", "getOutPoint", "getZeroPoint", "getEndTime"]);
}
function canInspectSequencePlayhead() {
return activeSequenceHas(["getPlayerPosition"]);
}
function canInspectFrameAlignment() {
return !!(ppro.FrameRate && typeof ppro.FrameRate.createWithValue === "function" &&
ppro.TickTime && typeof ppro.TickTime.createWithSeconds === "function" &&
typeof ppro.TickTime.createWithFrameAndFrameRate === "function");
}
function canInspectSequenceTiming() {
return activeSequenceHas([
"getFrameSize", "getTimebase", "getSequenceAudioTimeDisplayFormat",
"getSequenceVideoTimeDisplayFormat", "getProjectItem"
]);
}
async function canInspectSequenceTimingByGuid() {
if (!canInspectProject() || !ppro.Guid || typeof ppro.Guid.fromString !== "function") return false;
const project = await ppro.Project.getActiveProject();
return !project || typeof project.getSequence === "function";
}
function canInspectAppPreferences() {
if (!ppro.AppPreference || typeof ppro.AppPreference.getValue !== "function") return false;
return Object.values(APP_PREFERENCE_KEYS).every((property) => typeof ppro.AppPreference[property] === "string" && ppro.AppPreference[property]);
}
function canSetAppPreferences() {
return canInspectAppPreferences() && typeof ppro.AppPreference.setValue === "function" &&
Number.isFinite(ppro.AppPreference.PROPERTY_PERSISTENT) && Number.isFinite(ppro.AppPreference.PROPERTY_NON_PERSISTENT);
}
async function canSetSequencePlayhead() {
return !!(ppro.TickTime && typeof ppro.TickTime.createWithSeconds === "function" &&
await canInspectSequencePlayhead() && await activeSequenceHas(["setPlayerPosition"]));
}
async function canUpdateSequenceRange() {
if (!ppro.TickTime || typeof ppro.TickTime.createWithSeconds !== "function" || !await canInspectSequenceRange()) return false;
const project = await ppro.Project.getActiveProject();
if (!project) return true;
if (typeof project.lockedAccess !== "function" || typeof project.executeTransaction !== "function") return false;
const sequence = await project.getActiveSequence();
return !sequence || ["createSetInPointAction", "createSetOutPointAction", "createSetZeroPointAction"]
.every((method) => typeof sequence[method] === "function");
}
function canExportInterchange() {
return !!(ppro.ProjectConverter && typeof ppro.ProjectConverter.exportAsOpenTimelineIO === "function" &&
typeof ppro.ProjectConverter.exportAsFinalCutProXML === "function");
}
function canExportAaf() {
return !!(ppro.ProjectConverter && typeof ppro.ProjectConverter.exportAAF === "function" &&
typeof ppro.AAFExportOptions === "function");
}
async function activeSequenceHas(methods) {
if (!canInspectProject()) return false;
const project = await ppro.Project.getActiveProject();
if (!project) return true;
const sequence = await project.getActiveSequence();
return !sequence || methods.every((method) => typeof sequence[method] === "function");
}
function canRenameTracks() {
return activeSequenceHas(["getAudioTrackCount", "getAudioTrack", "getVideoTrackCount", "getVideoTrack", "getCaptionTrackCount", "getCaptionTrack"]);
}
function canCreateSubclips() {
return canInspectProject() && !!(ppro.ClipProjectItem && typeof ppro.ClipProjectItem.cast === "function");
}
function canListMarkers() { return !!(ppro.Markers && typeof ppro.Markers.getMarkers === "function"); }
function canSetSourceMonitorPosition() {
return !!(ppro.SourceMonitor && typeof ppro.SourceMonitor.setPosition === "function" && typeof ppro.SourceMonitor.getPosition === "function" &&
ppro.TickTime && typeof ppro.TickTime.createWithSeconds === "function");
}
function canQueryTranscriptLanguages() { return !!(ppro.Transcript && typeof ppro.Transcript.querySupportedLanguages === "function"); }
function canInspectObjectMasks() { return !!(ppro.ObjectMaskUtils && typeof ppro.ObjectMaskUtils.hasObjectMask === "function"); }
function canConfigureEncoder() {
return !!(ppro.EncoderManager && typeof ppro.EncoderManager.launchEncoder === "function" &&
typeof ppro.EncoderManager.startBatchEncode === "function");
}
function canListTransitions() { return !!(ppro.TransitionFactory && typeof ppro.TransitionFactory.getVideoTransitionMatchNames === "function"); }
function canInspectTransitions() {
return activeSequenceHas(["getVideoTrackCount", "getVideoTrack"]);
}
async function canMutateTransitions() {
if (!canListTransitions() || typeof ppro.TransitionFactory.createVideoTransition !== "function" ||
typeof ppro.AddTransitionOptions !== "function" || !ppro.TickTime || typeof ppro.TickTime.createWithSeconds !== "function" ||
!(ppro.Constants && ppro.Constants.TrackItemType && ppro.Constants.TransitionPosition) || !await canInspectTransitions()) return false;
const project = await ppro.Project.getActiveProject();
return !project || (typeof project.lockedAccess === "function" && typeof project.executeTransaction === "function");
}
async function initialize() {
return nextWorkflowRuntime && typeof nextWorkflowRuntime.initialize === "function"
? nextWorkflowRuntime.initialize()
: { initialized: true };
}
async function dispose() {
return nextWorkflowRuntime && typeof nextWorkflowRuntime.dispose === "function"
? nextWorkflowRuntime.dispose()
: { disposed: true };
}
return { definitions, dispatch, capabilities, stateSnapshot, initialize, dispose };
}
function validateAddArgs(args) {
assertObject(args);
assertOnlyKeys(args, ["videoTrackIndex", "clipIndex", "matchName", "position", "durationSeconds", "forceSingleSided", "transitionAlignment", "expectedTarget", "operationId"]);
const result = targetArgs(args);
if (typeof args.matchName !== "string" || !args.matchName.trim() || args.matchName.length > 256) throw commandError("UXP_INVALID_ARGUMENT", "matchName must be a non-empty string of at most 256 characters");
result.matchName = args.matchName; result.position = optionalPosition(args.position);
result.expectedTarget = validateExpectedTransitionTarget(args.expectedTarget, result);
if (args.durationSeconds != null) result.durationSeconds = positiveNumber(args.durationSeconds, "durationSeconds");
if (args.forceSingleSided != null) {
if (typeof args.forceSingleSided !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "forceSingleSided must be a boolean");
result.forceSingleSided = args.forceSingleSided;
}
if (args.transitionAlignment != null) {
if (!Number.isInteger(args.transitionAlignment)) throw commandError("UXP_INVALID_ARGUMENT", "transitionAlignment must be an integer");
result.transitionAlignment = args.transitionAlignment;
}
return result;
}
function validateRemoveArgs(args) {
assertObject(args); assertOnlyKeys(args, ["videoTrackIndex", "clipIndex", "position", "expectedTarget", "operationId"]);
const result = targetArgs(args); result.position = optionalPosition(args.position); result.expectedTarget = validateExpectedTransitionTarget(args.expectedTarget, result); return result;
}
function validateInspectTransitionArgs(args) {
assertObject(args); assertOnlyKeys(args, ["videoTrackIndex", "clipIndex", "position"]);
const result = targetArgs(args); result.position = optionalPosition(args.position); return result;
}
function validateExpectedTransitionTarget(value, target) {
assertObject(value);
assertOnlyKeys(value, ["sequenceGuid", "videoTrackIndex", "clipIndex", "projectItemId", "startSeconds", "endSeconds", "position", "transitionPresent"]);
const sequenceGuid = boundedString(value.sequenceGuid, "expectedTarget.sequenceGuid", 512);
const projectItemId = boundedString(value.projectItemId, "expectedTarget.projectItemId", 512);
const videoTrackIndex = nonNegativeInt(value.videoTrackIndex, "expectedTarget.videoTrackIndex"), clipIndex = nonNegativeInt(value.clipIndex, "expectedTarget.clipIndex");
if (videoTrackIndex !== target.videoTrackIndex || clipIndex !== target.clipIndex) {
throw commandError("UXP_INVALID_ARGUMENT", "expectedTarget coordinates must match videoTrackIndex and clipIndex");
}
const position = optionalPosition(value.position);
if (position !== target.position) throw commandError("UXP_INVALID_ARGUMENT", "expectedTarget.position must match position");
const startSeconds = boundedTransitionSeconds(value.startSeconds, "expectedTarget.startSeconds"), endSeconds = boundedTransitionSeconds(value.endSeconds, "expectedTarget.endSeconds");
if (endSeconds < startSeconds) throw commandError("UXP_INVALID_ARGUMENT", "expectedTarget.endSeconds must be greater than or equal to startSeconds");
return { sequenceGuid, videoTrackIndex, clipIndex, projectItemId, startSeconds, endSeconds, position, transitionPresent: requiredBoolean(value.transitionPresent, "expectedTarget.transitionPresent") };
}
function validateLiftArgs(args) {
assertObject(args); assertOnlyKeys(args, ["expectedSequenceGuid", "operationId"]);
const result = {};
if (args.expectedSequenceGuid != null) result.expectedSequenceGuid = boundedString(args.expectedSequenceGuid, "expectedSequenceGuid", 512);
return result;
}
function validateRenameTrackArgs(args) {
assertObject(args); assertOnlyKeys(args, ["trackType", "trackIndex", "name", "operationId"]);
const trackType = args.trackType;
if (trackType !== "audio" && trackType !== "video" && trackType !== "caption") {
throw commandError("UXP_INVALID_ARGUMENT", "trackType must be audio, video, or caption");
}
return { trackType, trackIndex: nonNegativeInt(args.trackIndex, "trackIndex"), name: boundedString(args.name, "name", 255) };
}
function validateSubclipArgs(args) {
assertObject(args);
assertOnlyKeys(args, ["projectItemId", "projectItemName", "name", "startSeconds", "endSeconds", "hasHardBoundaries", "takeVideo", "takeAudio", "operationId"]);
const target = validateProjectItemTarget(args);
const startSeconds = nonNegativeNumber(args.startSeconds, "startSeconds"), endSeconds = nonNegativeNumber(args.endSeconds, "endSeconds");
if (endSeconds <= startSeconds) throw commandError("UXP_INVALID_ARGUMENT", "endSeconds must be greater than startSeconds");
const hasHardBoundaries = optionalBoolean(args.hasHardBoundaries, false, "hasHardBoundaries");
const takeVideo = optionalBoolean(args.takeVideo, true, "takeVideo"), takeAudio = optionalBoolean(args.takeAudio, true, "takeAudio");
if (!takeVideo && !takeAudio) throw commandError("UXP_INVALID_ARGUMENT", "At least one of takeVideo or takeAudio must be true");
return Object.assign(target, {
name: boundedString(args.name, "name", 255), startSeconds, endSeconds, hasHardBoundaries, takeVideo, takeAudio
});
}
function validateAppPreferenceSetArgs(args) {
assertObject(args);
assertOnlyKeys(args, ["preference", "expectedValue", "value", "persistence", "confirmPreferenceChange", "operationId"]);
const preference = args.preference;
if (preference !== "auto_peak_generation" && preference !== "import_workspace" && preference !== "show_quickstart_dialog") {
throw commandError("UXP_INVALID_ARGUMENT", "preference must be auto_peak_generation, import_workspace, or show_quickstart_dialog");
}
if (args.confirmPreferenceChange !== true) {
throw commandError("UXP_CONFIRMATION_REQUIRED", "App preference changes are direct application-state updates; pass confirmPreferenceChange=true after review");
}
const persistence = args.persistence;
if (persistence !== "persistent" && persistence !== "non_persistent") {
throw commandError("UXP_INVALID_ARGUMENT", "persistence must be persistent or non_persistent");
}
return {
preference,
expectedValue: boundedStringAllowEmpty(args.expectedValue, "expectedValue", 1024),
value: boundedStringAllowEmpty(args.value, "value", 1024),
persistence,
operationId: requiredOperationId(args.operationId)
};
}
function validateMarkerListArgs(args) {
assertObject(args); assertOnlyKeys(args, ["scope", "projectItemId", "projectItemName", "filters", "includeWebLinks", "includeColorValues"]);
const scope = args.scope == null ? "sequence" : args.scope;
if (scope !== "sequence" && scope !== "projectItem") throw commandError("UXP_INVALID_ARGUMENT", "scope must be sequence or projectItem");
if (scope === "sequence" && (args.projectItemId != null || args.projectItemName != null)) {
throw commandError("UXP_INVALID_ARGUMENT", "project item targeting is only valid for projectItem scope");
}
const target = scope === "projectItem" ? validateProjectItemTarget(args) : {};
let filters = [];
if (args.filters != null) {
if (!Array.isArray(args.filters) || args.filters.length > 16) throw commandError("UXP_INVALID_ARGUMENT", "filters must contain at most 16 marker types");
filters = args.filters.map((value, index) => boundedString(value, "filters[" + index + "]", 64));
}
return Object.assign({
scope, filters,
includeWebLinks: optionalBoolean(args.includeWebLinks, false, "includeWebLinks"),
includeColorValues: optionalBoolean(args.includeColorValues, false, "includeColorValues")
}, target);
}
function validateProjectItemTarget(args) {
const hasId = args.projectItemId != null, hasName = args.projectItemName != null;
if (hasId && hasName) throw commandError("UXP_INVALID_ARGUMENT", "Pass either projectItemId or projectItemName, not both");
const result = {};
if (hasId) result.projectItemId = boundedString(args.projectItemId, "projectItemId", 512);
if (hasName) result.projectItemName = boundedString(args.projectItemName, "projectItemName", 255);
return result;
}
function validateAafArgs(args) {
assertObject(args); assertOnlyKeys(args, ["outputFilePath", "options", "operationId"]);
const result = { outputFilePath: requiredString(args.outputFilePath, "outputFilePath"), options: {} };
if (result.outputFilePath.length > 4096) throw commandError("UXP_INVALID_ARGUMENT", "outputFilePath must be at most 4096 characters");
if (args.options == null) return result;
assertObject(args.options);
const value = args.options;
assertOnlyKeys(value, [
"mixdownVideo", "explodeToMono", "sampleRate", "bitsPerSample", "embedAudio", "audioFileFormat",
"trimSources", "handleFrames", "videoMixdownPresetPath", "renderAudioEffects", "interleaveWithoutEffects", "preserveParentFolder"
]);
const booleans = ["mixdownVideo", "explodeToMono", "embedAudio", "trimSources", "renderAudioEffects", "interleaveWithoutEffects", "preserveParentFolder"];
for (let i = 0; i < booleans.length; i += 1) {
const key = booleans[i];
if (value[key] != null) result.options[key] = requiredBoolean(value[key], key);
}
if (value.sampleRate != null) result.options.sampleRate = oneOfInt(value.sampleRate, "sampleRate", [32000, 44100, 48000, 88200, 96000]);
if (value.bitsPerSample != null) result.options.bitsPerSample = oneOfInt(value.bitsPerSample, "bitsPerSample", [16, 24, 32]);
if (value.audioFileFormat != null) {
if (value.audioFileFormat !== "aiff" && value.audioFileFormat !== "wav") throw commandError("UXP_INVALID_ARGUMENT", "audioFileFormat must be aiff or wav");
result.options.audioFileFormat = value.audioFileFormat;
}
if (value.handleFrames != null) {
const frames = nonNegativeInt(value.handleFrames, "handleFrames");
if (frames > 10000) throw commandError("UXP_INVALID_ARGUMENT", "handleFrames must be at most 10000");
result.options.handleFrames = frames;
}
if (value.videoMixdownPresetPath != null) result.options.videoMixdownPresetPath = boundedString(value.videoMixdownPresetPath, "videoMixdownPresetPath", 4096);
return result;
}
function buildAafExportOptions(ppro, options) {
if (!ppro.AAFExportOptions || typeof ppro.AAFExportOptions !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose AAFExportOptions");
}
const aaf = ppro.AAFExportOptions();
const setters = {
mixdownVideo: "setMixdownVideo", explodeToMono: "setExplodeToMono", sampleRate: "setSampleRate",
bitsPerSample: "setBitsPerSample", embedAudio: "setEmbedAudio", trimSources: "setTrimSources",
handleFrames: "setHandleFrames", videoMixdownPresetPath: "setVideoMixdownPresetPath",
renderAudioEffects: "setRenderAudioEffects", interleaveWithoutEffects: "setInterleaveWithoutEffects",
preserveParentFolder: "setPreserveParentFolder"
};
Object.keys(setters).forEach((key) => {
if (options[key] == null) return;
const setter = setters[key];
if (typeof aaf[setter] !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "AAF option " + key + " is unavailable in this Premiere build");
aaf[setter](options[key]);
});
if (options.audioFileFormat != null) {
const formats = ppro.Constants && ppro.Constants.AAFExportAudioFormat;
const format = formats && formats[options.audioFileFormat === "aiff" ? "AIFF" : "WAV"];
if (format == null || typeof aaf.setAudioFileFormat !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "AAF audio file format options are unavailable in this Premiere build");
aaf.setAudioFileFormat(format);
}
return aaf;
}
function targetArgs(args) { return { videoTrackIndex: nonNegativeInt(args.videoTrackIndex, "videoTrackIndex"), clipIndex: nonNegativeInt(args.clipIndex, "clipIndex") }; }
function optionalPosition(value) { const result = value == null ? "end" : value; if (result !== "start" && result !== "end") throw commandError("UXP_INVALID_ARGUMENT", "position must be start or end"); return result; }
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 nonNegativeInt(value, name) { if (!Number.isInteger(value) || value < 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-negative integer"); return value; }
function nonNegativeNumber(value, name) { const number = Number(value); if (!Number.isFinite(number) || number < 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-negative number"); return number; }
function boundedTransitionSeconds(value, name) { const number = nonNegativeNumber(value, name); if (number > 86400) throw commandError("UXP_INVALID_ARGUMENT", name + " must be at most 86400"); return number; }
function positiveNumber(value, name) { const number = Number(value); if (!Number.isFinite(number) || number <= 0) throw commandError("UXP_INVALID_ARGUMENT", name + " must be positive"); return number; }
function positiveInt(value, fallback, name) { return Math.round(positiveNumber(value == null ? fallback : value, name)); }
function requiredString(value, name) { if (typeof value !== "string" || !value.trim() || value.length > 4096) throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-empty string"); return value; }
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 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 oneOfInt(value, name, allowed) { if (!Number.isInteger(value) || !allowed.includes(value)) throw commandError("UXP_INVALID_ARGUMENT", name + " must be one of " + allowed.join(", ")); return value; }
function requiredOperationId(value) { if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value)) throw commandError("UXP_INVALID_ARGUMENT", "operationId is required and must be 1-128 safe characters"); return value; }
function validateOperationId(value) { if (value == null) return null; if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value)) throw commandError("UXP_INVALID_ARGUMENT", "operationId must be 1-128 safe characters"); return value; }
function simpleRevision(value) { var hash = 2166136261; for (var i = 0; i < value.length; i += 1) { hash ^= value.charCodeAt(i); hash = Math.imul(hash, 16777619); } return "uxp-" + (hash >>> 0).toString(16); }
function commandError(code, message) { const error = new Error(message); error.code = code; return error; }
return { createCommandRegistry, validateAddArgs, validateRemoveArgs, validateInspectTransitionArgs, commandError };
});