Marca cada título gerado (dynamic/plain) em metadata para que regenerar
substitua a saída anterior em vez de empilhar, e usa os spans de ênfase
revisados (não os segmentos brutos do Whisper) como janela da composição
dinâmica, evitando que ela invada o trecho de legenda comum seguinte.
suppress_plain_under_dynamic corta qualquer sobra visível como rede de
segurança.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
generate_dynamic_subtitles e a metade dinâmica de generate_subtitles_by_emphasis
passam a empacotar cada sub-frase da legenda dinâmica num compound clip por
padrão (compound_subphrases=True), completando o wrap_titles_in_compound
e split_into_subphrases do commit anterior — que ainda não tinham chamador
em produção.
Também torna validate_subtitle_layout ciente de compound clips: media cada
grupo (spine principal + cada <media> de compound) no seu próprio espaço de
tempo, em vez de uma varredura .//title global — sem isso, âncoras de
compounds diferentes liam offset "0s" e acusavam colisão espacial entre
frases que nunca dividem a tela, só porque compartilham o mesmo zero de
tempo local.
Testado ponta a ponta na gravação real (Mastopexia): 12 compounds, 41
títulos todos empacotados, zero soltos, zero IDs duplicados, DTD válida.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
split_into_subphrases divide a frase na vírgula — onde a fala respira —
mas funde de volta o pedaço curto ("né?", "Então..."), que lê como parte
da frase anterior e não como bloco próprio.
wrap_titles_in_compound empacota os títulos de uma sub-frase num compound
clip, replicando a estrutura que o próprio Final Cut produz: o primeiro
título vira âncora do spine em offset 0, os demais penduram nele por lane,
e um ref-clip toma o lugar deles na lane original. Os offsets dos filhos
são rebaseados para o espaço de tempo da âncora, senão cada palavra
escorregaria pela diferença entre os dois start.
Junto: _filter_children_for_segment passa a filtrar também o <video> do
Clipe de Ajuste. Sem isso, cada corte subsequente duplicava o zoom em
todos os pedaços resultantes com o offset original intacto, e as cópias
desenhavam empilhadas na mesma posição da timeline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Object-tracker/tracking-shape (dado de rastreamento de objeto preservado
do asset original) mantinha o mesmo id em cada deepcopy feito por
split_clip/cut_clip_ranges, e o FCP acabava rejeitando o arquivo com "ID
tr1 already defined" depois de vários cortes. Mesmo mecanismo do bug já
corrigido para text-style-def, agora coberto também para tracking-shape.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dois problemas reais vistos no projeto Mastopexia:
1. cut_clip_ranges só absorvia um keep-segment curto no INÍCIO/FIM do
clipe (a lógica já existente do #6). Um keep curto no MEIO (entre dois
cuts, sem nenhum vizinho mantido pra herdar) nunca era absorvido —
sobrava como clipe de vídeo de 0,07-0,23s na timeline. Generalizado
pra qualquer posição, com limiar maior (6 frames / 0,3s, medido no
material real) — no meio, o pedacinho é descartado (vira parte do
corte ao redor), nas bordas continua sendo herdado pelo vizinho.
2. generate_subtitles_by_emphasis gerava a legenda comum inteira e
desativava (enabled="0") onde a dinâmica cobre. Título desativado
continua aparecendo como clipe riscado na timeline do Final Cut mesmo
sem renderizar — um corte com bastante ênfase virava dezenas de clipes
mortos poluindo a trilha (visto ao vivo pelo usuário: "ficou uma
bosta"). Trocado por não gerar o bloco comum ali, em vez de gerar e
desativar. Custo: reativar ênfase manualmente depois exige regenerar a
legenda comum daquele trecho, não só reabilitar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
O writer tinha 4.199 linhas, das quais 3.300 numa única classe com dezoito
assuntos dentro. Achar o trecho de zoom exigia rolar por marcadores,
velocidade e legendas.
Agora é o pacote fcpxml/writer/, com um arquivo por assunto e o
FCPXMLModifier montado por composição de mixins. Mixins, e não objetos
separados, porque todas essas operações mexem no mesmo documento e nos
mesmos índices — separá-las em objetos independentes transformaria toda
chamada interna em travessia de fronteira sem nada em troca. A divisão que
importa aqui é de leitura, não de estado.
Nenhuma mudança de comportamento e nenhuma alteração nos ~50 pontos que
importam do writer: o __init__ re-exporta tudo, inclusive os nomes com
underscore que a suíte já usava.
core 723 carga, índices, navegação na spine, save
titles 600 títulos e legendas dinâmicas
cut 333 dividir, cortar faixas, apagar
speed 297 velocidade e zoom
(+ 20 módulos menores)
Único ajuste de chamada: quatro testes faziam patch em
fcpxml.writer.subprocess, que agora mora em writer.document (ver
Engine/docs/05_EXPERIENCIAS.md #23).
Lint zerado, 1441 testes passando.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>