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
@@ -194,6 +194,7 @@ class FCPXMLParser:
|
||||
media_path=media_path,
|
||||
audio_role=elem.get('audioRole', ''),
|
||||
video_role=elem.get('videoRole', ''),
|
||||
rotation=self._parse_clip_rotation(elem),
|
||||
)
|
||||
|
||||
clip.markers.extend(self._collect_markers(elem))
|
||||
@@ -205,6 +206,19 @@ class FCPXMLParser:
|
||||
|
||||
return clip
|
||||
|
||||
def _parse_clip_rotation(self, elem: ET.Element) -> float:
|
||||
"""Degrees from this clip's ``<adjust-transform rotation="...">`` —
|
||||
an edit-time correction (e.g. straightening a tilted phone shot),
|
||||
not the camera's own recorded orientation. FCP writes the rotation
|
||||
as an attribute on that element, not as a filter param."""
|
||||
transform = elem.find('adjust-transform')
|
||||
if transform is None:
|
||||
return 0.0
|
||||
try:
|
||||
return float(transform.get('rotation', '0'))
|
||||
except ValueError:
|
||||
return 0.0
|
||||
|
||||
def _parse_marker_element(self, elem: ET.Element) -> Optional[Marker]:
|
||||
"""Parse any marker element (<marker> or <chapter-marker>).
|
||||
|
||||
@@ -337,6 +351,7 @@ class FCPXMLParser:
|
||||
lane=lane, offset=offset, source_start=start,
|
||||
media_path=media_path, clip_type=elem.tag, role=role,
|
||||
ref_id=ref, parent_clip_name=parent_name,
|
||||
rotation=self._parse_clip_rotation(elem),
|
||||
)
|
||||
|
||||
connected.markers.extend(self._collect_markers(elem))
|
||||
|
||||
Reference in New Issue
Block a user