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

1906 lines
90 KiB
JavaScript
Executable File

(function (root, factory) {
const api = factory();
if (typeof module === "object" && module.exports) module.exports = api;
root.PremiereMcpNextWorkflows = api;
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
"use strict";
function createNextWorkflowDefinitions(deps) {
return createNextWorkflowRuntime(deps).definitions;
}
function createNextWorkflowRuntime(deps) {
const ppro = deps.ppro, events = deps.events;
const now = typeof deps.now === "function" ? deps.now : function () { return Date.now(); };
const sleep = typeof deps.sleep === "function" ? deps.sleep : function (milliseconds) {
return new Promise(function (resolve) { setTimeout(resolve, milliseconds); });
};
const scheduleTimer = typeof deps.setTimer === "function" ? deps.setTimer : function (callback, milliseconds) {
return setTimeout(callback, milliseconds);
};
const cancelTimer = typeof deps.clearTimer === "function" ? deps.clearTimer : function (timer) { clearTimeout(timer); };
const localStorage = deps.storage || (typeof globalThis !== "undefined" ? globalThis.localStorage : null);
const GROWING_LEASE_KEY = "premiereMcp.growingMediaLease";
let growingLease = null, growingTimer = null;
// Source-media mutations share a per-project-item tail. A frame-rate or
// pixel-aspect-ratio override must not slip between a guarded timing
// update's preflight and readback (or the other way around).
const sourceMediaUpdateTails = new Map();
const definitions = {
"events.list": {
readOnly: true,
minHostVersion: "25.6.0",
probe: canUseEvents,
handler: listEvents
},
"events.wait": {
readOnly: true,
minHostVersion: "25.6.0",
probe: canUseEvents,
handler: waitForEvents
},
"readiness.snapshot": {
readOnly: true,
minHostVersion: "25.6.0",
probe: canInspectReadiness,
handler: readinessSnapshot
},
"readiness.analysis.wait": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canWaitForAnalysis,
handler: waitForAnalysis
},
"readiness.operation.wait": {
readOnly: true,
minHostVersion: "25.6.0",
probe: canUseEvents,
handler: waitForOperation
},
"project.sessions.list": {
readOnly: true,
minHostVersion: "26.2.0",
probe: canListProjectSessions,
handler: listProjectSessions
},
"project.sessions.validate": {
readOnly: true,
requiresWorkspace: true,
minHostVersion: "26.2.0",
probe: canManageProjectSessions,
handler: validateProjectSession
},
"project.sessions.create": {
destructive: true,
undoable: false,
requiresWorkspace: true,
minHostVersion: "26.2.0",
probe: canManageProjectSessions,
handler: createProjectSession
},
"project.sessions.open": {
destructive: true,
undoable: false,
requiresWorkspace: true,
minHostVersion: "26.2.0",
probe: canManageProjectSessions,
handler: openProjectSession
},
"project.sessions.save": {
destructive: true,
undoable: false,
idempotent: true,
minHostVersion: "26.2.0",
probe: canManageProjectSessions,
handler: saveProjectSession
},
"project.sessions.saveAs": {
destructive: true,
undoable: false,
requiresWorkspace: true,
minHostVersion: "26.2.0",
probe: canManageProjectSessions,
handler: saveProjectSessionAs
},
"project.sessions.branchCopies": {
destructive: true,
undoable: false,
requiresWorkspace: true,
minHostVersion: "26.2.0",
probe: canManageProjectSessions,
handler: createProjectBranchCopies
},
"project.sessions.close": {
destructive: true,
undoable: false,
minHostVersion: "26.2.0",
probe: canManageProjectSessions,
handler: closeProjectSession
},
"growing.status": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canControlGrowingMedia,
handler: growingMediaStatus
},
"growing.pause": {
destructive: true,
undoable: false,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canControlGrowingMedia,
handler: pauseGrowingMedia
},
"growing.resume": {
destructive: true,
undoable: false,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canControlGrowingMedia,
handler: resumeGrowingMedia
},
"checkpoint.has": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canUseWorkflowCheckpoints,
handler: hasWorkflowCheckpoint
},
"checkpoint.get": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canUseWorkflowCheckpoints,
handler: getWorkflowCheckpoint
},
"checkpoint.set": {
destructive: true,
undoable: true,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canUseWorkflowCheckpoints,
handler: setWorkflowCheckpoint
},
"checkpoint.clear": {
destructive: true,
undoable: true,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canUseWorkflowCheckpoints,
handler: clearWorkflowCheckpoint
},
"media.health.inspect": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canMaintainMediaHealth,
handler: inspectMediaHealth
},
"media.health.refresh": {
destructive: true,
undoable: false,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canMaintainMediaHealth,
handler: refreshMediaHealth
},
"media.health.setOffline": {
destructive: true,
undoable: true,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canMaintainMediaHealth,
handler: setMediaOffline
},
"media.health.findByPath": {
readOnly: true,
requiresWorkspace: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canMaintainMediaHealth,
handler: findMediaByPath
},
"source.mediaTiming.inspect": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "26.3.0",
probe: canManageSourceMediaTiming,
handler: inspectSourceMediaTiming
},
"source.mediaTiming.setStart": {
destructive: true,
undoable: true,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "26.3.0",
probe: canManageSourceMediaTiming,
handler: setSourceMediaStart
},
"source.mediaOverrides.inspect": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "26.3.0",
probe: canManageSourceMediaOverrides,
handler: inspectSourceMediaOverrides
},
"source.mediaOverrides.update": {
destructive: true,
undoable: true,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "26.3.0",
probe: canManageSourceMediaOverrides,
handler: updateSourceMediaOverrides
},
"track.state.inspect": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canManageTrackState,
handler: inspectTrackState
},
"track.state.set": {
destructive: true,
undoable: false,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canManageTrackState,
handler: setTrackState
},
"source.clip.inspect": {
readOnly: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canManageSourceClip,
handler: inspectSourceClip
},
"source.clip.update": {
destructive: true,
undoable: true,
idempotent: true,
targetCapabilityProbe: true,
minHostVersion: "25.6.0",
probe: canManageSourceClip,
handler: updateSourceClip
}
};
return { definitions, initialize, dispose };
function canUseEvents() {
return !!(events && typeof events.list === "function" && typeof events.wait === "function");
}
function listEvents(args) {
assertOnlyKeys(args, ["afterRevision", "categories", "eventNames", "limit"]);
return events.list(query(args, false));
}
function waitForEvents(args) {
assertOnlyKeys(args, ["afterRevision", "categories", "eventNames", "limit", "timeoutMs"]);
return events.wait(query(args, true));
}
function canInspectReadiness() {
return !!(ppro && ppro.Project && typeof ppro.Project.getActiveProject === "function");
}
function canListProjectSessions() {
return !!(ppro && ppro.ProjectUtils &&
typeof ppro.ProjectUtils.getProjectViewIds === "function" &&
typeof ppro.ProjectUtils.getProjectFromViewId === "function");
}
function canManageProjectSessions() {
return !!(canListProjectSessions() && ppro.Project &&
typeof ppro.Project.getActiveProject === "function" &&
typeof ppro.Project.getProject === "function" &&
typeof ppro.Project.open === "function" &&
typeof ppro.Project.createProject === "function" &&
typeof ppro.Project.isProject === "function");
}
async function canControlGrowingMedia() {
if (!ppro || !ppro.Project || typeof ppro.Project.getActiveProject !== "function") return false;
try {
const project = await ppro.Project.getActiveProject();
return !!(project && typeof project.pauseGrowing === "function");
} catch (_) { return false; }
}
function canUseWorkflowCheckpoints() {
return !!(ppro && ppro.Properties && typeof ppro.Properties.getProperties === "function");
}
function canMaintainMediaHealth() {
return !!(ppro && ppro.ClipProjectItem && typeof ppro.ClipProjectItem.cast === "function");
}
function canManageSourceMediaTiming() {
return !!(canMaintainMediaHealth() && ppro.TickTime && typeof ppro.TickTime.createWithSeconds === "function");
}
function canManageSourceMediaOverrides() {
return !!canMaintainMediaHealth();
}
async function canManageTrackState() {
if (!canInspectReadiness()) return false;
try {
const project = await ppro.Project.getActiveProject();
const sequence = project && await project.getActiveSequence();
return !!(sequence && typeof sequence.getVideoTrackCount === "function" &&
typeof sequence.getAudioTrackCount === "function" && typeof sequence.getCaptionTrackCount === "function");
} catch (_) { return false; }
}
function canManageSourceClip() {
return !!(canMaintainMediaHealth() && ppro.TickTime && typeof ppro.TickTime.createWithSeconds === "function" &&
ppro.Constants && ppro.Constants.MediaType);
}
async function initialize() {
const stored = readGrowingLease();
if (!stored) return { recovered: false };
growingLease = stored;
try {
const receipt = await resumeLease("startup_recovery");
return { recovered: !!receipt.resumed, receipt };
} catch (error) {
return { recovered: false, recoveryPending: true, error: error && error.message || String(error) };
}
}
async function dispose() {
clearGrowingTimer();
if (!growingLease && !readGrowingLease()) return { resumed: false, alreadyResumed: true };
try { return await resumeLease("panel_or_bridge_disconnect"); }
catch (error) { return { resumed: false, recoveryPending: true, error: error && error.message || String(error) }; }
}
async function canWaitForAnalysis() {
if (!canInspectReadiness()) return false;
try {
const project = await ppro.Project.getActiveProject();
const sequence = project && await project.getActiveSequence();
return !!(sequence && typeof sequence.isDoneAnalyzingForVideoEffects === "function");
} catch (_) {
return false;
}
}
async function readinessSnapshot(args) {
assertOnlyKeys(args, ["sequenceId"]);
const project = await activeProject(false);
const sequence = project && await resolveSequence(project, args.sequenceId, false);
const analysisSupported = !!(sequence && typeof sequence.isDoneAnalyzingForVideoEffects === "function");
const analysisDone = analysisSupported ? !!(await sequence.isDoneAnalyzingForVideoEffects()) : null;
const journal = canUseEvents() ? events.status() : null;
return {
projectOpen: !!project,
sequenceId: sequence ? guidString(sequence.guid) : null,
analysisSupported,
analysisDone,
eventRevision: journal ? journal.latestRevision : null,
capturedAt: new Date(now()).toISOString()
};
}
async function waitForAnalysis(args) {
assertOnlyKeys(args, ["sequenceId", "expectedSequenceId", "timeoutMs", "pollMinMs", "pollMaxMs"]);
const project = await activeProject(true), sequence = await resolveSequence(project, args.sequenceId, true);
if (typeof sequence.isDoneAnalyzingForVideoEffects !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This sequence cannot report video-effect analysis readiness");
}
const sequenceId = guidString(sequence.guid);
if (args.expectedSequenceId != null && optionalToken(args.expectedSequenceId, "expectedSequenceId") !== sequenceId) {
throw commandError("UXP_STALE_TARGET", "The active or requested sequence changed before the readiness wait");
}
const timeoutMs = args.timeoutMs == null ? 30000 : integer(args.timeoutMs, "timeoutMs", 0, 60000);
const pollMinMs = args.pollMinMs == null ? 100 : integer(args.pollMinMs, "pollMinMs", 100, 2000);
const pollMaxMs = args.pollMaxMs == null ? 2000 : integer(args.pollMaxMs, "pollMaxMs", pollMinMs, 5000);
const startedAt = now();
let interval = pollMinMs, checks = 0;
while (true) {
checks += 1;
if (await sequence.isDoneAnalyzingForVideoEffects()) {
return {
ready: true, timedOut: false, sequenceId, checks,
elapsedMs: Math.max(0, now() - startedAt),
verificationBoundary: "sequence_analysis_readback"
};
}
const elapsed = Math.max(0, now() - startedAt);
if (elapsed >= timeoutMs) {
return {
ready: false, timedOut: true, sequenceId, checks, elapsedMs: elapsed,
verificationBoundary: "sequence_analysis_readback"
};
}
await sleep(Math.min(interval, timeoutMs - elapsed));
interval = Math.min(pollMaxMs, Math.ceil(interval * 1.5));
}
}
async function waitForOperation(args) {
assertOnlyKeys(args, ["operationType", "afterRevision", "timeoutMs"]);
if (args.afterRevision == null) {
throw commandError("UXP_INVALID_ARGUMENT", "afterRevision from a pre-dispatch readiness snapshot is required");
}
const operationType = optionalToken(args.operationType, "operationType");
const names = {
import: "operation.import.complete",
export: "operation.export.complete",
effectDrop: "operation.effect.drop.complete",
generativeExtend: "operation.generative.extend.complete"
};
if (!names[operationType]) throw commandError("UXP_INVALID_ARGUMENT", "operationType is not supported");
const result = await events.wait({
afterRevision: integer(args.afterRevision, "afterRevision", 0, Number.MAX_SAFE_INTEGER),
categories: ["operation"],
eventNames: [names[operationType]],
limit: 1,
timeoutMs: args.timeoutMs == null ? 30000 : integer(args.timeoutMs, "timeoutMs", 0, 60000)
});
const receipt = result.events[0] || null;
return {
ready: !!receipt,
timedOut: !receipt && !!result.timedOut,
operationType,
receipt,
outcome: receipt ? operationOutcome(receipt.detail && receipt.detail.state) : "pending",
overflow: result.overflow,
latestRevision: result.latestRevision,
verificationBoundary: receipt ? "operation_terminal_event_only" : "bounded_wait_timeout"
};
}
async function listProjectSessions(args) {
assertOnlyKeys(args, ["includePaths"]);
const includePaths = optionalBoolean(args.includePaths, false, "includePaths");
const projects = await openProjectInventory(includePaths);
const active = await ppro.Project.getActiveProject();
return {
count: projects.length,
activeProjectId: active ? guidString(active.guid) : null,
projects,
pathDisclosure: includePaths ? "requested" : "redacted"
};
}
async function validateProjectSession(args) {
assertOnlyKeys(args, ["path"]);
const path = await allowedWorkspacePath(args.path, "path");
return {
isProject: !!ppro.Project.isProject(path),
pathAccepted: true,
pathDisclosure: "caller_supplied_only"
};
}
async function createProjectSession(args) {
assertOnlyKeys(args, ["path", "confirmExternalWrite", "confirmOverwrite", "operationId"]);
requireConfirmation(args.confirmExternalWrite, "confirmExternalWrite", "Creating a project writes a new file");
const path = await allowedWorkspacePath(args.path, "path");
rejectExistingProject(path, args.confirmOverwrite);
const project = await ppro.Project.createProject(path);
assertProjectPath(project, path, "created project");
return projectMutationReceipt("created", project, path);
}
async function openProjectSession(args) {
assertOnlyKeys(args, ["path", "showDialogs", "addToMru", "operationId"]);
const path = await allowedWorkspacePath(args.path, "path");
if (!ppro.Project.isProject(path)) throw commandError("UXP_TARGET_NOT_FOUND", "The requested path is not an openable Premiere project");
const project = await ppro.Project.open(path, openOptions(args));
assertProjectPath(project, path, "opened project");
return projectMutationReceipt("opened", project, path);
}
async function saveProjectSession(args) {
assertOnlyKeys(args, ["projectId", "expectedPath", "operationId"]);
const project = await targetProject(args.projectId);
if (args.expectedPath != null) assertProjectPath(project, requiredPath(args.expectedPath, "expectedPath"), "target project");
if (typeof project.save !== "function" || !await project.save()) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the project save");
}
return projectMutationReceipt("saved", project, String(project.path || ""));
}
async function saveProjectSessionAs(args) {
assertOnlyKeys(args, ["projectId", "expectedPath", "path", "confirmExternalWrite", "confirmOverwrite", "operationId"]);
requireConfirmation(args.confirmExternalWrite, "confirmExternalWrite", "Save As writes a new project file and retargets the project handle");
const project = await targetProject(args.projectId);
if (args.expectedPath != null) assertProjectPath(project, requiredPath(args.expectedPath, "expectedPath"), "target project");
const path = await allowedWorkspacePath(args.path, "path");
rejectExistingProject(path, args.confirmOverwrite);
if (typeof project.saveAs !== "function" || !await project.saveAs(path)) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm Save As");
}
assertProjectPath(project, path, "Save As project");
return projectMutationReceipt("saved_as", project, path);
}
async function createProjectBranchCopies(args) {
assertOnlyKeys(args, ["projectId", "expectedPath", "paths", "confirmExternalWrite", "confirmOverwrite", "operationId"]);
requireConfirmation(args.confirmExternalWrite, "confirmExternalWrite", "Branch copies write, close, and reopen project files");
if (!Array.isArray(args.paths) || !args.paths.length || args.paths.length > 16) {
throw commandError("UXP_INVALID_ARGUMENT", "paths must contain 1-16 project paths");
}
let project = await targetProject(args.projectId);
const sourcePath = await allowedWorkspacePath(args.expectedPath == null ? project.path : args.expectedPath, "expectedPath");
assertProjectPath(project, sourcePath, "source project");
const paths = [];
for (let i = 0; i < args.paths.length; i += 1) {
const path = await allowedWorkspacePath(args.paths[i], "paths[" + i + "]");
if (samePath(path, sourcePath) || paths.some(function (item) { return samePath(item, path); })) {
throw commandError("UXP_INVALID_ARGUMENT", "Branch paths must be distinct from the source and each other");
}
rejectExistingProject(path, args.confirmOverwrite);
paths.push(path);
}
const branches = [];
for (let i = 0; i < paths.length; i += 1) {
const path = paths[i];
if (typeof project.saveAs !== "function" || !await project.saveAs(path)) {
throw commandError("UXP_PARTIAL_FAILURE", "Premiere stopped while creating branch copy " + (i + 1));
}
assertProjectPath(project, path, "branch copy");
branches.push({ index: i, projectId: guidString(project.guid), path, verified: "project_path_readback" });
if (typeof project.close !== "function" || !await project.close(closeOptions({ promptIfDirty: false }))) {
throw commandError("UXP_PARTIAL_FAILURE", "Branch copy was saved but its project view could not be closed");
}
project = await ppro.Project.open(sourcePath, openOptions({ showDialogs: false, addToMru: false }));
assertProjectPath(project, sourcePath, "reopened source project");
}
return {
created: branches.length,
branches,
sourceProjectId: guidString(project.guid),
sourceReopened: true,
outcome: "verified",
verificationBoundary: "project_path_readback_after_each_save_as"
};
}
async function closeProjectSession(args) {
assertOnlyKeys(args, ["projectId", "expectedPath", "saveBeforeClose", "confirmClose", "confirmDiscardUnsaved", "operationId"]);
requireConfirmation(args.confirmClose, "confirmClose", "Closing a project changes the Premiere workspace");
const project = await targetProject(args.projectId);
if (args.expectedPath != null) assertProjectPath(project, requiredPath(args.expectedPath, "expectedPath"), "target project");
const projectId = guidString(project.guid);
const saveBeforeClose = optionalBoolean(args.saveBeforeClose, true, "saveBeforeClose");
if (saveBeforeClose) {
if (typeof project.save !== "function" || !await project.save()) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the pre-close save");
}
} else {
requireConfirmation(args.confirmDiscardUnsaved, "confirmDiscardUnsaved", "Closing without saving may discard project changes");
}
if (typeof project.close !== "function" || !await project.close(closeOptions({ promptIfDirty: false }))) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the project close");
}
const remaining = await openProjectInventory(false);
if (remaining.some(function (item) { return item.projectId === projectId; })) {
throw commandError("UXP_VERIFICATION_FAILED", "The closed project is still present in an open Project view");
}
return { closed: true, saved: saveBeforeClose, projectId, outcome: "verified", verificationBoundary: "project_view_absence_readback" };
}
function growingMediaStatus(args) {
assertOnlyKeys(args, []);
const stored = readGrowingLease();
const lease = growingLease || stored;
return {
pausedByThisPanel: !!lease,
projectId: lease ? lease.projectId : null,
expiresAt: lease ? new Date(lease.expiresAt).toISOString() : null,
recoveryPending: !!stored && !growingLease,
verificationBoundary: "panel_local_lease_only"
};
}
async function pauseGrowingMedia(args) {
assertOnlyKeys(args, ["projectId", "expectedPath", "leaseMs", "confirmPause", "operationId"]);
requireConfirmation(args.confirmPause, "confirmPause", "Pausing growing-media swaps can delay visibility of newly written media");
const leaseMs = args.leaseMs == null ? 60000 : integer(args.leaseMs, "leaseMs", 1000, 600000);
const project = await targetProject(args.projectId);
if (args.expectedPath != null) assertProjectPath(project, requiredPath(args.expectedPath, "expectedPath"), "target project");
if (typeof project.pauseGrowing !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This project cannot control growing media");
if (growingLease || readGrowingLease()) await resumeLease("superseded_by_new_lease");
if (!await project.pauseGrowing(true)) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not confirm the growing-media pause request");
growingLease = {
schemaVersion: 1,
projectId: guidString(project.guid),
expiresAt: now() + leaseMs
};
writeGrowingLease(growingLease);
clearGrowingTimer();
growingTimer = scheduleTimer(function () {
Promise.resolve(resumeLease("lease_expired")).catch(function () {});
}, leaseMs);
return {
paused: true,
projectId: growingLease.projectId,
leaseMs,
expiresAt: new Date(growingLease.expiresAt).toISOString(),
outcome: "committed_unverified",
verificationBoundary: "project_pauseGrowing_host_return_only"
};
}
async function resumeGrowingMedia(args) {
assertOnlyKeys(args, ["projectId", "operationId"]);
return resumeLease("explicit_resume", args.projectId);
}
async function hasWorkflowCheckpoint(args) {
assertOnlyKeys(args, ["owner", "sequenceId", "expectedOwnerId", "name"]);
const context = await checkpointContext(args);
return checkpointReceipt(context, context.properties.hasValue(context.key), null, null);
}
async function getWorkflowCheckpoint(args) {
assertOnlyKeys(args, ["owner", "sequenceId", "expectedOwnerId", "name", "valueType"]);
const context = await checkpointContext(args);
const valueType = checkpointValueType(args.valueType);
const exists = !!context.properties.hasValue(context.key);
return checkpointReceipt(context, exists, valueType, exists ? readCheckpointValue(context.properties, context.key, valueType) : null);
}
async function setWorkflowCheckpoint(args) {
assertOnlyKeys(args, ["owner", "sequenceId", "expectedOwnerId", "name", "valueType", "value", "persistence", "operationId"]);
const context = await checkpointContext(args);
const valueType = checkpointValueType(args.valueType);
const value = checkpointValue(args.value, valueType);
const persistence = checkpointPersistence(args.persistence);
let committed = false;
context.project.lockedAccess(function () {
const action = context.properties.createSetValueAction(context.key, value, persistence.value);
committed = context.project.executeTransaction(function (compoundAction) {
if (!action || compoundAction.addAction(action) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the checkpoint set action");
}
}, "Set Premiere MCP checkpoint");
});
if (!committed) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not commit the checkpoint transaction");
if (!context.properties.hasValue(context.key)) throw commandError("UXP_VERIFICATION_FAILED", "Checkpoint was absent after the transaction");
const readback = readCheckpointValue(context.properties, context.key, valueType);
if (!sameCheckpointValue(readback, value, valueType)) throw commandError("UXP_VERIFICATION_FAILED", "Checkpoint readback did not match the requested value");
return {
...checkpointReceipt(context, true, valueType, readback),
persistence: persistence.name,
outcome: "verified",
verificationBoundary: "typed_property_readback"
};
}
async function clearWorkflowCheckpoint(args) {
assertOnlyKeys(args, ["owner", "sequenceId", "expectedOwnerId", "name", "operationId"]);
const context = await checkpointContext(args);
if (!context.properties.hasValue(context.key)) {
return { ...checkpointReceipt(context, false, null, null), cleared: false, outcome: "verified", verificationBoundary: "property_absence_readback" };
}
let committed = false;
context.project.lockedAccess(function () {
const action = context.properties.createClearValueAction(context.key);
committed = context.project.executeTransaction(function (compoundAction) {
if (!action || compoundAction.addAction(action) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the checkpoint clear action");
}
}, "Clear Premiere MCP checkpoint");
});
if (!committed || context.properties.hasValue(context.key)) {
throw commandError("UXP_VERIFICATION_FAILED", "Checkpoint remained after the clear transaction");
}
return { ...checkpointReceipt(context, false, null, null), cleared: true, outcome: "verified", verificationBoundary: "property_absence_readback" };
}
async function checkpointContext(args) {
const owner = args.owner == null ? "project" : args.owner;
if (owner !== "project" && owner !== "sequence") throw commandError("UXP_INVALID_ARGUMENT", "owner must be project or sequence");
const project = await activeProject(true);
const target = owner === "project" ? project : await resolveSequence(project, args.sequenceId, true);
const ownerId = guidString(target.guid);
if (args.expectedOwnerId != null && optionalToken(args.expectedOwnerId, "expectedOwnerId") !== ownerId) {
throw commandError("UXP_STALE_TARGET", "The checkpoint owner changed before dispatch");
}
const name = checkpointName(args.name);
const properties = await ppro.Properties.getProperties(target);
if (!properties || typeof properties.hasValue !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This checkpoint owner does not expose Adobe properties");
}
return { owner, ownerId, project, target, properties, name, key: "premiereMcp." + name };
}
function checkpointName(value) {
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/.test(value) || value.indexOf("premiereMcp.") === 0) {
throw commandError("UXP_INVALID_ARGUMENT", "name must be a 1-96 character unprefixed checkpoint token");
}
return value;
}
function checkpointValueType(value) {
if (value !== "string" && value !== "int" && value !== "float" && value !== "bool") {
throw commandError("UXP_INVALID_ARGUMENT", "valueType must be string, int, float, or bool");
}
return value;
}
function checkpointValue(value, valueType) {
if (valueType === "string") {
if (typeof value !== "string" || value.length > 8192 || value.indexOf("\0") !== -1) {
throw commandError("UXP_INVALID_ARGUMENT", "string checkpoint values must be at most 8192 characters and contain no NUL");
}
return value;
}
if (valueType === "bool") {
if (typeof value !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "bool checkpoint values must be boolean");
return value;
}
if (typeof value !== "number" || !Number.isFinite(value)) throw commandError("UXP_INVALID_ARGUMENT", valueType + " checkpoint values must be finite numbers");
if (valueType === "int" && (!Number.isSafeInteger(value) || Math.abs(value) > 2147483647)) {
throw commandError("UXP_INVALID_ARGUMENT", "int checkpoint values must be 32-bit safe integers");
}
return value;
}
function checkpointPersistence(value) {
const name = value == null ? "session" : value;
if (name !== "session" && name !== "persistent") throw commandError("UXP_INVALID_ARGUMENT", "persistence must be session or persistent");
const constants = ppro.Constants && ppro.Constants.PropertyType || {};
const persistent = ppro.Properties.PROPERTY_PERSISTENT != null ? ppro.Properties.PROPERTY_PERSISTENT : constants.PERSISTENT;
const nonPersistent = ppro.Properties.PROPERTY_NON_PERSISTENT != null ? ppro.Properties.PROPERTY_NON_PERSISTENT : constants.NON_PERSISTENT;
const flag = name === "persistent" ? persistent : nonPersistent;
if (flag == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build does not expose checkpoint persistence constants");
return { name, value: flag };
}
function readCheckpointValue(properties, key, valueType) {
const readers = { string: "getValue", int: "getValueAsInt", float: "getValueAsFloat", bool: "getValueAsBool" };
const reader = readers[valueType];
if (typeof properties[reader] !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This checkpoint value type is unavailable");
return properties[reader](key);
}
function sameCheckpointValue(left, right, valueType) {
if (valueType === "float") return Math.abs(left - right) <= Math.max(1e-9, Math.abs(right) * 1e-9);
return left === right;
}
function checkpointReceipt(context, exists, valueType, value) {
return {
owner: context.owner,
ownerId: context.ownerId,
name: context.name,
keyNamespace: "premiereMcp.",
exists: !!exists,
valueType: valueType || null,
value: exists && valueType ? value : null
};
}
async function inspectMediaHealth(args) {
assertOnlyKeys(args, ["projectItemIds", "includePaths", "includeMediaTiming"]);
const project = await activeProject(true);
const clips = await resolveMediaHealthClips(project, args.projectItemIds);
const includePaths = optionalBoolean(args.includePaths, false, "includePaths");
const includeMediaTiming = optionalBoolean(args.includeMediaTiming, false, "includeMediaTiming");
const items = [];
for (let i = 0; i < clips.length; i += 1) items.push(await mediaHealthSnapshot(clips[i], includePaths, includeMediaTiming));
return { count: items.length, items, pathDisclosure: includePaths ? "requested" : "redacted" };
}
async function refreshMediaHealth(args) {
assertOnlyKeys(args, ["projectItemIds", "expectedOffline", "operationId"]);
const project = await activeProject(true);
const clips = await resolveMediaHealthClips(project, args.projectItemIds);
const expectedOffline = optionalExpectedBoolean(args.expectedOffline, "expectedOffline");
const preflight = [];
for (let i = 0; i < clips.length; i += 1) {
const offline = !!(await clips[i].isOffline());
if (expectedOffline != null && offline !== expectedOffline) {
throw commandError("UXP_STALE_TARGET", "A media item changed offline state before refresh");
}
preflight.push({ clip: clips[i], projectItemId: await clipProjectItemId(clips[i]), beforeOffline: offline });
}
const items = [];
for (let i = 0; i < preflight.length; i += 1) {
const target = preflight[i];
try {
const accepted = !!(await target.clip.refreshMedia());
items.push({
projectItemId: target.projectItemId,
accepted,
beforeOffline: target.beforeOffline,
afterOffline: !!(await target.clip.isOffline()),
verificationBoundary: "refreshMedia_return_and_offline_readback"
});
} catch (error) {
items.push({ projectItemId: target.projectItemId, accepted: false, error: error && error.message || String(error) });
}
}
const refreshed = items.filter(function (item) { return item.accepted; }).length;
return {
requested: items.length,
refreshed,
failed: items.length - refreshed,
items,
outcome: refreshed === items.length ? "verified" : refreshed ? "partial" : "failed",
verificationBoundary: "per_item_refresh_return_and_offline_readback"
};
}
async function setMediaOffline(args) {
assertOnlyKeys(args, ["projectItemIds", "expectedOffline", "confirmSetOffline", "operationId"]);
requireConfirmation(args.confirmSetOffline, "confirmSetOffline", "Setting source media offline changes every selected clip reference");
const project = await activeProject(true);
const clips = await resolveMediaHealthClips(project, args.projectItemIds);
const expectedOffline = optionalExpectedBoolean(args.expectedOffline, "expectedOffline");
const targets = [];
for (let i = 0; i < clips.length; i += 1) {
const offline = !!(await clips[i].isOffline());
if (expectedOffline != null && offline !== expectedOffline) {
throw commandError("UXP_STALE_TARGET", "A media item changed offline state before the transaction");
}
targets.push({ clip: clips[i], projectItemId: await clipProjectItemId(clips[i]) });
}
let committed = false;
project.lockedAccess(function () {
const actions = targets.map(function (target) { return target.clip.createSetOfflineAction(); });
committed = project.executeTransaction(function (compoundAction) {
for (let i = 0; i < actions.length; i += 1) {
if (!actions[i] || compoundAction.addAction(actions[i]) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected a set-offline action");
}
}
}, "Set source media offline");
});
if (!committed) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not commit the set-offline transaction");
const items = [];
for (let i = 0; i < targets.length; i += 1) {
const offline = !!(await targets[i].clip.isOffline());
if (!offline) throw commandError("UXP_VERIFICATION_FAILED", "A media item remained online after the transaction");
items.push({ projectItemId: targets[i].projectItemId, offline: true });
}
return {
updated: items.length,
items,
outcome: "verified",
verificationBoundary: "offline_state_readback",
undoLabel: "Set source media offline"
};
}
async function findMediaByPath(args) {
assertOnlyKeys(args, ["projectItemId", "matchPath", "ignoreSubclips", "includePaths"]);
const project = await activeProject(true);
const clips = await resolveMediaHealthClips(project, args.projectItemId == null ? null : [args.projectItemId]);
if (clips.length !== 1) throw commandError("UXP_INVALID_ARGUMENT", "find_by_media_path requires exactly one seed media item");
const matchPath = await allowedWorkspacePath(args.matchPath, "matchPath");
if (typeof clips[0].findItemsMatchingMediaPath !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This media item cannot search project media paths");
}
const matches = Array.from(await clips[0].findItemsMatchingMediaPath(
matchPath, optionalBoolean(args.ignoreSubclips, true, "ignoreSubclips")
) || []);
if (matches.length > 512) throw commandError("UXP_PROJECT_TOO_LARGE", "Media-path search exceeded the 512-result safety cap");
const includePaths = optionalBoolean(args.includePaths, false, "includePaths");
const items = [];
for (let i = 0; i < matches.length; i += 1) {
const clip = castMediaClip(matches[i]);
const snapshot = await mediaHealthSnapshot(clip, includePaths);
items.push(snapshot);
}
return {
count: items.length,
items,
matchPath: includePaths ? matchPath : null,
pathDisclosure: includePaths ? "requested" : "redacted"
};
}
async function inspectSourceMediaTiming(args) {
assertOnlyKeys(args, ["projectItemId"]);
const projectItemId = boundedIdentifier(args.projectItemId, "projectItemId");
const project = await activeProject(true);
const clips = await resolveMediaHealthClips(project, [projectItemId]);
return {
...await sourceMediaTimingSnapshot(clips[0], projectItemId),
verificationBoundary: "source_media_timing_readback"
};
}
async function setSourceMediaStart(args) {
assertOnlyKeys(args, ["projectItemId", "expectedTiming", "startSeconds", "confirmSetStart", "operationId"]);
requireConfirmation(args.confirmSetStart, "confirmSetStart", "Changing a source media start time changes its timecode offset");
const projectItemId = boundedIdentifier(args.projectItemId, "projectItemId");
const expectedTiming = requiredSourceMediaTiming(args.expectedTiming, "expectedTiming");
const startSeconds = boundedSeconds(args.startSeconds, "startSeconds");
const initialProject = await activeProject(true);
const projectId = guidString(initialProject.guid);
if (!projectId) throw commandError("UXP_COMMAND_UNAVAILABLE", "The active project does not expose a stable GUID");
return withSourceMediaUpdateLock(projectId + "\u0000" + projectItemId, async function () {
const project = await activeProject(true);
if (guidString(project.guid) !== projectId) {
throw commandError("UXP_STALE_TARGET", "The active project changed before the source media timing update");
}
const clips = await resolveMediaHealthClips(project, [projectItemId]);
const clip = clips[0];
const before = await sourceMediaTimingSnapshot(clip, projectItemId);
if (!sameSourceMediaTiming(before, expectedTiming)) {
throw commandError("UXP_STALE_TARGET", "The source media timing changed before the transaction");
}
const media = await sourceMediaForUpdate(clip);
let committed = false;
project.lockedAccess(function () {
const current = synchronousSourceMediaTimingSnapshot(media, projectItemId);
if (!sameSourceMediaTiming(current, expectedTiming)) {
throw commandError("UXP_STALE_TARGET", "The source media timing changed before action creation");
}
const action = media.createSetStartAction(ppro.TickTime.createWithSeconds(startSeconds));
if (!action) throw commandError("UXP_ACTION_REJECTED", "Premiere did not create the source media start action");
committed = project.executeTransaction(function (compoundAction) {
if (compoundAction.addAction(action) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected the source media start action");
}
}, "Set source media start time");
});
if (!committed) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not commit the source media timing transaction");
const afterProject = await activeProject(true);
if (guidString(afterProject.guid) !== projectId) {
throw commandError("UXP_VERIFICATION_FAILED", "The active project changed before source media timing readback");
}
const afterClip = (await resolveMediaHealthClips(afterProject, [projectItemId]))[0];
const after = await sourceMediaTimingSnapshot(afterClip, projectItemId);
if (!sameSeconds(after.startSeconds, startSeconds) || !sameSeconds(after.durationSeconds, expectedTiming.durationSeconds)) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not retain the requested source media timing");
}
return {
updated: true,
projectItemId,
before,
after,
outcome: "verified",
verificationBoundary: "source_media_timing_readback",
undoLabel: "Set source media start time"
};
});
}
async function inspectSourceMediaOverrides(args) {
assertOnlyKeys(args, ["projectItemId"]);
const projectItemId = boundedIdentifier(args.projectItemId, "projectItemId");
const project = await activeProject(true);
const projectGuid = guidString(project.guid);
if (!projectGuid) throw commandError("UXP_COMMAND_UNAVAILABLE", "The active project does not expose a stable GUID");
const clips = await resolveMediaHealthClips(project, [projectItemId]);
return {
...await sourceMediaOverrideSnapshot(projectGuid, clips[0], projectItemId),
verificationBoundary: "source_media_effective_interpretation_readback"
};
}
async function updateSourceMediaOverrides(args) {
assertOnlyKeys(args, ["projectItemId", "expectedOverrides", "frameRate", "pixelAspectRatio", "confirmMediaInterpretation", "operationId"]);
requireConfirmation(args.confirmMediaInterpretation, "confirmMediaInterpretation", "Changing source media interpretation can alter editorial timing and framing");
const projectItemId = boundedIdentifier(args.projectItemId, "projectItemId");
const expected = requiredSourceMediaOverrides(args.expectedOverrides, "expectedOverrides");
const requested = requestedSourceMediaOverrides(args);
requireOperationId(args.operationId, "operationId");
const initialProject = await activeProject(true);
const projectGuid = guidString(initialProject.guid);
if (!projectGuid) throw commandError("UXP_COMMAND_UNAVAILABLE", "The active project does not expose a stable GUID");
if (projectGuid !== expected.projectGuid) {
throw commandError("UXP_STALE_TARGET", "The active project does not match the inspected source media override snapshot");
}
return withSourceMediaUpdateLock(projectGuid + "\u0000" + projectItemId, async function () {
const project = await activeProject(true);
if (guidString(project.guid) !== projectGuid) {
throw commandError("UXP_STALE_TARGET", "The active project changed before the source media override update");
}
const clip = (await resolveMediaHealthClips(project, [projectItemId]))[0];
const before = await sourceMediaOverrideSnapshot(projectGuid, clip, projectItemId);
if (!sameSourceMediaOverrides(before, expected)) {
throw commandError("UXP_STALE_TARGET", "The source media interpretation changed before the transaction");
}
let committed = false;
project.lockedAccess(function () {
// getFootageInterpretation() is asynchronous in the documented API,
// so the complete stale snapshot is intentionally taken immediately
// before this synchronous locked action-creation boundary. The
// per-item tail prevents another MCP mutation from interleaving here.
if (guidString(project.guid) !== projectGuid) {
throw commandError("UXP_STALE_TARGET", "The active project changed before source media override action creation");
}
const actions = [];
if (requested.frameRate != null) {
if (typeof clip.createSetOverrideFrameRateAction !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This source clip cannot create an override frame-rate action");
}
actions.push(clip.createSetOverrideFrameRateAction(requested.frameRate));
}
if (requested.pixelAspectRatio != null) {
if (typeof clip.createSetOverridePixelAspectRatioAction !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This source clip cannot create an override pixel-aspect-ratio action");
}
actions.push(clip.createSetOverridePixelAspectRatioAction(
requested.pixelAspectRatio.numerator, requested.pixelAspectRatio.denominator
));
}
committed = project.executeTransaction(function (compoundAction) {
for (let i = 0; i < actions.length; i += 1) {
if (!actions[i] || compoundAction.addAction(actions[i]) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected a source media override action");
}
}
}, "Set source media interpretation override");
});
if (!committed) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not commit the source media override transaction");
const afterProject = await activeProject(true);
if (guidString(afterProject.guid) !== projectGuid) {
throw commandError("UXP_VERIFICATION_FAILED", "The active project changed before source media override readback");
}
const afterClip = (await resolveMediaHealthClips(afterProject, [projectItemId]))[0];
const after = await sourceMediaOverrideSnapshot(projectGuid, afterClip, projectItemId);
const expectedAfter = {
projectGuid,
projectItemId,
frameRate: requested.frameRate == null ? expected.frameRate : requested.frameRate,
pixelAspectRatio: requested.pixelAspectRatio == null
? expected.pixelAspectRatio
: requested.pixelAspectRatio.numerator / requested.pixelAspectRatio.denominator
};
if (!sameSourceMediaOverrides(after, expectedAfter)) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not retain the requested source media interpretation override");
}
return {
updated: true,
projectItemId,
before,
after,
requested,
outcome: "verified",
verificationBoundary: "source_media_effective_interpretation_readback",
undoLabel: "Set source media interpretation override"
};
});
}
function withSourceMediaUpdateLock(key, operation) {
const previous = sourceMediaUpdateTails.get(key) || Promise.resolve();
let release;
const gate = new Promise(function (resolve) { release = resolve; });
const tail = previous.catch(function () { return undefined; }).then(function () { return gate; });
sourceMediaUpdateTails.set(key, tail);
return previous.catch(function () { return undefined; }).then(operation).finally(function () {
release();
if (sourceMediaUpdateTails.get(key) === tail) sourceMediaUpdateTails.delete(key);
});
}
async function sourceMediaOverrideSnapshot(projectGuid, clip, projectItemId) {
if (!clip || typeof clip.getFootageInterpretation !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This project item cannot expose source media interpretation");
}
const interpretation = await clip.getFootageInterpretation();
if (!interpretation || typeof interpretation.getFrameRate !== "function" || typeof interpretation.getPixelAspectRatio !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This source media cannot read its effective frame rate and pixel aspect ratio");
}
return {
projectGuid,
projectItemId,
frameRate: boundedSourceFrameRate(interpretation.getFrameRate(), "host frameRate"),
pixelAspectRatio: boundedPixelAspectRatio(interpretation.getPixelAspectRatio(), "host pixelAspectRatio")
};
}
function requiredSourceMediaOverrides(value, name) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be an inspect snapshot object");
}
assertOnlyKeys(value, ["projectGuid", "frameRate", "pixelAspectRatio"]);
return {
projectGuid: optionalToken(value.projectGuid, name + ".projectGuid"),
frameRate: boundedSourceFrameRate(value.frameRate, name + ".frameRate"),
pixelAspectRatio: boundedPixelAspectRatio(value.pixelAspectRatio, name + ".pixelAspectRatio")
};
}
function requestedSourceMediaOverrides(args) {
const frameRate = args.frameRate == null ? null : boundedSourceFrameRate(args.frameRate, "frameRate");
let pixelAspectRatio = null;
if (args.pixelAspectRatio != null) {
const value = args.pixelAspectRatio;
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw commandError("UXP_INVALID_ARGUMENT", "pixelAspectRatio must be a ratio object");
}
assertOnlyKeys(value, ["numerator", "denominator"]);
pixelAspectRatio = {
numerator: integer(value.numerator, "pixelAspectRatio.numerator", 1, 10000),
denominator: integer(value.denominator, "pixelAspectRatio.denominator", 1, 10000)
};
boundedPixelAspectRatio(pixelAspectRatio.numerator / pixelAspectRatio.denominator, "pixelAspectRatio");
}
if (frameRate == null && pixelAspectRatio == null) {
throw commandError("UXP_INVALID_ARGUMENT", "Provide frameRate and/or pixelAspectRatio to update");
}
return { frameRate, pixelAspectRatio };
}
function boundedSourceFrameRate(value, name) {
const number = Number(value);
if (!Number.isFinite(number) || number < 1 || number > 240) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be between 1 and 240 frames per second");
}
return number;
}
function boundedPixelAspectRatio(value, name) {
const number = Number(value);
if (!Number.isFinite(number) || number < 0.01 || number > 100) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be between 0.01 and 100");
}
return number;
}
function sameSourceMediaOverrides(left, right) {
return !!left && !!right && left.projectGuid === right.projectGuid &&
(right.projectItemId == null || left.projectItemId === right.projectItemId) &&
sameSeconds(left.frameRate, right.frameRate) && sameSeconds(left.pixelAspectRatio, right.pixelAspectRatio);
}
async function sourceMediaForUpdate(clip) {
if (!clip || typeof clip.getMedia !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This project item cannot expose source media timing");
}
const media = await clip.getMedia();
if (!media || typeof media.createSetStartAction !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This source media cannot create a start-time action");
}
return media;
}
async function sourceMediaTimingSnapshot(clip, projectItemId) {
const media = await sourceMediaForRead(clip);
const start = await mediaTimingValue(media, "start");
const duration = await mediaTimingValue(media, "duration");
if (start.seconds == null || duration.seconds == null) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not return a bounded source media timing snapshot");
}
return { projectItemId, startSeconds: start.seconds, durationSeconds: duration.seconds };
}
async function sourceMediaForRead(clip) {
if (!clip || typeof clip.getMedia !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This project item cannot expose source media timing");
}
const media = await clip.getMedia();
if (!media) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere did not return source media for this project item");
return media;
}
function synchronousSourceMediaTimingSnapshot(media, projectItemId) {
const start = synchronousMediaTimingValue(media, "start");
const duration = synchronousMediaTimingValue(media, "duration");
if (start == null || duration == null) {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This host cannot synchronously read stable source media timing during action creation");
}
return { projectItemId, startSeconds: start, durationSeconds: duration };
}
function synchronousMediaTimingValue(media, propertyName) {
try {
const value = media[propertyName];
if (value && typeof value.then === "function") return null;
return boundedMediaTimingSeconds(value);
} catch (_) { return null; }
}
function requiredSourceMediaTiming(value, name) {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be an inspect snapshot object");
}
assertOnlyKeys(value, ["startSeconds", "durationSeconds"]);
return {
startSeconds: boundedSeconds(value.startSeconds, name + ".startSeconds"),
durationSeconds: boundedSeconds(value.durationSeconds, name + ".durationSeconds")
};
}
function sameSourceMediaTiming(left, right) {
return !!left && !!right && sameSeconds(left.startSeconds, right.startSeconds) && sameSeconds(left.durationSeconds, right.durationSeconds);
}
async function resolveMediaHealthClips(project, projectItemIds) {
if (projectItemIds == null) {
if (!ppro.ProjectUtils || typeof ppro.ProjectUtils.getSelection !== "function") {
throw commandError("UXP_INVALID_ARGUMENT", "projectItemIds are required because Project-panel selection is unavailable");
}
const selection = await ppro.ProjectUtils.getSelection(project);
const selected = selection && Array.from(await selection.getItems() || []);
if (!selected || !selected.length || selected.length > 64) {
throw commandError("UXP_INVALID_ARGUMENT", "Select 1-64 media items or pass projectItemIds");
}
return selected.map(castMediaClip);
}
if (!Array.isArray(projectItemIds) || !projectItemIds.length || projectItemIds.length > 64) {
throw commandError("UXP_INVALID_ARGUMENT", "projectItemIds must contain 1-64 identifiers");
}
const wanted = projectItemIds.map(function (value, index) { return boundedIdentifier(value, "projectItemIds[" + index + "]"); });
if (new Set(wanted).size !== wanted.length) throw commandError("UXP_INVALID_ARGUMENT", "projectItemIds must be unique");
const found = new Map(), queue = [await project.getRootItem()];
let visited = 0;
while (queue.length && found.size < wanted.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) {
visited += 1;
if (visited > 10000) throw commandError("UXP_PROJECT_TOO_LARGE", "Project-item lookup exceeded 10000 entries");
const item = children[i], id = await projectItemId(item);
if (wanted.indexOf(id) !== -1) found.set(id, castMediaClip(item));
const childFolder = castFolder(item);
if (childFolder) queue.push(childFolder);
}
}
const missing = wanted.filter(function (id) { return !found.has(id); });
if (missing.length) throw commandError("UXP_TARGET_NOT_FOUND", "One or more projectItemIds were not found as media clips");
return wanted.map(function (id) { return found.get(id); });
}
function castMediaClip(item) {
try {
const clip = ppro.ClipProjectItem.cast(item);
if (clip) return clip;
} catch (_) {}
throw commandError("UXP_TARGET_NOT_FOUND", "A selected project item is not a media clip");
}
function castFolder(item) {
if (!ppro.FolderItem || typeof ppro.FolderItem.cast !== "function") return null;
try { return ppro.FolderItem.cast(item) || null; } catch (_) { return null; }
}
async function projectItemId(item) {
let value = item;
if (ppro.ProjectItem && typeof ppro.ProjectItem.cast === "function") {
try { value = ppro.ProjectItem.cast(item) || item; } catch (_) {}
}
if (!value || typeof value.getId !== "function") return "";
return String(await value.getId() || "");
}
function clipProjectItemId(clip) {
return projectItemId(clip);
}
async function mediaHealthSnapshot(clip, includePaths, includeMediaTiming) {
const id = await clipProjectItemId(clip);
const offline = typeof clip.isOffline === "function" ? !!(await clip.isOffline()) : null;
const hasProxy = typeof clip.hasProxy === "function" ? !!(await clip.hasProxy()) : null;
const result = {
projectItemId: id,
name: String(clip.name || ""),
offline,
canChangeMediaPath: typeof clip.canChangeMediaPath === "function" ? !!(await clip.canChangeMediaPath()) : null,
canProxy: typeof clip.canProxy === "function" ? !!(await clip.canProxy()) : null,
hasProxy,
mergedClip: typeof clip.isMergedClip === "function" ? !!(await clip.isMergedClip()) : null,
multicamClip: typeof clip.isMulticamClip === "function" ? !!(await clip.isMulticamClip()) : null
};
if (includePaths) {
result.mediaPath = typeof clip.getMediaFilePath === "function" ? String(await clip.getMediaFilePath() || "") : null;
result.proxyPath = hasProxy && typeof clip.getProxyPath === "function" ? String(await clip.getProxyPath() || "") : null;
result.originatingProjectPath = typeof clip.getOriginatingProjectPath === "function"
? String(await clip.getOriginatingProjectPath() || "") : null;
}
if (includeMediaTiming) result.mediaTiming = await mediaTimingSnapshot(clip);
return result;
}
async function mediaTimingSnapshot(clip) {
const unavailable = function () {
return {
available: false,
startSeconds: null,
durationSeconds: null,
startAccessor: null,
durationAccessor: null
};
};
if (!clip || typeof clip.getMedia !== "function") return unavailable();
let media;
try { media = await clip.getMedia(); } catch (_) { return unavailable(); }
if (!media) return unavailable();
const start = await mediaTimingValue(media, "start");
const duration = await mediaTimingValue(media, "duration");
return {
available: start.seconds != null && duration.seconds != null,
startSeconds: start.seconds,
durationSeconds: duration.seconds,
startAccessor: start.accessor,
durationAccessor: duration.accessor
};
}
async function mediaTimingValue(media, propertyName) {
try {
return { accessor: propertyName, seconds: boundedMediaTimingSeconds(await media[propertyName]) };
} catch (_) {
return { accessor: propertyName, seconds: null };
}
}
function boundedMediaTimingSeconds(value) {
try {
if (!value || typeof value !== "object" || typeof value.seconds !== "number") return null;
const seconds = value.seconds;
return Number.isFinite(seconds) && seconds >= 0 && seconds <= 86400000 ? seconds : null;
} catch (_) { return null; }
}
async function inspectTrackState(args) {
assertOnlyKeys(args, ["sequenceId", "expectedSequenceId", "mediaType", "trackIndices"]);
const project = await activeProject(true), sequence = await resolveSequence(project, args.sequenceId, true);
const sequenceId = guidString(sequence.guid);
if (args.expectedSequenceId != null && optionalToken(args.expectedSequenceId, "expectedSequenceId") !== sequenceId) {
throw commandError("UXP_STALE_TARGET", "The target sequence changed before track inspection");
}
const mediaType = trackMediaType(args.mediaType, true);
if (mediaType === "all" && args.trackIndices != null) {
throw commandError("UXP_INVALID_ARGUMENT", "trackIndices require one explicit mediaType");
}
const mediaTypes = mediaType === "all" ? ["video", "audio", "caption"] : [mediaType];
const tracks = [];
for (let i = 0; i < mediaTypes.length; i += 1) {
const type = mediaTypes[i], indices = await requestedTrackIndices(sequence, type, args.trackIndices, false);
for (let j = 0; j < indices.length; j += 1) tracks.push(await trackStateSnapshot(await trackAt(sequence, type, indices[j]), type, indices[j]));
}
return { sequenceId, count: tracks.length, tracks, verificationBoundary: "track_mute_readback" };
}
async function setTrackState(args) {
assertOnlyKeys(args, ["sequenceId", "expectedSequenceId", "mediaType", "trackIndices", "muted", "expectedMuted", "operationId"]);
const project = await activeProject(true), sequence = await resolveSequence(project, args.sequenceId, true);
const sequenceId = guidString(sequence.guid);
if (args.expectedSequenceId != null && optionalToken(args.expectedSequenceId, "expectedSequenceId") !== sequenceId) {
throw commandError("UXP_STALE_TARGET", "The target sequence changed before track mutation");
}
const mediaType = trackMediaType(args.mediaType, false);
if (typeof args.muted !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", "muted must be a boolean");
const expectedMuted = optionalExpectedBoolean(args.expectedMuted, "expectedMuted");
const indices = await requestedTrackIndices(sequence, mediaType, args.trackIndices, true);
const targets = [];
for (let i = 0; i < indices.length; i += 1) {
const track = await trackAt(sequence, mediaType, indices[i]);
if (typeof track.setMute !== "function" || typeof track.isMuted !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "A target track cannot report and set mute state");
}
const beforeMuted = !!(await track.isMuted());
if (expectedMuted != null && beforeMuted !== expectedMuted) {
throw commandError("UXP_STALE_TARGET", "A target track changed mute state before dispatch");
}
targets.push({ track, trackIndex: indices[i], beforeMuted });
}
const tracks = [];
for (let i = 0; i < targets.length; i += 1) {
const target = targets[i];
try {
const accepted = !!(await target.track.setMute(args.muted));
const afterMuted = !!(await target.track.isMuted());
tracks.push({
mediaType, trackIndex: target.trackIndex, beforeMuted: target.beforeMuted,
requestedMuted: args.muted, accepted, afterMuted,
verified: accepted && afterMuted === args.muted
});
} catch (error) {
tracks.push({
mediaType, trackIndex: target.trackIndex, beforeMuted: target.beforeMuted,
requestedMuted: args.muted, accepted: false, verified: false,
error: error && error.message || String(error)
});
}
}
const verified = tracks.filter(function (track) { return track.verified; }).length;
return {
sequenceId,
mediaType,
requested: tracks.length,
updated: verified,
failed: tracks.length - verified,
tracks,
outcome: verified === tracks.length ? "verified" : verified ? "partial" : "failed",
undoable: false,
verificationBoundary: "per_track_mute_readback"
};
}
function trackMediaType(value, allowAll) {
const result = value == null ? (allowAll ? "all" : null) : value;
if (result === "video" || result === "audio" || result === "caption" || (allowAll && result === "all")) return result;
throw commandError("UXP_INVALID_ARGUMENT", "mediaType must be " + (allowAll ? "all, " : "") + "video, audio, or caption");
}
async function requestedTrackIndices(sequence, mediaType, values, required) {
const countMethod = { video: "getVideoTrackCount", audio: "getAudioTrackCount", caption: "getCaptionTrackCount" }[mediaType];
const count = Number(await sequence[countMethod]());
if (!Number.isInteger(count) || count < 0 || count > 1024) throw commandError("UXP_PROJECT_TOO_LARGE", "Track count is invalid or exceeds 1024");
if (values == null) {
if (required) throw commandError("UXP_INVALID_ARGUMENT", "trackIndices are required for set");
if (count > 64) throw commandError("UXP_PROJECT_TOO_LARGE", "Inspecting all tracks exceeds the 64-track response cap");
return Array.from({ length: count }, function (_, index) { return index; });
}
if (!Array.isArray(values) || !values.length || values.length > 64) {
throw commandError("UXP_INVALID_ARGUMENT", "trackIndices must contain 1-64 indices");
}
const indices = values.map(function (value, index) { return integer(value, "trackIndices[" + index + "]", 0, Math.max(0, count - 1)); });
if (new Set(indices).size !== indices.length) throw commandError("UXP_INVALID_ARGUMENT", "trackIndices must be unique");
return indices;
}
async function trackAt(sequence, mediaType, index) {
const method = { video: "getVideoTrack", audio: "getAudioTrack", caption: "getCaptionTrack" }[mediaType];
if (typeof sequence[method] !== "function") throw commandError("UXP_COMMAND_UNAVAILABLE", "This sequence cannot access " + mediaType + " tracks");
const track = await sequence[method](index);
if (!track) throw commandError("UXP_TARGET_NOT_FOUND", "Track index was not found");
return track;
}
async function trackStateSnapshot(track, mediaType, requestedIndex) {
return {
mediaType,
trackIndex: typeof track.getIndex === "function" ? Number(await track.getIndex()) : requestedIndex,
trackId: track.id == null ? null : Number(track.id),
name: String(track.name || ""),
muted: typeof track.isMuted === "function" ? !!(await track.isMuted()) : null
};
}
async function inspectSourceClip(args) {
assertOnlyKeys(args, ["items"]);
const input = validateSourceClipItems(args.items, false);
const project = await activeProject(true);
const clips = await resolveMediaHealthClips(project, input.map(function (item) { return item.projectItemId; }));
const items = [];
for (let i = 0; i < input.length; i += 1) items.push(await sourceClipSnapshot(clips[i], input[i].projectItemId, input[i].mediaType));
return { count: items.length, items, verificationBoundary: "source_in_out_readback" };
}
async function updateSourceClip(args) {
assertOnlyKeys(args, ["items", "operationId"]);
const input = validateSourceClipItems(args.items, true);
const project = await activeProject(true);
const clips = await resolveMediaHealthClips(project, input.map(function (item) { return item.projectItemId; }));
const targets = [];
for (let i = 0; i < input.length; i += 1) {
const before = await sourceClipSnapshot(clips[i], input[i].projectItemId, input[i].mediaType);
if (input[i].expectedInSeconds != null && !sameSeconds(before.inSeconds, input[i].expectedInSeconds)) {
throw commandError("UXP_STALE_TARGET", "A source in point changed before the transaction");
}
if (input[i].expectedOutSeconds != null && !sameSeconds(before.outSeconds, input[i].expectedOutSeconds)) {
throw commandError("UXP_STALE_TARGET", "A source out point changed before the transaction");
}
targets.push({ input: input[i], clip: clips[i], before });
}
let committed = false;
project.lockedAccess(function () {
const actions = [];
for (let i = 0; i < targets.length; i += 1) {
const target = targets[i], value = target.input;
if (value.clearInOut) {
actions.push(target.clip.createClearInOutPointsAction());
} else if (value.inSeconds != null && value.outSeconds != null) {
actions.push(target.clip.createSetInOutPointsAction(
ppro.TickTime.createWithSeconds(value.inSeconds),
ppro.TickTime.createWithSeconds(value.outSeconds)
));
} else {
if (value.inSeconds != null) actions.push(target.clip.createSetInPointAction(ppro.TickTime.createWithSeconds(value.inSeconds)));
if (value.outSeconds != null) actions.push(target.clip.createSetOutPointAction(ppro.TickTime.createWithSeconds(value.outSeconds)));
}
if (value.scaleToFrame) actions.push(target.clip.createSetScaleToFrameSizeAction());
}
committed = project.executeTransaction(function (compoundAction) {
for (let i = 0; i < actions.length; i += 1) {
if (!actions[i] || compoundAction.addAction(actions[i]) === false) {
throw commandError("UXP_ACTION_REJECTED", "Premiere rejected a source-clip action");
}
}
}, "Update source clip trim and framing");
});
if (!committed) throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not commit the source-clip transaction");
const items = [];
let fullyVerified = true;
for (let i = 0; i < targets.length; i += 1) {
const target = targets[i], after = await sourceClipSnapshot(target.clip, target.input.projectItemId, target.input.mediaType);
let trimVerified = true;
if (target.input.inSeconds != null) trimVerified = trimVerified && sameSeconds(after.inSeconds, target.input.inSeconds);
if (target.input.outSeconds != null) trimVerified = trimVerified && sameSeconds(after.outSeconds, target.input.outSeconds);
const clearVerified = target.input.clearInOut ? false : null;
const scaleVerified = target.input.scaleToFrame ? false : null;
if (!trimVerified) throw commandError("UXP_VERIFICATION_FAILED", "Source in/out readback did not match the requested trim");
if (target.input.clearInOut || target.input.scaleToFrame) fullyVerified = false;
items.push({
projectItemId: target.input.projectItemId,
mediaType: target.input.mediaType,
before: target.before,
after,
trimVerified,
clearRequested: target.input.clearInOut,
clearVerified,
scaleToFrameRequested: target.input.scaleToFrame,
scaleToFrameVerified: scaleVerified
});
}
return {
updated: items.length,
items,
outcome: fullyVerified ? "verified" : "committed_unverified",
verificationBoundary: fullyVerified ? "source_in_out_readback" : "transaction_commit_with_missing_clear_or_scale_getter",
undoLabel: "Update source clip trim and framing"
};
}
function validateSourceClipItems(value, mutation) {
if (!Array.isArray(value) || !value.length || value.length > 64) {
throw commandError("UXP_INVALID_ARGUMENT", "items must contain 1-64 source clips");
}
const ids = new Set();
return value.map(function (item, index) {
if (!item || typeof item !== "object" || Array.isArray(item)) throw commandError("UXP_INVALID_ARGUMENT", "items[" + index + "] must be an object");
const allowed = mutation
? ["projectItemId", "mediaType", "expectedInSeconds", "expectedOutSeconds", "inSeconds", "outSeconds", "clearInOut", "scaleToFrame"]
: ["projectItemId", "mediaType"];
assertOnlyKeys(item, allowed);
const projectItemId = boundedIdentifier(item.projectItemId, "items[" + index + "].projectItemId");
if (ids.has(projectItemId)) throw commandError("UXP_INVALID_ARGUMENT", "Each projectItemId may appear only once per transaction");
ids.add(projectItemId);
const mediaType = item.mediaType == null ? "video" : item.mediaType;
if (mediaType !== "video" && mediaType !== "audio") throw commandError("UXP_INVALID_ARGUMENT", "mediaType must be video or audio");
const result = { projectItemId, mediaType };
if (!mutation) return result;
const numberKeys = ["expectedInSeconds", "expectedOutSeconds", "inSeconds", "outSeconds"];
for (let i = 0; i < numberKeys.length; i += 1) {
const key = numberKeys[i];
if (item[key] != null) result[key] = boundedSeconds(item[key], "items[" + index + "]." + key);
}
result.clearInOut = optionalBoolean(item.clearInOut, false, "items[" + index + "].clearInOut");
result.scaleToFrame = optionalBoolean(item.scaleToFrame, false, "items[" + index + "].scaleToFrame");
if (item.scaleToFrame === false) throw commandError("UXP_INVALID_ARGUMENT", "scaleToFrame supports only true because Adobe exposes only a set-true action");
if (result.clearInOut && (result.inSeconds != null || result.outSeconds != null)) {
throw commandError("UXP_INVALID_ARGUMENT", "clearInOut cannot be combined with new in/out points");
}
if (result.inSeconds != null && result.outSeconds != null && result.outSeconds <= result.inSeconds) {
throw commandError("UXP_INVALID_ARGUMENT", "outSeconds must be greater than inSeconds");
}
if (!result.clearInOut && !result.scaleToFrame && result.inSeconds == null && result.outSeconds == null) {
throw commandError("UXP_INVALID_ARGUMENT", "Each update item must request a trim, clear, or scale-to-frame action");
}
return result;
});
}
async function sourceClipSnapshot(clip, projectItemId, mediaType) {
if (typeof clip.getInPoint !== "function" || typeof clip.getOutPoint !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This source clip cannot report in/out points");
}
const mediaConstants = ppro.Constants && ppro.Constants.MediaType || {};
const constant = mediaConstants[mediaType === "video" ? "VIDEO" : "AUDIO"];
if (constant == null) throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere media-type constants are unavailable");
return {
projectItemId,
mediaType,
inSeconds: tickSeconds(await clip.getInPoint(constant)),
outSeconds: tickSeconds(await clip.getOutPoint(constant))
};
}
function tickSeconds(value) {
const seconds = value && Number(value.seconds);
return Number.isFinite(seconds) ? seconds : null;
}
function sameSeconds(left, right) {
return typeof left === "number" && Math.abs(left - right) <= 1e-6;
}
async function resumeLease(reason, explicitProjectId) {
const lease = growingLease || readGrowingLease();
const projectId = explicitProjectId == null ? lease && lease.projectId : optionalToken(explicitProjectId, "projectId");
if (!lease && !projectId) {
return { resumed: false, alreadyResumed: true, reason, outcome: "verified", verificationBoundary: "panel_local_lease_only" };
}
const project = await projectForLease(projectId);
if (!project || typeof project.pauseGrowing !== "function") {
throw commandError("UXP_RECOVERY_PENDING", "The paused project is not currently available for growing-media recovery");
}
if (!await project.pauseGrowing(false)) {
throw commandError("UXP_RECOVERY_PENDING", "Premiere did not confirm the growing-media resume request");
}
clearGrowingTimer();
growingLease = null;
removeGrowingLease();
return {
resumed: true,
projectId: guidString(project.guid),
reason,
outcome: "committed_unverified",
verificationBoundary: "project_pauseGrowing_host_return_only"
};
}
async function projectForLease(projectId) {
if (projectId && ppro.Guid && typeof ppro.Guid.fromString === "function" &&
ppro.Project && typeof ppro.Project.getProject === "function") {
try {
const project = ppro.Project.getProject(ppro.Guid.fromString(projectId));
if (project) return project;
} catch (_) {}
}
return ppro.Project && typeof ppro.Project.getActiveProject === "function"
? ppro.Project.getActiveProject()
: null;
}
function readGrowingLease() {
if (!localStorage || typeof localStorage.getItem !== "function") return null;
try {
const value = JSON.parse(localStorage.getItem(GROWING_LEASE_KEY) || "null");
if (!value || value.schemaVersion !== 1 || typeof value.projectId !== "string" ||
!Number.isFinite(value.expiresAt)) return null;
return { schemaVersion: 1, projectId: value.projectId, expiresAt: value.expiresAt };
} catch (_) { return null; }
}
function writeGrowingLease(value) {
if (!localStorage || typeof localStorage.setItem !== "function") return;
try { localStorage.setItem(GROWING_LEASE_KEY, JSON.stringify(value)); } catch (_) {}
}
function removeGrowingLease() {
if (!localStorage || typeof localStorage.removeItem !== "function") return;
try { localStorage.removeItem(GROWING_LEASE_KEY); } catch (_) {}
}
function clearGrowingTimer() {
if (growingTimer != null) cancelTimer(growingTimer);
growingTimer = null;
}
async function openProjectInventory(includePaths) {
const viewIds = Array.from(await ppro.ProjectUtils.getProjectViewIds() || []);
if (viewIds.length > 64) throw commandError("UXP_PROJECT_TOO_LARGE", "Open Project views exceed the 64-view safety cap");
const seen = new Set(), projects = [];
for (let i = 0; i < viewIds.length; i += 1) {
const project = await ppro.ProjectUtils.getProjectFromViewId(viewIds[i]);
if (!project) continue;
const projectId = guidString(project.guid);
if (!projectId || seen.has(projectId)) continue;
seen.add(projectId);
projects.push({
projectId,
name: String(project.name || ""),
hasPath: !!project.path,
...(includePaths ? { path: String(project.path || "") } : {})
});
}
return projects;
}
async function targetProject(projectId) {
if (projectId == null) {
const active = await ppro.Project.getActiveProject();
if (!active) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
return active;
}
const token = optionalToken(projectId, "projectId");
if (!ppro.Guid || typeof ppro.Guid.fromString !== "function") {
throw commandError("UXP_COMMAND_UNAVAILABLE", "This Premiere build cannot resolve a project GUID");
}
let project = null;
try { project = ppro.Project.getProject(ppro.Guid.fromString(token)); } catch (_) {}
if (!project) throw commandError("UXP_TARGET_NOT_FOUND", "The requested project is not open");
return project;
}
async function allowedWorkspacePath(value, label) {
const path = requiredPath(value, label);
if (!deps.workspace || typeof deps.workspace.assertPathAllowed !== "function") {
throw commandError("UXP_WORKSPACE_REQUIRED", "An approved UXP workspace is required");
}
return deps.workspace.assertPathAllowed(path, { label, kind: "file" });
}
function rejectExistingProject(path, confirmation) {
if (ppro.Project.isProject(path) && confirmation !== true) {
throw commandError("UXP_CONFIRMATION_REQUIRED", "confirmOverwrite is required when the destination is already a Premiere project");
}
}
function projectMutationReceipt(action, project, path) {
return {
action,
projectId: guidString(project.guid),
projectName: String(project.name || ""),
path,
outcome: "verified",
verificationBoundary: "project_path_readback"
};
}
function assertProjectPath(project, expectedPath, label) {
if (!project || !samePath(String(project.path || ""), expectedPath)) {
throw commandError("UXP_VERIFICATION_FAILED", "Premiere did not expose the expected " + label + " path");
}
}
function samePath(left, right) {
const normalize = function (value) { return String(value || "").replace(/\\/g, "/").replace(/\/$/, ""); };
const a = normalize(left), b = normalize(right);
return /^[A-Za-z]:\//.test(a) || /^\/\//.test(a) ? a.toLowerCase() === b.toLowerCase() : a === b;
}
function openOptions(args) {
const value = typeof ppro.OpenProjectOptions === "function" ? ppro.OpenProjectOptions() : undefined;
if (!value) return undefined;
const showDialogs = optionalBoolean(args.showDialogs, false, "showDialogs");
const addToMru = optionalBoolean(args.addToMru, false, "addToMru");
if (typeof value.setShowConvertProjectDialog === "function") value.setShowConvertProjectDialog(showDialogs);
if (typeof value.setShowLocateFileDialog === "function") value.setShowLocateFileDialog(showDialogs);
if (typeof value.setShowWarningDialog === "function") value.setShowWarningDialog(showDialogs);
if (typeof value.setAddToMRUList === "function") value.setAddToMRUList(addToMru);
return value;
}
function closeOptions(args) {
const value = typeof ppro.CloseProjectOptions === "function" ? ppro.CloseProjectOptions() : undefined;
if (!value) return undefined;
if (typeof value.setPromptIfDirty === "function") value.setPromptIfDirty(!!args.promptIfDirty);
if (typeof value.setShowCancelButton === "function") value.setShowCancelButton(true);
if (typeof value.setIsAppBeingPreparedToQuit === "function") value.setIsAppBeingPreparedToQuit(false);
if (typeof value.setSaveWorkspace === "function") value.setSaveWorkspace(true);
return value;
}
async function activeProject(required) {
const project = canInspectReadiness() ? await ppro.Project.getActiveProject() : null;
if (!project && required) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
return project;
}
async function resolveSequence(project, sequenceId, required) {
if (!project) return null;
if (sequenceId == null) {
const active = await project.getActiveSequence();
if (!active && required) throw commandError("UXP_NO_ACTIVE_SEQUENCE", "No active sequence");
return active;
}
const wanted = optionalToken(sequenceId, "sequenceId");
const values = Array.from(await project.getSequences() || []);
if (values.length > 1024) throw commandError("UXP_PROJECT_TOO_LARGE", "Sequence lookup exceeds 1024 entries");
const sequence = values.find(function (item) { return guidString(item && item.guid) === wanted; }) || null;
if (!sequence && required) throw commandError("UXP_TARGET_NOT_FOUND", "Sequence was not found");
return sequence;
}
function operationOutcome(state) {
if (state == null) return "unknown";
const constants = ppro.Constants && ppro.Constants.OperationCompleteState || {};
const staticValues = ppro.OperationCompleteEvent || {};
const success = constants.SUCCESS != null ? constants.SUCCESS : staticValues.OPERATION_STATE_SUCCESS;
const cancelled = constants.CANCELLED != null ? constants.CANCELLED : staticValues.OPERATION_STATE_CANCELLED;
const failed = constants.FAILED != null ? constants.FAILED : staticValues.OPERATION_STATE_FAILED;
if (state === success) return "completed";
if (state === cancelled) return "cancelled";
if (state === failed) return "failed";
return "unknown";
}
}
function query(args, allowTimeout) {
const value = args || {};
return {
afterRevision: value.afterRevision == null ? 0 : integer(value.afterRevision, "afterRevision", 0, Number.MAX_SAFE_INTEGER),
categories: tokenArray(value.categories, "categories"),
eventNames: tokenArray(value.eventNames, "eventNames"),
limit: value.limit == null ? 100 : integer(value.limit, "limit", 1, 256),
timeoutMs: allowTimeout && value.timeoutMs != null ? integer(value.timeoutMs, "timeoutMs", 0, 60000) : 0
};
}
function tokenArray(value, name) {
if (value == null) return [];
if (!Array.isArray(value) || value.length > 32) throw commandError("UXP_INVALID_ARGUMENT", name + " must contain at most 32 values");
return value.map(function (item) {
if (typeof item !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(item)) {
throw commandError("UXP_INVALID_ARGUMENT", name + " contains an invalid token");
}
return item;
});
}
function integer(value, name, minimum, maximum) {
const number = Number(value);
if (!Number.isInteger(number) || number < minimum || number > maximum) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be an integer between " + minimum + " and " + maximum);
}
return number;
}
function optionalToken(value, name) {
if (typeof value !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value)) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be a 1-128 character token");
}
return value;
}
function requireOperationId(value, name) {
if (value == null) throw commandError("UXP_INVALID_ARGUMENT", name + " is required for safe replay");
return optionalToken(value, name);
}
function requiredPath(value, name) {
if (typeof value !== "string" || !value.trim() || value.length > 4096 || value.indexOf("\0") !== -1) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-empty absolute path of at most 4096 characters");
}
return value;
}
function boundedIdentifier(value, name) {
if (typeof value !== "string" || !value || value.length > 512 || value.indexOf("\0") !== -1) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be a non-empty identifier of at most 512 characters");
}
return value;
}
function boundedSeconds(value, name) {
const number = Number(value);
if (!Number.isFinite(number) || number < 0 || number > 86400000) {
throw commandError("UXP_INVALID_ARGUMENT", name + " must be between 0 and 86400000 seconds");
}
return number;
}
function optionalExpectedBoolean(value, name) {
if (value == null) return null;
if (typeof value !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", name + " must be a boolean");
return value;
}
function optionalBoolean(value, fallback, name) {
if (value == null) return fallback;
if (typeof value !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", name + " must be a boolean");
return value;
}
function requireConfirmation(value, name, reason) {
if (value !== true) throw commandError("UXP_CONFIRMATION_REQUIRED", name + " must be true. " + reason + ".");
}
function guidString(value) {
if (value == null) return "";
try { return typeof value.toString === "function" ? String(value.toString()) : String(value); } catch (_) { return ""; }
}
function assertOnlyKeys(value, allowed) {
if (!value || typeof value !== "object" || Array.isArray(value)) throw commandError("UXP_INVALID_ARGUMENT", "arguments must be an object");
for (const key of Object.keys(value)) if (allowed.indexOf(key) === -1) throw commandError("UXP_INVALID_ARGUMENT", "Unexpected argument: " + key);
}
function commandError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
return { createNextWorkflowDefinitions, createNextWorkflowRuntime };
});