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:
@@ -11,8 +11,12 @@
|
||||
* "coordenada pós-corte" (ver adjustTimeForCuts) antes de procurar o clipe.
|
||||
*
|
||||
* Limitações conhecidas:
|
||||
* - "zoom" aplica um scale estático (set_clip_scale) no clipe do intervalo, sem rampa
|
||||
* de entrada/saída. Suficiente para um punch-in simples; não é um Ken Burns animado.
|
||||
* - "zoom" cria uma camada de ajuste (adjustment layer) numa track de vídeo dedicada,
|
||||
* posicionada e recortada para durar exatamente o intervalo da action, com o Scale
|
||||
* do próprio Motion da camada de ajuste ajustado — não altera o clipe original.
|
||||
* É um scale estático (sem rampa de entrada/saída); suficiente para um punch-in
|
||||
* simples, não é um Ken Burns animado. A track de ajuste é criada uma vez (no
|
||||
* topo da pilha de vídeo) e reaproveitada por todas as actions "zoom" do plano.
|
||||
* - "text" não tem API de scripting suportada no Premiere para criar clipe de texto
|
||||
* a partir de string crua (add_text_overlay do MCP já retorna erro por design).
|
||||
* Em vez de falhar o plano inteiro, cada action "text" vira um marker de sequência
|
||||
@@ -28,6 +32,9 @@ import { getDiscoveryTools } from "../dist/tools/discovery.js";
|
||||
import { getProjectTools } from "../dist/tools/project.js";
|
||||
import { getMarkerTools } from "../dist/tools/markers.js";
|
||||
import { getTrackTargetingTools } from "../dist/tools/track-targeting.js";
|
||||
import { getTrackTools } from "../dist/tools/tracks.js";
|
||||
import { getPlayheadTools } from "../dist/tools/playhead.js";
|
||||
import { getUtilityTools } from "../dist/tools/utility.js";
|
||||
|
||||
// Progresso para a barra do painel CEP: linhas @@PROGRESS em stderr, que o painel
|
||||
// intercepta em vez de despejar no log. Fases A/B/C viram uma barra única.
|
||||
@@ -121,6 +128,9 @@ async function main() {
|
||||
const project = getProjectTools(bridgeOptions);
|
||||
const markers = getMarkerTools(bridgeOptions);
|
||||
const trackTargeting = getTrackTargetingTools(bridgeOptions);
|
||||
const tracks = getTrackTools(bridgeOptions);
|
||||
const playhead = getPlayheadTools(bridgeOptions);
|
||||
const utility = getUtilityTools(bridgeOptions);
|
||||
|
||||
console.error(
|
||||
`[apply-editorial-actions] sequência="${args.sequence}" cuts=${cuts.length} outras=${others.length} dryRun=${args.dryRun}`
|
||||
@@ -236,6 +246,9 @@ async function main() {
|
||||
reportProgress("Aplicando zooms, textos e marcadores", phaseCBase, `0 de ${others.length} ações`);
|
||||
let applied = 0;
|
||||
const warnings = [];
|
||||
// Criada sob demanda, na primeira action "zoom" do plano — planos sem zoom não
|
||||
// ganham uma track extra à toa.
|
||||
let adjustmentTrackIndex = null;
|
||||
for (const action of others) {
|
||||
const adjStart = adjustTimeForCuts(action.start, sortedCutsForAdjust);
|
||||
const adjEnd = adjustTimeForCuts(action.end, sortedCutsForAdjust);
|
||||
@@ -258,13 +271,60 @@ async function main() {
|
||||
warnings.push(`zoom @${action.start} sem params.scale numérico — ignorado.`);
|
||||
continue;
|
||||
}
|
||||
const videoClip = unwrap(
|
||||
await discovery.get_clip_at_position.handler({ time_seconds: mid, track_index: args.videoTrack, track_type: "video" }),
|
||||
`get_clip_at_position video @${mid}`
|
||||
);
|
||||
const duration = adjEnd - adjStart;
|
||||
if (duration <= 0) {
|
||||
warnings.push(`zoom @${action.start} com duração inválida (${duration}s) — ignorado.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// A camada de ajuste é criada uma vez, no topo da pilha de vídeo, e
|
||||
// reaproveitada por todas as actions "zoom" — não altera o clipe
|
||||
// original, então cortes/re-splits feitos na Fase A/B não a afetam.
|
||||
if (adjustmentTrackIndex === null) {
|
||||
const trackResult = unwrap(
|
||||
await tracks.add_track.handler({ track_type: "video", count: 1 }),
|
||||
"add_track (camada de ajuste para zoom)"
|
||||
);
|
||||
adjustmentTrackIndex = trackResult.totalTracks - 1;
|
||||
await sleep(BRIDGE_DELAY_MS);
|
||||
}
|
||||
|
||||
unwrap(
|
||||
await trackTargeting.set_clip_scale.handler({ node_id: videoClip.nodeId, scale: scale * 100 }),
|
||||
`set_clip_scale ${videoClip.nodeId}`
|
||||
await playhead.set_playhead_position.handler({ time_seconds: adjStart }),
|
||||
`set_playhead_position @${adjStart}`
|
||||
);
|
||||
await sleep(BRIDGE_DELAY_MS);
|
||||
|
||||
unwrap(
|
||||
await utility.add_adjustment_layer.handler({ track_index: adjustmentTrackIndex }),
|
||||
`add_adjustment_layer @${adjStart}`
|
||||
);
|
||||
await sleep(BRIDGE_DELAY_MS);
|
||||
|
||||
const adjClip = unwrap(
|
||||
await discovery.get_clip_at_position.handler({
|
||||
time_seconds: adjStart + 0.01,
|
||||
track_index: adjustmentTrackIndex,
|
||||
track_type: "video",
|
||||
}),
|
||||
`get_clip_at_position (camada de ajuste) @${adjStart}`
|
||||
);
|
||||
|
||||
unwrap(
|
||||
await timeline.trim_clip.handler({ node_id: adjClip.nodeId, new_out_seconds: duration }),
|
||||
`trim_clip (camada de ajuste) @${adjStart}`
|
||||
);
|
||||
await sleep(BRIDGE_DELAY_MS);
|
||||
|
||||
unwrap(
|
||||
await timeline.move_clip.handler({ node_id: adjClip.nodeId, new_start_seconds: adjStart }),
|
||||
`move_clip (camada de ajuste) @${adjStart}`
|
||||
);
|
||||
await sleep(BRIDGE_DELAY_MS);
|
||||
|
||||
unwrap(
|
||||
await trackTargeting.set_clip_scale.handler({ node_id: adjClip.nodeId, scale: scale * 100 }),
|
||||
`set_clip_scale (camada de ajuste) ${adjClip.nodeId}`
|
||||
);
|
||||
} else if (action.kind === "text") {
|
||||
// Sem API de scripting suportada para clipe de texto a partir de string crua
|
||||
|
||||
Reference in New Issue
Block a user