feat: criado protótipo local de montagem incremental com gancho e
- criado protótipo local de montagem incremental com gancho e trechos preservados - adicionado checkbox no painel para executar planos pelo modo de montagem incremental Resumo: - 8 arquivos alterados - 4 novos - 4 modificados - 0 removidos 4 files changed, 230 insertions(+) Arquivos: - .jhonny/analises.db - code/cep-plugin/index.html - code/cep-plugin/main.js - code/src/tools/timeline.ts - code/engine/aplicar_montagem_incremental_prototipo.py - code/engine/editor/pedido_montagem_incremental_exemplo.json - code/engine/editor/plano_montagem_incremental_6.json - code/engine/editor/prototipo_montagem_incremental.py
This commit is contained in:
@@ -3,6 +3,184 @@ import { sendCommand, BridgeOptions } from "../bridge/file-bridge.js";
|
||||
|
||||
export function getTimelineTools(bridgeOptions: BridgeOptions) {
|
||||
return {
|
||||
assemble_incremental_edit_prototype: {
|
||||
description:
|
||||
"PROTOTYPE: clone a source sequence, clear only the clone, and assemble one project item's A/V source ranges in order. The source sequence is not edited.",
|
||||
parameters: {
|
||||
type: "object" as const,
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
source_sequence_id: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
description: "Source sequence name or ID to clone",
|
||||
},
|
||||
source_item_id: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
description: "Project item name or node ID used by every source range",
|
||||
},
|
||||
target_name: {
|
||||
type: "string",
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
description: "Name for the cloned destination sequence",
|
||||
},
|
||||
ranges: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 64,
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
in_seconds: { type: "number", minimum: 0 },
|
||||
out_seconds: { type: "number", minimum: 0 },
|
||||
destination_seconds: { type: "number", minimum: 0 },
|
||||
label: { type: "string", maxLength: 255 },
|
||||
},
|
||||
required: ["in_seconds", "out_seconds", "destination_seconds"],
|
||||
},
|
||||
},
|
||||
confirm_prototype: {
|
||||
type: "boolean",
|
||||
description: "Must be true because this creates and mutates a Premiere sequence",
|
||||
},
|
||||
},
|
||||
required: ["source_sequence_id", "source_item_id", "target_name", "ranges", "confirm_prototype"],
|
||||
},
|
||||
handler: async (args: {
|
||||
source_sequence_id: string;
|
||||
source_item_id: string;
|
||||
target_name: string;
|
||||
ranges: Array<{
|
||||
in_seconds: number;
|
||||
out_seconds: number;
|
||||
destination_seconds: number;
|
||||
label?: string;
|
||||
}>;
|
||||
confirm_prototype: boolean;
|
||||
}) => {
|
||||
if (args.confirm_prototype !== true) {
|
||||
return { success: false, error: "confirm_prototype must be true for the experimental sequence assembly." };
|
||||
}
|
||||
if (!Array.isArray(args.ranges) || args.ranges.length === 0 || args.ranges.length > 64) {
|
||||
return { success: false, error: "ranges must contain between 1 and 64 source ranges." };
|
||||
}
|
||||
for (const [index, range] of args.ranges.entries()) {
|
||||
if (!Number.isFinite(range.in_seconds) || !Number.isFinite(range.out_seconds) || !Number.isFinite(range.destination_seconds) ||
|
||||
range.in_seconds < 0 || range.out_seconds <= range.in_seconds || range.destination_seconds < 0) {
|
||||
return { success: false, error: `ranges[${index}] has invalid source or destination timing.` };
|
||||
}
|
||||
}
|
||||
|
||||
const sourceSequenceId = escapeForExtendScript(args.source_sequence_id);
|
||||
const sourceItemId = escapeForExtendScript(args.source_item_id);
|
||||
const targetName = escapeForExtendScript(args.target_name);
|
||||
const ranges = args.ranges.map((range) => ({
|
||||
inSeconds: range.in_seconds,
|
||||
outSeconds: range.out_seconds,
|
||||
destinationSeconds: range.destination_seconds,
|
||||
label: range.label || "",
|
||||
}));
|
||||
const rangeJson = JSON.stringify(ranges);
|
||||
|
||||
const script = buildToolScript(`
|
||||
var sourceSequence = __findSequence("${sourceSequenceId}");
|
||||
if (!sourceSequence) return __error("Source sequence not found; no prototype sequence was created.");
|
||||
var item = __findProjectItem("${sourceItemId}");
|
||||
if (!item) return __error("Source project item not found; no prototype sequence was created.");
|
||||
|
||||
var beforeIds = {};
|
||||
var sequenceIndex;
|
||||
for (sequenceIndex = 0; sequenceIndex < app.project.sequences.numSequences; sequenceIndex++) {
|
||||
beforeIds[String(app.project.sequences[sequenceIndex].sequenceID)] = true;
|
||||
}
|
||||
sourceSequence.clone();
|
||||
|
||||
var destinationSequence = null;
|
||||
for (sequenceIndex = 0; sequenceIndex < app.project.sequences.numSequences; sequenceIndex++) {
|
||||
var candidate = app.project.sequences[sequenceIndex];
|
||||
if (!beforeIds[String(candidate.sequenceID)]) {
|
||||
destinationSequence = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!destinationSequence) return __error("Premiere did not expose the cloned destination sequence.");
|
||||
destinationSequence.name = "${targetName}";
|
||||
|
||||
var trackGroup;
|
||||
var trackIndex;
|
||||
var clipIndex;
|
||||
var trackGroups = [destinationSequence.videoTracks, destinationSequence.audioTracks];
|
||||
for (trackGroup = 0; trackGroup < trackGroups.length; trackGroup++) {
|
||||
for (trackIndex = 0; trackIndex < trackGroups[trackGroup].numTracks; trackIndex++) {
|
||||
var track = trackGroups[trackGroup][trackIndex];
|
||||
for (clipIndex = track.clips.numItems - 1; clipIndex >= 0; clipIndex--) {
|
||||
track.clips[clipIndex].remove(false, false);
|
||||
}
|
||||
if (track.clips.numItems !== 0) return __error("Could not clear the cloned destination sequence.");
|
||||
}
|
||||
}
|
||||
if (!destinationSequence.videoTracks.numTracks || !destinationSequence.audioTracks.numTracks) {
|
||||
return __error("The cloned destination sequence must have at least one video and one audio track.");
|
||||
}
|
||||
|
||||
var ranges = ${rangeJson};
|
||||
var oldIn = item.getInPoint(4);
|
||||
var oldOut = item.getOutPoint(4);
|
||||
var oldInTicks = oldIn ? oldIn.ticks : null;
|
||||
var oldOutTicks = oldOut ? oldOut.ticks : null;
|
||||
var placements = [];
|
||||
var insertionError = null;
|
||||
try {
|
||||
for (var rangeIndex = 0; rangeIndex < ranges.length; rangeIndex++) {
|
||||
var range = ranges[rangeIndex];
|
||||
if (range.outSeconds <= range.inSeconds) return __error("A prototype source range has non-positive duration.");
|
||||
item.setInPoint(__secondsToTicks(range.inSeconds).toString(), 4);
|
||||
item.setOutPoint(__secondsToTicks(range.outSeconds).toString(), 4);
|
||||
destinationSequence.insertClip(item, __secondsToTicks(range.destinationSeconds).toString(), 0, 0);
|
||||
placements.push({
|
||||
label: range.label,
|
||||
sourceInSeconds: range.inSeconds,
|
||||
sourceOutSeconds: range.outSeconds,
|
||||
destinationSeconds: range.destinationSeconds,
|
||||
durationSeconds: range.outSeconds - range.inSeconds
|
||||
});
|
||||
}
|
||||
} catch (insertError) {
|
||||
insertionError = insertError.toString();
|
||||
} finally {
|
||||
try {
|
||||
if (oldInTicks !== null) item.setInPoint(String(oldInTicks), 4); else item.clearInPoint();
|
||||
if (oldOutTicks !== null) item.setOutPoint(String(oldOutTicks), 4); else item.clearOutPoint();
|
||||
} catch (restoreError) {
|
||||
return __error("The source item's in/out could not be restored: " + restoreError.toString());
|
||||
}
|
||||
}
|
||||
if (insertionError !== null) return __error("Premiere rejected a prototype source-range insertion: " + insertionError);
|
||||
|
||||
var videoCount = destinationSequence.videoTracks[0].clips.numItems;
|
||||
var audioCount = destinationSequence.audioTracks[0].clips.numItems;
|
||||
if (videoCount !== ranges.length || audioCount !== ranges.length) {
|
||||
return __error("The prototype destination did not receive exactly one video and one audio clip per range.");
|
||||
}
|
||||
return __result({
|
||||
prototype: true,
|
||||
verified: true,
|
||||
sourceSequenceId: String(sourceSequence.sequenceID),
|
||||
destinationSequenceId: String(destinationSequence.sequenceID),
|
||||
destinationSequenceName: destinationSequence.name,
|
||||
videoClipCount: videoCount,
|
||||
audioClipCount: audioCount,
|
||||
placements: placements,
|
||||
note: "The source sequence was not edited. Review the cloned sequence manually before keeping it."
|
||||
});
|
||||
`);
|
||||
return sendCommand(script, bridgeOptions);
|
||||
},
|
||||
},
|
||||
|
||||
add_to_timeline: {
|
||||
description:
|
||||
"Insert a project item at a timeline position and verify Premiere added no unexpected same-track fragments.",
|
||||
|
||||
Reference in New Issue
Block a user