feat: alinhada a remoção da engine Python ao fluxo ripple do execu

- alinhada a remoção da engine Python ao fluxo ripple do executor CEP para cortar vídeo e áudio
- criada remoção atômica para manter vídeo e áudio vinculados sincronizados
- criada aba de configuração de legendas offline e inclusão das preferências no pacote para a IA
- criado gerador offline de blocos de legenda sincronizados com palavras da transcrição e cortes aprovados
- criada uma versão base portátil da skill edit-video-by-voice para testes em outros agentes de IA
- incorporado perfil editorial opcional à skill portátil para orientar decisões de edição

Resumo:
- 14 arquivos alterados
- 4 novos
- 10 modificados
- 0 removidos

 10 files changed, 278 insertions(+), 33 deletions(-)

Arquivos:
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/docs/supported-actions.md
  - code/engine/editor/aplicador_de_plano_de_edicao.py
  - code/engine/editor/escrita/escrita_no_editor.py
  - code/engine/testes/duplos_de_premiere.py
  - code/engine/testes/test_aplicador_de_plano_de_edicao.py
  - code/src/tools/timeline.ts
  - code/tests/tools/structural-verification.test.ts
  - code/tests/tools/tool-modules.test.ts
  - code/engine/arquitetura/legendas.md
  - code/engine/editor/legendas/
  - code/engine/testes/test_gerador_de_legendas.py
  - skills/
This commit is contained in:
João Henrique
2026-09-09 13:05:07 -04:00
parent f3e356340b
commit 1249b3aeb7
18 changed files with 730 additions and 33 deletions
+50
View File
@@ -144,6 +144,56 @@ export function getTimelineTools(bridgeOptions: BridgeOptions) {
},
},
remove_linked_from_timeline: {
description:
"Remove um par lógico de vídeo e áudio da timeline em uma única operação, fechando o espaço nos dois lados.",
parameters: {
type: "object" as const,
properties: {
node_ids: {
type: "array",
items: { type: "string" },
minItems: 2,
maxItems: 2,
description: "IDs dos dois clipes correspondentes de vídeo e áudio.",
},
},
required: ["node_ids"],
},
handler: async (args: { node_ids: string[] }) => {
if (!Array.isArray(args.node_ids) || args.node_ids.length !== 2 || args.node_ids.some((id) => typeof id !== "string" || !id)) {
return { success: false, error: "node_ids deve conter exatamente dois IDs de clipes." };
}
const ids = args.node_ids.map(escapeForExtendScript);
const script = buildToolScript(`
var primeiro = __findClip("${ids[0]}");
var segundo = __findClip("${ids[1]}");
if (!primeiro || !segundo) return __error("Um dos clipes do par não foi encontrado; nenhuma remoção foi confirmada.");
if (primeiro.trackType === segundo.trackType) return __error("O par precisa conter um clipe de vídeo e um de áudio.");
var primeiroInicio = parseFloat(primeiro.clip.start.ticks);
var segundoInicio = parseFloat(segundo.clip.start.ticks);
var primeiroFim = parseFloat(primeiro.clip.end.ticks);
var segundoFim = parseFloat(segundo.clip.end.ticks);
if (Math.abs(primeiroInicio - segundoInicio) > 1 || Math.abs(primeiroFim - segundoFim) > 1) {
return __error("Os clipes de vídeo e áudio não ocupam o mesmo intervalo; nenhuma remoção foi feita.");
}
// alignToVideo=false evita que a remoção do áudio seja reposicionada
// pelo estado anterior do vídeo. As duas remoções são feitas nesta
// mesma chamada, depois da validação do par.
primeiro.clip.remove(true, false);
segundo.clip.remove(true, false);
if (__findClip("${ids[0]}") || __findClip("${ids[1]}")) {
return __error("O Premiere não confirmou a remoção dos dois clipes vinculados.");
}
return __result({ removed: true, verified: true, nodeIds: ["${ids[0]}", "${ids[1]}"] });
`);
return sendCommand(script, bridgeOptions);
},
},
move_clip: {
description: "Move a clip to a new position on the timeline",
parameters: {