feat: criado monitoramento contínuo de trilhas com varredura recur

- criado monitoramento contínuo de trilhas com varredura recursiva e baixa prioridade
- gerado e validado plano editorial revisável do depoimento da Erika sem aplicação na timeline
- adicionada leitura e comparação estrutural da timeline após aplicar planos de edição
- limitadas as classificações de trilhas às duas maiores probabilidades de cada categoria
- limpo o catálogo musical para reiniciar as análises sem remover dados de vídeo

Resumo:
- 34 arquivos alterados
- 6 novos
- 28 modificados
- 0 removidos

 28 files changed, 833 insertions(+), 75 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/engine/analisar_trilhas.py
  - code/engine/aplicar_plano_de_edicao.py
  - code/engine/consultar_detalhes_de_trilha.py
  - code/engine/editor/__init__.py
  - code/engine/editor/aplicador_de_plano_de_edicao.py
  - code/engine/editor/backup_de_sequencia.py
  - code/engine/editor/escrita/escrita_no_editor.py
  - code/engine/editor/modelos.py
  - code/engine/editor/validacao_semantica.py
  - code/engine/integracoes/audio/classificador_de_musica_essentia.py
  - code/engine/integracoes/audio/modelos_de_analise_musical.py
  - code/engine/integracoes/audio/provider_de_analise_musical_essentia.py
  - code/engine/persistencia/esquema.py
  - code/engine/persistencia/repositorio_de_trilhas.py
  - code/engine/testes/duplos_de_premiere.py
  - code/engine/testes/test_aplicador_de_plano_de_edicao.py
  - code/engine/testes/test_aplicar_plano_de_edicao.py
  - code/engine/testes/test_escrita_no_editor.py
  - code/engine/testes/test_leitor_de_plano_de_edicao.py
  - code/engine/testes/test_validador_semantico_de_plano.py
  - code/plugins/premiere-pro/skills/editar-por-voz/SKILL.md
  - code/plugins/premiere-pro/skills/editar-por-voz/criterios/08-formato-de-saida.md
  - code/plugins/premiere-pro/skills/editar-por-voz/criterios/10-revisao-humana.md
  - code/src/tools/timeline.ts
  - code/tests/tools/tool-modules.test.ts
  - .jhonny/planos/
  - code/.jhonny/analises.db-shm
  - code/.jhonny/analises.db-wal
  - code/engine/editor/verificador_da_aplicacao.py
  - code/engine/testes/test_verificador_da_aplicacao.py
  - code/plugins/premiere-pro/skills/editar-por-voz/criterios/11-gancho.md
