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:
João Henrique
2026-09-09 09:31:29 -04:00
parent 4308c72441
commit 69e931eb45
18 changed files with 1040 additions and 133 deletions
+10 -1
View File
@@ -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
View File
@@ -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);