feat: documentada a entrada universal da skill edit-video-by-voice

-documentada a entrada universal da skill edit-video-by-voice e reforçados os limites do contrato de ações 1.0
- corrigida a identificação da mídia na engine Python usando nome do clipe e nome-base de sourceFile

Resumo:
- 5 arquivos alterados
- 1 novos
- 4 modificados
- 0 removidos

 4 files changed, 54 insertions(+), 16 deletions(-)

Arquivos:
  - code/engine/editor/aplicador_de_plano_de_edicao.py
  - code/engine/testes/test_aplicador_de_plano_de_edicao.py
  - code/plugins/premiere-pro/skills/edit-video-by-voice/SKILL.md
  - code/plugins/premiere-pro/skills/edit-video-by-voice/agents/openai.yaml
  - code/plugins/premiere-pro/skills/edit-video-by-voice/references/input-schema.md
This commit is contained in:
João Henrique
2026-09-09 12:18:47 -04:00
parent fd3440bcc4
commit f3e356340b
5 changed files with 114 additions and 16 deletions
@@ -9,6 +9,7 @@ preserva a validade das posições calculadas — ver docstring de
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from ..dominio import Clipe, Faixa, Timeline
from ..integracoes.premiere.conversores import ConversorDeTimeline
@@ -305,8 +306,7 @@ class AplicadorDePlanoDeEdicao:
deixou uma faixa de áudio inteira sem ser cortada numa aplicação
real deste plano.
"""
alvo = arquivo_de_origem.strip().lower()
nomeados = [clipe for clipe in faixa.clipes if (clipe.nome or "").strip().lower() == alvo]
nomeados = [clipe for clipe in faixa.clipes if self._clipe_corresponde_a_fonte(clipe, arquivo_de_origem)]
sem_origem = [clipe for clipe in nomeados if clipe.intervalo_na_origem is None]
if sem_origem:
raise ErroDeMapeamentoDeTempo(
@@ -316,6 +316,23 @@ class AplicadorDePlanoDeEdicao:
)
return sorted(nomeados, key=lambda clipe: clipe.intervalo_na_origem.inicio)
@staticmethod
def _clipe_corresponde_a_fonte(clipe: Clipe, arquivo_de_origem: str) -> bool:
"""Compara ``source`` com o nome do clipe ou com o nome-base da mídia real.
O Premiere pode devolver no campo ``name`` um nome editorial diferente
do arquivo de mídia, enquanto ``sourceFile`` contém o caminho real.
A comparação por nome-base mantém o plano portátil entre máquinas e
evita exigir que a IA conheça caminhos locais. A busca continua
restrita à faixa atual; nenhuma fonte é escolhida por aproximação.
"""
fonte_informada = Path(str(arquivo_de_origem).strip()).name.casefold()
if not fonte_informada:
return False
nome_do_clipe = (clipe.nome or "").strip().casefold()
caminho_da_midia = Path(str(clipe.arquivo or "").strip()).name.casefold()
return fonte_informada in {nome_do_clipe, caminho_da_midia}
def _borda_de_entrada(self, clipe: Clipe, inicio_de_origem: float) -> float:
"""Posição de entrada do corte na timeline: a borda do clipe se o corte começa antes dele."""
if inicio_de_origem <= clipe.intervalo_na_origem.inicio:
@@ -10,6 +10,7 @@ do clipe lido ao vivo, nunca de um tempo fixo.
import unittest
from typing import Any
from engine.dominio import Clipe, Faixa, IntervaloDeTempo
from engine.editor.aplicador_de_plano_de_edicao import AplicadorDePlanoDeEdicao
from engine.editor.escrita import EscritaNoEditor
from engine.editor.mapeamento import MapeadorDeTempoDeOrigemParaTimeline
@@ -44,6 +45,20 @@ class TesteAplicadorDePlanoDeEdicao(unittest.TestCase):
{"clipe_a_video", "clipe_a_audio"},
)
def test_localiza_fonte_por_nome_base_do_source_file(self):
"""Aceita um source completo quando o Premiere retorna outro nome de clipe."""
faixa = Faixa("video_0", "Vídeo 1", "video", 0, [
Clipe(
identificador="clipe_real",
nome="Câmera 01",
intervalo_na_timeline=IntervaloDeTempo(0.0, 20.0),
intervalo_na_origem=IntervaloDeTempo(0.0, 20.0),
arquivo="/Volumes/Mídia/0E6A8290.MP4",
)
])
clipes = self.aplicador._clipes_do_arquivo(faixa, "/Volumes/OutroLugar/0e6a8290.mp4")
self.assertEqual([clipe.identificador for clipe in clipes], ["clipe_real"])
def test_marcador_e_zoom_sao_aplicados_depois_dos_cortes(self):
# Zoom muda uma propriedade do clipe inteiro (set_clip_properties),
# não de um trecho dele. Se o zoom rodasse antes do corte, cairia no