This commit is contained in:
João Henrique
2026-09-10 15:09:27 -04:00
parent da400de7d3
commit 2c9bdf2ff0
34 changed files with 1179 additions and 75 deletions
+170
View File
@@ -194,6 +194,176 @@ export function getTimelineTools(bridgeOptions: BridgeOptions) {
},
},
duplicate_linked_range_to_start: {
description:
"Duplicate one isolated, normal-speed linked video/audio range at sequence start with a verified ripple insert while preserving the original occurrence.",
parameters: {
type: "object" as const,
properties: {
node_ids: {
type: "array",
items: { type: "string" },
minItems: 2,
maxItems: 2,
description: "IDs of the isolated video and audio clips that form the range.",
},
},
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 must contain exactly two non-empty clip IDs." };
}
const ids = args.node_ids.map(escapeForExtendScript);
const script = buildToolScript(`
var first = __findClip("${ids[0]}");
var second = __findClip("${ids[1]}");
if (!first || !second) return __error("One of the requested clips was not found; nothing was duplicated.");
if (first.trackType === second.trackType) return __error("The range must contain one video clip and one audio clip.");
var video = first.trackType === "video" ? first : second;
var audio = first.trackType === "audio" ? first : second;
var seq = app.project.activeSequence;
if (!seq) return __error("No active sequence; nothing was duplicated.");
var frameTicks = parseFloat(seq.timebase);
if (!frameTicks || isNaN(frameTicks)) frameTicks = TICKS_PER_SECOND / 24;
var beforeEndTicks = parseFloat(seq.end.ticks);
var videoStart = parseFloat(video.clip.start.ticks);
var audioStart = parseFloat(audio.clip.start.ticks);
var videoEnd = parseFloat(video.clip.end.ticks);
var audioEnd = parseFloat(audio.clip.end.ticks);
if (Math.abs(videoStart - audioStart) > frameTicks || Math.abs(videoEnd - audioEnd) > frameTicks) {
return __error("The video and audio clips do not occupy the same range; nothing was duplicated.");
}
if (videoStart <= frameTicks) return __error("The requested range is already at sequence start; nothing was duplicated.");
if (!video.clip.projectItem || !audio.clip.projectItem || video.clip.projectItem.nodeId !== audio.clip.projectItem.nodeId) {
return __error("The video and audio clips do not share one source project item; nothing was duplicated.");
}
var videoSpeed = 100;
var audioSpeed = 100;
var videoReversed = false;
var audioReversed = false;
try { videoSpeed = Number(video.clip.getSpeed()); } catch (e) {}
try { audioSpeed = Number(audio.clip.getSpeed()); } catch (e) {}
try { videoReversed = video.clip.isSpeedReversed() == 1; } catch (e) {}
try { audioReversed = audio.clip.isSpeedReversed() == 1; } catch (e) {}
if (Math.abs(videoSpeed - 100) > 0.001 || Math.abs(audioSpeed - 100) > 0.001 || videoReversed || audioReversed) {
return __error("Retimed or reversed clips cannot be duplicated by this guarded operation.");
}
var g;
var t;
var groups = [seq.videoTracks, seq.audioTracks];
for (g = 0; g < groups.length; g++) {
for (t = 0; t < groups[g].numTracks; t++) {
var isTarget = (g === 0 && t === video.trackIndex) || (g === 1 && t === audio.trackIndex);
if (!isTarget && groups[g][t].clips.numItems > 0) {
return __error("The guarded hook only supports a simple sequence with content on one video and one audio track.");
}
}
}
if (seq.videoTracks[video.trackIndex].clips.numItems < 1 || seq.audioTracks[audio.trackIndex].clips.numItems < 1) {
return __error("The target tracks are empty; nothing was duplicated.");
}
if ((seq.videoTracks[video.trackIndex].isLocked && seq.videoTracks[video.trackIndex].isLocked()) ||
(seq.audioTracks[audio.trackIndex].isLocked && seq.audioTracks[audio.trackIndex].isLocked())) {
return __error("A target track is locked; nothing was duplicated.");
}
if (parseFloat(seq.videoTracks[video.trackIndex].clips[0].start.ticks) > frameTicks ||
parseFloat(seq.audioTracks[audio.trackIndex].clips[0].start.ticks) > frameTicks) {
return __error("The guarded hook requires both target tracks to begin at sequence time zero.");
}
var item = video.clip.projectItem;
var sourceInTicks = parseFloat(video.clip.inPoint.ticks);
var sourceOutTicks = parseFloat(video.clip.outPoint.ticks);
if (Math.abs(sourceInTicks - parseFloat(audio.clip.inPoint.ticks)) > frameTicks ||
Math.abs(sourceOutTicks - parseFloat(audio.clip.outPoint.ticks)) > frameTicks) {
return __error("The linked clips do not share matching source in/out points; nothing was duplicated.");
}
var durationTicks = videoEnd - videoStart;
if (durationTicks <= frameTicks) return __error("The requested range is too short to duplicate safely.");
var oldIn = item.getInPoint(4);
var oldOut = item.getOutPoint(4);
var oldInTicks = oldIn ? oldIn.ticks : null;
var oldOutTicks = oldOut ? oldOut.ticks : null;
var insertionError = null;
try {
item.setInPoint(String(sourceInTicks), 4);
item.setOutPoint(String(sourceOutTicks), 4);
seq.insertClip(item, "0", video.trackIndex, audio.trackIndex);
} 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 range could not be restored after insertion: " + restoreError.toString());
}
}
if (insertionError !== null) return __error("Premiere could not insert the range at sequence start: " + insertionError);
var insertedVideo = null;
var insertedAudio = null;
var vc;
var ac;
for (vc = 0; vc < seq.videoTracks[video.trackIndex].clips.numItems; vc++) {
var candidateVideo = seq.videoTracks[video.trackIndex].clips[vc];
if (candidateVideo.nodeId !== "${ids[0]}" && candidateVideo.nodeId !== "${ids[1]}" &&
candidateVideo.projectItem && candidateVideo.projectItem.nodeId === item.nodeId &&
Math.abs(parseFloat(candidateVideo.start.ticks)) <= frameTicks) insertedVideo = candidateVideo;
}
for (ac = 0; ac < seq.audioTracks[audio.trackIndex].clips.numItems; ac++) {
var candidateAudio = seq.audioTracks[audio.trackIndex].clips[ac];
if (candidateAudio.nodeId !== "${ids[0]}" && candidateAudio.nodeId !== "${ids[1]}" &&
candidateAudio.projectItem && candidateAudio.projectItem.nodeId === item.nodeId &&
Math.abs(parseFloat(candidateAudio.start.ticks)) <= frameTicks) insertedAudio = candidateAudio;
}
if (!insertedVideo || !insertedAudio) {
return __error("Premiere inserted content but the new linked range could not be verified at sequence start. Restore the automatic sequence backup.");
}
if (Math.abs((parseFloat(insertedVideo.end.ticks) - parseFloat(insertedVideo.start.ticks)) - durationTicks) > frameTicks ||
Math.abs((parseFloat(insertedAudio.end.ticks) - parseFloat(insertedAudio.start.ticks)) - durationTicks) > frameTicks) {
return __error("The inserted range has an unexpected duration. Restore the automatic sequence backup.");
}
var insertedVideoId = String(insertedVideo.nodeId);
var insertedAudioId = String(insertedAudio.nodeId);
var preservedFirst = __findClip("${ids[0]}");
var preservedSecond = __findClip("${ids[1]}");
if (!preservedFirst || !preservedSecond ||
Math.abs(parseFloat(preservedFirst.clip.start.ticks) - (videoStart + durationTicks)) > frameTicks ||
Math.abs(parseFloat(preservedSecond.clip.start.ticks) - (audioStart + durationTicks)) > frameTicks) {
return __error("The original range was not preserved after the ripple insert. Restore the automatic sequence backup.");
}
var hookVideo = __findClip(insertedVideoId);
var hookAudio = __findClip(insertedAudioId);
if (!hookVideo || !hookAudio ||
Math.abs(parseFloat(hookVideo.clip.start.ticks)) > frameTicks ||
Math.abs(parseFloat(hookAudio.clip.start.ticks)) > frameTicks) {
return __error("The duplicated hook was not preserved at sequence start. Restore the automatic sequence backup.");
}
var afterEndTicks = parseFloat(seq.end.ticks);
if (Math.abs(afterEndTicks - (beforeEndTicks + durationTicks)) > frameTicks * 2) {
return __error("The sequence duration did not grow by the hook duration. Restore the automatic sequence backup.");
}
return __result({
duplicated: true,
verified: true,
targetSeconds: 0,
durationSeconds: __ticksToSeconds(durationTicks),
insertedNodeIds: [String(hookVideo.clip.nodeId), String(hookAudio.clip.nodeId)],
originalNodeIdsPreserved: ["${ids[0]}", "${ids[1]}"],
clipEffectsPreserved: false
});
`);
return sendCommand(script, bridgeOptions);
},
},
move_clip: {
description: "Move a clip to a new position on the timeline",
parameters: {