feat(voz): legenda por ênfase, forced align, IA local e correções de zoom/revisão
Trabalho da branch feat/revisao-enfases: pipeline de edição por voz ganha alinhamento forçado (whisperx), roteirização por LLM local (Ollama), e a etapa 5 (revisão de frases) passa a refletir de verdade o que é aplicado. - generate_subtitles_by_emphasis: legenda comum cobre o clipe inteiro, legenda dinâmica só nas frases de ênfase, e a comum é desativada (enabled="0") onde a dinâmica cobre, em vez de nunca ser gerada ali. - validate_subtitle_layout ignora títulos com enabled="0" — corrige falso positivo de colisão contra o que está desativado no lugar dele. - Corrige zoom/marcador sendo descartado quando a borda encosta exatamente no início de um corte. - Etapa 5 do Assistente: recarrega quando as decisões da IA mudam (com fresh=true, ignorando a revisão salva antiga) — resolve a dessincronia entre "ativa" na tela e o que já foi cortado no FCPXML. - Etapa "Processar" reaplica as decisões da revisão (_phrase_actions.json) antes da cadeia de remoção de silêncio/legendas — antes, desativar uma frase na etapa 5 não tinha efeito nenhum no vídeo final. - Etapa "Concluído" fundida em "Processar" — abrir no Final Cut/Finder aparece assim que termina, sem slide extra. - Palavra clicável na etapa 5 agora funciona como toggle (clique de novo desfaz) e mostra a própria ênfase (sublinhado colorido + peso da fonte). - fcpxml/forced_align.py, fcpxml/llm_local.py, ai_edit.py: alinhamento fonético via whisperx e roteirização local via Ollama/Gemma. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
711c397dfe
commit
7b5aed79ee
@@ -264,3 +264,26 @@ class TestIntegration:
|
||||
report = modifier.validate_subtitle_layout()
|
||||
assert report["summary"]["spatial_collision"] >= 1
|
||||
assert blocking(report["severity"])
|
||||
|
||||
def test_disabled_title_is_excluded_from_validation(self, temp_fcpxml):
|
||||
"""A title with enabled="0" never renders in Final Cut
|
||||
(generate_subtitles_by_emphasis disables plain titles under an
|
||||
emphasis phrase instead of never creating them) — it must not count
|
||||
as a collision, or as outside-frame/outside-safe-area, against the
|
||||
title actually drawn in its place."""
|
||||
modifier = FCPXMLModifier(temp_fcpxml)
|
||||
titles = modifier.generate_dynamic_subtitles("Interview_A", WORDS, WORD_MODE)
|
||||
|
||||
def position(el):
|
||||
for p in el.findall("param"):
|
||||
if p.get("name") == "Position":
|
||||
return p
|
||||
return None
|
||||
|
||||
p0 = position(titles[0])
|
||||
position(titles[1]).set("value", p0.get("value"))
|
||||
titles[1].set("enabled", "0")
|
||||
|
||||
report = modifier.validate_subtitle_layout()
|
||||
assert report["summary"]["spatial_collision"] == 0
|
||||
assert not blocking(report["severity"])
|
||||
|
||||
Reference in New Issue
Block a user