feat: Alinhado o aplicador Python de plano de edição (aplicador_de
- Alinhado o aplicador Python de plano de edição (aplicador_de_plano_de_edicao.py) com o script apply-editorial-actions.mjs de referência: no-op de split restrito aos padrões reais do bridge, pausa entre chamadas ao MCP, tolerância de borda de corte usa a posição real em vez de rejeitar desvio; adicionado backup automático da sequência (duplicate_sequence) e documentado o comando python code/engine/aplicar_plano_de_edicao.py como via alternativa ao painel CEP. Resumo: - 18 arquivos alterados - 5 novos - 13 modificados - 0 removidos 13 files changed, 704 insertions(+), 133 deletions(-) Arquivos: - .jhonny/analises.db - CLAUDE.md - code/engine/aplicar_plano_de_edicao.py - code/engine/editor/__init__.py - code/engine/editor/aplicador_de_plano_de_edicao.py - code/engine/editor/escrita/escrita_no_editor.py - code/engine/integracoes/premiere/__init__.py - code/engine/integracoes/premiere/cliente_mcp.py - code/engine/testes/duplos_de_premiere.py - code/engine/testes/test_aplicador_de_plano_de_edicao.py - code/scripts/apply-editorial-actions.mjs - code/src/tools/discovery.ts - code/src/tools/timeline.ts - code/engine/editor/backup_de_sequencia.py - code/engine/integracoes/premiere/cliente_mcp_com_pausa.py - code/engine/testes/test_backup_de_sequencia.py - code/engine/testes/test_cliente_mcp_com_pausa.py - code/engine/testes/test_escrita_no_editor.py
This commit is contained in:
@@ -166,7 +166,16 @@ export function getDiscoveryTools(bridgeOptions: BridgeOptions) {
|
||||
name: clip.name,
|
||||
start: __ticksToSeconds(clip.start.ticks),
|
||||
end: __ticksToSeconds(clip.end.ticks),
|
||||
duration: __ticksToSeconds(clip.duration.ticks)
|
||||
inPoint: __ticksToSeconds(clip.inPoint.ticks),
|
||||
outPoint: __ticksToSeconds(clip.outPoint.ticks),
|
||||
duration: __ticksToSeconds(clip.duration.ticks),
|
||||
sourceFile: clip.projectItem && clip.projectItem.getMediaPath
|
||||
? String(clip.projectItem.getMediaPath() || "")
|
||||
: "",
|
||||
sourceProjectItemId: clip.projectItem ? String(clip.projectItem.nodeId || "") : "",
|
||||
sourceOffline: clip.projectItem && clip.projectItem.isOffline
|
||||
? !!clip.projectItem.isOffline()
|
||||
: false
|
||||
});
|
||||
}
|
||||
audioTracks.push({
|
||||
|
||||
+124
-46
@@ -418,7 +418,22 @@ export function getTimelineTools(bridgeOptions: BridgeOptions) {
|
||||
return __error("Refusing trim before mutation: " + beforeKeyframes.outside.length + " effect keyframe(s) would remain outside the visible clip. Use keyframe_policy: preserve only if retaining those keyframes is intentional, or adjust them explicitly with the keyframe tools.");
|
||||
}
|
||||
|
||||
${args.new_in_seconds !== undefined ? `clip.inPoint = __secondsToTicks(${args.new_in_seconds}).toString();` : "clip.outPoint = __secondsToTicks(" + args.new_out_seconds + ").toString();"}
|
||||
// Set the source point AND the corresponding timeline boundary
|
||||
// together, computed from the same before-snapshot, instead of
|
||||
// setting only the source point and trusting Premiere to move the
|
||||
// timeline boundary on its own: that auto-derivation is exactly
|
||||
// what silently no-op'd on the timeline side while still moving
|
||||
// the source point on a real Premiere Pro 26.x install, leaving a
|
||||
// clip whose visible duration no longer matched its source range.
|
||||
${args.new_in_seconds !== undefined ? `
|
||||
var expectedStartOnWrite = before.start + (${args.new_in_seconds} - before.inPoint);
|
||||
clip.start = __secondsToTicks(expectedStartOnWrite).toString();
|
||||
clip.inPoint = __secondsToTicks(${args.new_in_seconds}).toString();
|
||||
` : `
|
||||
var expectedEndOnWrite = before.end + (${args.new_out_seconds} - before.outPoint);
|
||||
clip.end = __secondsToTicks(expectedEndOnWrite).toString();
|
||||
clip.outPoint = __secondsToTicks(${args.new_out_seconds}).toString();
|
||||
`}
|
||||
|
||||
// Re-find the TrackItem after the write. Premiere can replace stale
|
||||
// DOM references during an edit, especially for audio clips.
|
||||
@@ -586,10 +601,50 @@ export function getTimelineTools(bridgeOptions: BridgeOptions) {
|
||||
if (!__razorFps || !isFinite(__razorFps) || __razorFps < 1) __razorFps = 30;
|
||||
var __razorFrames = Math.round(cutTicks / frameTicks);
|
||||
function __pad2(n) { return n < 10 ? "0" + n : "" + n; }
|
||||
var __razorTc = __pad2(Math.floor(__razorFrames / (__razorFps * 3600))) + ":" +
|
||||
__pad2(Math.floor((__razorFrames % (__razorFps * 3600)) / (__razorFps * 60))) + ":" +
|
||||
__pad2(Math.floor((__razorFrames % (__razorFps * 60)) / __razorFps)) + ":" +
|
||||
__pad2(__razorFrames % __razorFps);
|
||||
|
||||
// A sequence whose display format is drop-frame timecode (videoDisplayFormat
|
||||
// 102 for 29.97 or 106 for 59.94) numbers frames as SMPTE drop-frame: 2 frame
|
||||
// NUMBERS are skipped at the start of every minute except every 10th. razor()
|
||||
// parses its timecode string using that same numbering, so handing it a plain
|
||||
// non-drop-frame HH:MM:SS:FF built from the raw frame count lands the cut at
|
||||
// the wrong absolute time, worse the further into the sequence the cut is —
|
||||
// exactly the growing drift this tool used to report as an unexplained no-op
|
||||
// on some cuts. __razorFrames is the true physical frame count from ticks, so
|
||||
// it must be converted to the DROP-FRAME-numbered timecode when that display
|
||||
// format is active, not just formatted with a rounded fps.
|
||||
var __isDropFrame = false;
|
||||
try {
|
||||
// getSettings() existe na sequência do DOM, não na do QE: ler pelo
|
||||
// objeto errado lança, cai no catch e desliga o drop-frame em
|
||||
// silêncio, mantendo o desvio que esta correção existe para tirar.
|
||||
var __seqSettings = domSequence.getSettings ? domSequence.getSettings() : null;
|
||||
var __displayFormat = __seqSettings ? __seqSettings.videoDisplayFormat : null;
|
||||
__isDropFrame = __displayFormat === 102 || __displayFormat === 106;
|
||||
} catch (__dfErr) { __isDropFrame = false; }
|
||||
|
||||
var __razorTc;
|
||||
if (__isDropFrame) {
|
||||
var __dropPerMin = Math.round(__razorFps * 2 / 30);
|
||||
var __framesPerMinuteNominal = __razorFps * 60;
|
||||
var __framesPer10Minutes = __framesPerMinuteNominal * 10 - __dropPerMin * 9;
|
||||
var __framesPerMinuteDropped = __framesPerMinuteNominal - __dropPerMin;
|
||||
var __d = Math.floor(__razorFrames / __framesPer10Minutes);
|
||||
var __m = __razorFrames % __framesPer10Minutes;
|
||||
var __labelFrames = __razorFrames + __dropPerMin * 9 * __d;
|
||||
if (__m > __dropPerMin) {
|
||||
__labelFrames += __dropPerMin * Math.floor((__m - __dropPerMin) / __framesPerMinuteDropped);
|
||||
}
|
||||
var __dfFrames = __labelFrames % __razorFps;
|
||||
var __dfSeconds = Math.floor(__labelFrames / __razorFps) % 60;
|
||||
var __dfMinutes = Math.floor(__labelFrames / (__razorFps * 60)) % 60;
|
||||
var __dfHours = Math.floor(__labelFrames / (__razorFps * 3600)) % 24;
|
||||
__razorTc = __pad2(__dfHours) + ":" + __pad2(__dfMinutes) + ":" + __pad2(__dfSeconds) + ";" + __pad2(__dfFrames);
|
||||
} else {
|
||||
__razorTc = __pad2(Math.floor(__razorFrames / (__razorFps * 3600))) + ":" +
|
||||
__pad2(Math.floor((__razorFrames % (__razorFps * 3600)) / (__razorFps * 60))) + ":" +
|
||||
__pad2(Math.floor((__razorFrames % (__razorFps * 60)) / __razorFps)) + ":" +
|
||||
__pad2(__razorFrames % __razorFps);
|
||||
}
|
||||
|
||||
try {
|
||||
track.razor(__razorTc);
|
||||
@@ -750,55 +805,78 @@ export function getTimelineTools(bridgeOptions: BridgeOptions) {
|
||||
const script = buildToolScript(`
|
||||
var result = __findClip("${escapeForExtendScript(args.node_id)}");
|
||||
if (!result) return __error("Clip not found: ${escapeForExtendScript(args.node_id)}");
|
||||
|
||||
|
||||
var clip = result.clip;
|
||||
var changes = {};
|
||||
|
||||
var missing = [];
|
||||
|
||||
// Property lookup uses the fixed index of Premiere's built-in Motion
|
||||
// and Opacity effects, not displayName: displayName is localized
|
||||
// (e.g. "Escala"/"Posição"/"Rotação" in pt_BR Premiere), so a plain
|
||||
// string match against the English name silently matches nothing
|
||||
// and no-ops on non-English installs. Index 0 of Opacity is its
|
||||
// only property; Motion is fixed at 0=Position, 1=Scale, 4=Rotation
|
||||
// across locales — confirmed against a pt_BR install.
|
||||
function __motionComponent() {
|
||||
for (var i = 0; i < clip.components.numItems; i++) {
|
||||
if (clip.components[i].matchName === "AE.ADBE Motion") return clip.components[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function __opacityComponent() {
|
||||
for (var i = 0; i < clip.components.numItems; i++) {
|
||||
if (clip.components[i].matchName === "AE.ADBE Opacity") return clip.components[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
${args.opacity !== undefined ? `
|
||||
// Set opacity via Motion component
|
||||
for (var i = 0; i < clip.components.numItems; i++) {
|
||||
var comp = clip.components[i];
|
||||
if (comp.matchName === "AE.ADBE Opacity" || comp.displayName === "Opacity") {
|
||||
for (var p = 0; p < comp.properties.numItems; p++) {
|
||||
if (comp.properties[p].displayName === "Opacity") {
|
||||
comp.properties[p].setValue(${args.opacity}, true);
|
||||
changes.opacity = ${args.opacity};
|
||||
}
|
||||
}
|
||||
}
|
||||
var opacityComp = __opacityComponent();
|
||||
if (!opacityComp || opacityComp.properties.numItems < 1) {
|
||||
missing.push("opacity: componente Opacity não encontrado no clipe");
|
||||
} else {
|
||||
opacityComp.properties[0].setValue(${args.opacity}, true);
|
||||
changes.opacity = ${args.opacity};
|
||||
}
|
||||
` : ""}
|
||||
|
||||
|
||||
${args.scale !== undefined || args.position_x !== undefined || args.position_y !== undefined || args.rotation !== undefined ? `
|
||||
for (var i = 0; i < clip.components.numItems; i++) {
|
||||
var comp = clip.components[i];
|
||||
if (comp.matchName === "AE.ADBE Motion" || comp.displayName === "Motion") {
|
||||
for (var p = 0; p < comp.properties.numItems; p++) {
|
||||
var prop = comp.properties[p];
|
||||
${args.scale !== undefined ? `
|
||||
if (prop.displayName === "Scale") {
|
||||
prop.setValue(${args.scale}, true);
|
||||
changes.scale = ${args.scale};
|
||||
}` : ""}
|
||||
${args.position_x !== undefined || args.position_y !== undefined ? `
|
||||
if (prop.displayName === "Position") {
|
||||
var posVal = prop.getValue();
|
||||
var px = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[0] : 0;
|
||||
var py = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[1] : 0;
|
||||
${args.position_x !== undefined ? `px = ${args.position_x}; changes.position_x = ${args.position_x};` : ""}
|
||||
${args.position_y !== undefined ? `py = ${args.position_y}; changes.position_y = ${args.position_y};` : ""}
|
||||
prop.setValue([px, py], true);
|
||||
}` : ""}
|
||||
${args.rotation !== undefined ? `
|
||||
if (prop.displayName === "Rotation") {
|
||||
prop.setValue(${args.rotation}, true);
|
||||
changes.rotation = ${args.rotation};
|
||||
}` : ""}
|
||||
}
|
||||
}
|
||||
var motionComp = __motionComponent();
|
||||
if (!motionComp) {
|
||||
missing.push("componente Motion não encontrado no clipe");
|
||||
} else {
|
||||
${args.scale !== undefined ? `
|
||||
if (motionComp.properties.numItems < 2) {
|
||||
missing.push("scale: propriedade Escala (índice 1) não encontrada");
|
||||
} else {
|
||||
motionComp.properties[1].setValue(${args.scale}, true);
|
||||
changes.scale = ${args.scale};
|
||||
}` : ""}
|
||||
${args.position_x !== undefined || args.position_y !== undefined ? `
|
||||
if (motionComp.properties.numItems < 1) {
|
||||
missing.push("position: propriedade Posição (índice 0) não encontrada");
|
||||
} else {
|
||||
var posProp = motionComp.properties[0];
|
||||
var posVal = posProp.getValue();
|
||||
var px = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[0] : 0;
|
||||
var py = posVal && typeof posVal === "object" && posVal.length >= 2 ? posVal[1] : 0;
|
||||
${args.position_x !== undefined ? `px = ${args.position_x}; changes.position_x = ${args.position_x};` : ""}
|
||||
${args.position_y !== undefined ? `py = ${args.position_y}; changes.position_y = ${args.position_y};` : ""}
|
||||
posProp.setValue([px, py], true);
|
||||
}` : ""}
|
||||
${args.rotation !== undefined ? `
|
||||
if (motionComp.properties.numItems < 5) {
|
||||
missing.push("rotation: propriedade Rotação (índice 4) não encontrada");
|
||||
} else {
|
||||
motionComp.properties[4].setValue(${args.rotation}, true);
|
||||
changes.rotation = ${args.rotation};
|
||||
}` : ""}
|
||||
}
|
||||
` : ""}
|
||||
|
||||
|
||||
if (missing.length) {
|
||||
return __error("Não foi possível aplicar todas as propriedades pedidas: " + missing.join("; ") + ". Nenhuma mudança parcial foi reportada como sucesso.");
|
||||
}
|
||||
return __result({ updated: true, clipName: clip.name, changes: changes });
|
||||
`);
|
||||
return sendCommand(script, bridgeOptions);
|
||||
|
||||
Reference in New Issue
Block a user