- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
205 lines
9.0 KiB
JavaScript
Executable File
205 lines
9.0 KiB
JavaScript
Executable File
(function (root, factory) {
|
|
const api = factory();
|
|
if (typeof module === "object" && module.exports) module.exports = api;
|
|
root.PremiereMcpSourceMediaProvenanceWorkflows = api;
|
|
})(typeof globalThis !== "undefined" ? globalThis : this, function () {
|
|
"use strict";
|
|
|
|
// This command intentionally resolves only the explicitly requested item
|
|
// twice. It must not invoke media-path getters while traversing unrelated
|
|
// project items because those values are sensitive and are outside the
|
|
// caller's bounded target.
|
|
function createSourceMediaProvenanceWorkflowDefinitions(deps) {
|
|
const ppro = deps.ppro;
|
|
return {
|
|
"source.provenance.inspect": {
|
|
readOnly: true,
|
|
targetCapabilityProbe: true,
|
|
minHostVersion: "26.3.0",
|
|
probe: canInspectSourceProvenance,
|
|
handler: inspectSourceProvenance
|
|
}
|
|
};
|
|
|
|
function canInspectSourceProvenance() {
|
|
return !!(
|
|
ppro.Project && typeof ppro.Project.getActiveProject === "function" &&
|
|
ppro.FolderItem && typeof ppro.FolderItem.cast === "function" &&
|
|
ppro.ClipProjectItem && typeof ppro.ClipProjectItem.cast === "function"
|
|
);
|
|
}
|
|
|
|
async function inspectSourceProvenance(args) {
|
|
const request = requestFrom(args);
|
|
const first = await sourceProvenanceSnapshot(request);
|
|
const second = await sourceProvenanceSnapshot(request);
|
|
if (!sameSnapshot(first, second)) {
|
|
throw commandError(
|
|
"UXP_STALE_SOURCE_PROVENANCE",
|
|
"The active project, requested project item, or requested provenance path changed while it was being inspected"
|
|
);
|
|
}
|
|
return second;
|
|
}
|
|
|
|
function requestFrom(args) {
|
|
assertOnlyKeys(args, ["projectItemId", "includeMediaFilePath", "includeOriginatingProjectPath"]);
|
|
const includeMediaFilePath = optionalBoolean(args.includeMediaFilePath, "includeMediaFilePath");
|
|
const includeOriginatingProjectPath = optionalBoolean(args.includeOriginatingProjectPath, "includeOriginatingProjectPath");
|
|
if (!includeMediaFilePath && !includeOriginatingProjectPath) {
|
|
throw commandError(
|
|
"UXP_PATH_DISCLOSURE_REQUIRED",
|
|
"Set includeMediaFilePath or includeOriginatingProjectPath to true before a native path is read"
|
|
);
|
|
}
|
|
return {
|
|
projectItemId: requiredText(args.projectItemId, "projectItemId", 512),
|
|
includeMediaFilePath,
|
|
includeOriginatingProjectPath
|
|
};
|
|
}
|
|
|
|
async function sourceProvenanceSnapshot(request) {
|
|
const project = await activeProject();
|
|
const projectGuid = requiredGuid(project.guid, "active project GUID");
|
|
const rootItem = await requiredMethod(project, "getRootItem")();
|
|
const projectItem = await findProjectItem(rootItem, request.projectItemId);
|
|
if (!projectItem) {
|
|
throw commandError("UXP_TARGET_NOT_FOUND", "projectItemId does not identify an item in the active project");
|
|
}
|
|
const resolvedId = requiredText(requiredMethod(projectItem, "getId")(), "resolved project item ID", 512);
|
|
if (resolvedId !== request.projectItemId) {
|
|
throw commandError("UXP_STALE_SOURCE_PROVENANCE", "The resolved project item ID changed during provenance inspection");
|
|
}
|
|
const clipProjectItem = castClipProjectItem(projectItem);
|
|
const readbackId = requiredText(requiredMethod(projectItem, "getId")(), "resolved project item ID", 512);
|
|
if (readbackId !== request.projectItemId) {
|
|
throw commandError("UXP_STALE_SOURCE_PROVENANCE", "The resolved project item changed before provenance getters ran");
|
|
}
|
|
const snapshot = { projectGuid, projectItemId: readbackId };
|
|
if (request.includeMediaFilePath) {
|
|
snapshot.mediaFilePath = boundedPath(await requiredMethod(clipProjectItem, "getMediaFilePath")(), "media file path");
|
|
}
|
|
if (request.includeOriginatingProjectPath) {
|
|
snapshot.originatingProjectPath = boundedPath(await requiredMethod(clipProjectItem, "getOriginatingProjectPath")(), "originating project path");
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
async function activeProject() {
|
|
if (!ppro.Project || typeof ppro.Project.getActiveProject !== "function") {
|
|
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere project APIs are unavailable");
|
|
}
|
|
const project = await ppro.Project.getActiveProject();
|
|
if (!project) throw commandError("UXP_NO_ACTIVE_PROJECT", "No active project");
|
|
return project;
|
|
}
|
|
|
|
async function findProjectItem(rootItem, expectedId) {
|
|
if (!rootItem || typeof rootItem !== "object") {
|
|
throw commandError("UXP_VERIFICATION_FAILED", "Premiere returned an invalid project root item");
|
|
}
|
|
const pending = [rootItem];
|
|
const visited = new Set();
|
|
while (pending.length) {
|
|
const candidate = pending.shift();
|
|
const candidateId = requiredText(requiredMethod(candidate, "getId")(), "project item ID", 512);
|
|
if (visited.has(candidateId)) continue;
|
|
visited.add(candidateId);
|
|
if (visited.size > 4096) {
|
|
throw commandError("UXP_TARGET_TOO_LARGE", "The active project has more than 4096 reachable project items");
|
|
}
|
|
if (candidateId === expectedId) return candidate;
|
|
const folder = castFolderItem(candidate);
|
|
if (!folder) continue;
|
|
const children = await requiredMethod(folder, "getItems")();
|
|
if (!Array.isArray(children)) {
|
|
throw commandError("UXP_VERIFICATION_FAILED", "Premiere returned an invalid project folder collection");
|
|
}
|
|
if (children.length > 4096 || pending.length + children.length + visited.size > 4096) {
|
|
throw commandError("UXP_TARGET_TOO_LARGE", "The active project has more than 4096 reachable project items");
|
|
}
|
|
for (let index = 0; index < children.length; index += 1) pending.push(children[index]);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function castFolderItem(projectItem) {
|
|
if (!ppro.FolderItem || typeof ppro.FolderItem.cast !== "function") {
|
|
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere FolderItem APIs are unavailable");
|
|
}
|
|
try {
|
|
return ppro.FolderItem.cast(projectItem) || null;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function castClipProjectItem(projectItem) {
|
|
if (!ppro.ClipProjectItem || typeof ppro.ClipProjectItem.cast !== "function") {
|
|
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere ClipProjectItem APIs are unavailable");
|
|
}
|
|
try {
|
|
const clip = ppro.ClipProjectItem.cast(projectItem);
|
|
if (clip) return clip;
|
|
} catch (_) {
|
|
// A non-clip project item cannot expose the two documented provenance
|
|
// getters. Preserve that distinction instead of treating it as absent.
|
|
}
|
|
throw commandError("UXP_TARGET_NOT_MEDIA", "projectItemId does not identify a media-backed ClipProjectItem");
|
|
}
|
|
}
|
|
|
|
function assertObject(value, name) {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
throw commandError("UXP_INVALID_ARGUMENT", (name || "args") + " must be an object");
|
|
}
|
|
}
|
|
function assertOnlyKeys(value, allowed) {
|
|
assertObject(value, "args");
|
|
const unknown = Object.keys(value).filter(function (key) { return !allowed.includes(key); });
|
|
if (unknown.length) throw commandError("UXP_INVALID_ARGUMENT", "Unknown argument: " + unknown[0]);
|
|
}
|
|
function optionalBoolean(value, name) {
|
|
if (value === undefined) return false;
|
|
if (typeof value !== "boolean") throw commandError("UXP_INVALID_ARGUMENT", name + " must be a boolean");
|
|
return value;
|
|
}
|
|
function requiredMethod(value, method) {
|
|
if (!value || typeof value[method] !== "function") {
|
|
throw commandError("UXP_COMMAND_UNAVAILABLE", "Premiere does not expose " + method + " for this target");
|
|
}
|
|
return value[method].bind(value);
|
|
}
|
|
function requiredText(value, name, maximum) {
|
|
if (typeof value !== "string" || !value.length || value.length > maximum || value.indexOf("\u0000") !== -1) {
|
|
throw commandError("UXP_VERIFICATION_FAILED", name + " is unavailable or invalid");
|
|
}
|
|
return value;
|
|
}
|
|
function requiredGuid(value, name) {
|
|
let guid = "";
|
|
try { guid = value == null ? "" : typeof value.toString === "function" ? String(value.toString()) : String(value); } catch (_) { guid = ""; }
|
|
return requiredText(guid, name, 128);
|
|
}
|
|
function boundedPath(value, name) {
|
|
if (typeof value !== "string" || value.length > 4096 || value.indexOf("\u0000") !== -1) {
|
|
throw commandError("UXP_VERIFICATION_FAILED", "Premiere returned an invalid " + name);
|
|
}
|
|
return value;
|
|
}
|
|
function sameSnapshot(left, right) {
|
|
return left.projectGuid === right.projectGuid &&
|
|
left.projectItemId === right.projectItemId &&
|
|
left.mediaFilePath === right.mediaFilePath &&
|
|
left.originatingProjectPath === right.originatingProjectPath;
|
|
}
|
|
function commandError(code, message) {
|
|
const error = new Error(message);
|
|
error.code = code;
|
|
return error;
|
|
}
|
|
|
|
return { createSourceMediaProvenanceWorkflowDefinitions };
|
|
});
|