docs(skill): corte deve deixar folga na borda que encosta em fala mantida
Cortes escritos rente ao timestamp da palavra soavam secos (relatado no projeto Mastopexia) — o critério e o prompt do modelo local mandavam cobrir a frase inteira sem orientar a borda que toca fala mantida. Adiciona a regra de recuar ~0,15-0,25s nas duas pontas quando o corte encosta em conteúdo que fica, tanto no skill (06-texto-corte-marcador.md) quanto no prompt embutido do Ollama (llm_local.py) — pra não precisar ajustar na mão de novo. Também registra em 05_EXPERIENCIAS.md/09_MANUTENCAO.md a dívida de resolve_actions não tolerar margem quando zoom/marker encosta na borda de um corte (contornado manualmente, não corrigido em código ainda), e atualiza a lista de dívidas abertas (etapa 6/offset de whisper já resolvidos nesta sessão). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7b5aed79ee
commit
fd791e116a
@@ -53,6 +53,34 @@ Acima de 3s a pausa deixa de contar como ênfase por construção — medido em
|
|||||||
material real, lacunas de 6–9s rankeavam como os momentos mais enfáticos da
|
material real, lacunas de 6–9s rankeavam como os momentos mais enfáticos da
|
||||||
gravação só porque a escala saturava.
|
gravação só porque a escala saturava.
|
||||||
|
|
||||||
|
### Nunca corte rente à palavra — deixe uma folga
|
||||||
|
|
||||||
|
Um `cut` cujo `start`/`end` cai exatamente no timestamp da palavra (fim da
|
||||||
|
última palavra mantida = início do corte) produz um corte seco: a palavra é
|
||||||
|
engolida antes de terminar de soar, e a fala seguinte começa sem nenhum ar.
|
||||||
|
Isso é diferente de cortar a pausa curta (que seria apagar a própria ênfase,
|
||||||
|
proibido acima) — aqui a pausa **já existe** entre o fim de um bloco mantido
|
||||||
|
e o início do próximo, e o corte está comendo justamente essa margem.
|
||||||
|
|
||||||
|
Ao escrever a borda de um `cut` que encosta em fala mantida (não em silêncio
|
||||||
|
puro), recue **~0,15–0,25s** para dentro do próprio corte, nos dois lados:
|
||||||
|
|
||||||
|
- o `start` do corte fica ~0,2s **depois** do fim real da última palavra
|
||||||
|
mantida;
|
||||||
|
- o `end` do corte fica ~0,2s **antes** do início real da próxima palavra
|
||||||
|
mantida.
|
||||||
|
|
||||||
|
Caso real (projeto Mastopexia): um corte escrito rente (`10.77 → 95.50`,
|
||||||
|
exatamente nos timestamps de palavra) soava abrupto nas duas emendas.
|
||||||
|
Recuado para `10.97 → 95.30`, cada lado ganhou ~0,2s de respiro sem alterar
|
||||||
|
o que é dito — e não empurra o próximo zoom/marcador contra a borda do corte
|
||||||
|
(ver `05-zoom.md` sobre janelas encostadas em corte).
|
||||||
|
|
||||||
|
Isso vale também para o **início e o fim do vídeo**: ar morto antes da
|
||||||
|
primeira palavra e depois da última também leva `cut`, com a mesma folga —
|
||||||
|
não é "silêncio dentro da fala" (isso é `remove_media_silence`), é o mesmo
|
||||||
|
corte de tomada/bastidor que você já está decidindo.
|
||||||
|
|
||||||
### O que continua NÃO sendo seu trabalho
|
### O que continua NÃO sendo seu trabalho
|
||||||
|
|
||||||
| Tarefa | Ferramenta | Por quê |
|
| Tarefa | Ferramenta | Por quê |
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ que merece entrada.
|
|||||||
| 24 | 2026-08-19 | `admin/test_models_api.py` existia mas estava fora de `testpaths` — 13 testes que nunca rodaram | `resolvido` |
|
| 24 | 2026-08-19 | `admin/test_models_api.py` existia mas estava fora de `testpaths` — 13 testes que nunca rodaram | `resolvido` |
|
||||||
| 25 | 2026-08-20 | `admin/api/shared.py` apontava para `admin/code` (inexistente) após a divisão — install editável mascarou o bug em toda validação anterior | `resolvido` |
|
| 25 | 2026-08-20 | `admin/api/shared.py` apontava para `admin/code` (inexistente) após a divisão — install editável mascarou o bug em toda validação anterior | `resolvido` |
|
||||||
| 26 | 2026-08-21 | `generate_voice_script` (IA local/Ollama) caía com "Falha ao gerar roteiro por IA local" — prompt embutia a timeline inteira (47k tokens) e estourava `num_ctx`; e `response.json()` de conexão caída escapava como `JSONDecodeError` | `resolvido` |
|
| 26 | 2026-08-21 | `generate_voice_script` (IA local/Ollama) caía com "Falha ao gerar roteiro por IA local" — prompt embutia a timeline inteira (47k tokens) e estourava `num_ctx`; e `response.json()` de conexão caída escapava como `JSONDecodeError` | `resolvido` |
|
||||||
|
| 27 | 2026-08-21 | Cortes escritos rente ao timestamp da palavra soam secos — critério da skill e prompt do modelo local não instruíam folga na borda | `resolvido` |
|
||||||
|
|
||||||
> Mantenha o índice acima sempre sincronizado com as entradas mais recentes.
|
> Mantenha o índice acima sempre sincronizado com as entradas mais recentes.
|
||||||
|
|
||||||
@@ -1414,3 +1415,47 @@ o outro; percentil entrega um punhado útil nos dois casos.
|
|||||||
> de análise cru no prompt; projetar só o que a decisão usa. E qualquer parse
|
> de análise cru no prompt; projetar só o que a decisão usa. E qualquer parse
|
||||||
> de resposta de servidor local deve tratar body vazio/quebrado como erro de
|
> de resposta de servidor local deve tratar body vazio/quebrado como erro de
|
||||||
> transporte, não como sucesso mudo.
|
> transporte, não como sucesso mudo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2026-08-21 — Cortes escritos rente ao timestamp da palavra soam secos
|
||||||
|
|
||||||
|
- **Sintoma:** usuário revisou o corte final (projeto Mastopexia) e reportou
|
||||||
|
"os cortes estão muito secos, principalmente no final de frase — falta um
|
||||||
|
tempinho a mais pra concluir as palavras". Também notou que o ar morto
|
||||||
|
antes da primeira fala do vídeo não tinha sido cortado.
|
||||||
|
- **Causa:** o critério `06-texto-corte-marcador.md` (e o prompt embutido do
|
||||||
|
modelo local em `fcpxml/llm_local.py`) instruíam cobrir a frase inteira
|
||||||
|
(`start..end = início..fim da frase`) ao escrever um `cut`, sem nenhuma
|
||||||
|
orientação sobre a borda que encosta em fala **mantida** (não em silêncio
|
||||||
|
puro). Um `cut` com `start` exatamente no fim da última palavra mantida
|
||||||
|
engole essa palavra antes dela terminar de soar; um `cut` com `end` no
|
||||||
|
início exato da próxima engole o ataque da fala seguinte. É um problema
|
||||||
|
diferente de cortar a pausa curta (proibido, é a própria ênfase) — aqui a
|
||||||
|
pausa natural entre os blocos já existe, e o corte estava comendo essa
|
||||||
|
margem sozinho.
|
||||||
|
- **Correção:**
|
||||||
|
- `06-texto-corte-marcador.md` ganhou a seção "Nunca corte rente à
|
||||||
|
palavra — deixe uma folga": recuar `start`/`end` do corte em ~0,15–0,25s
|
||||||
|
para dentro do próprio corte nas bordas que tocam fala mantida (não em
|
||||||
|
silêncio puro), incluindo o início/fim do vídeo.
|
||||||
|
- `fcpxml/llm_local.py::_SYSTEM_PROMPT` (item 4) recebeu a mesma
|
||||||
|
instrução, para o modelo local gerar decisões já com a folga.
|
||||||
|
- **Validação manual:** reaplicado no projeto Mastopexia real —
|
||||||
|
`10.77 → 95.50` (rente) virou `10.97 → 95.30` (folga de ~0,2s nas duas
|
||||||
|
pontas), e as 4 emendas seguintes receberam o mesmo tratamento; zoom/texto/
|
||||||
|
marcador continuaram longe o suficiente da nova borda do corte — a folga
|
||||||
|
também evita o problema relacionado (não corrigido em código, só
|
||||||
|
contornado manualmente nesta sessão): um `zoom`/`marker` cuja borda cai
|
||||||
|
exatamente em cima do início/fim de um `cut` é descartado por
|
||||||
|
`resolve_actions` como "apontando para material cortado", mesmo quando a
|
||||||
|
intenção era ficar bem ao lado. Vale registrar como dívida: `resolve_actions`
|
||||||
|
poderia tolerar uma margem de meio-frame antes de considerar a ação "dentro"
|
||||||
|
do corte.
|
||||||
|
- **Estado:** `resolvido`
|
||||||
|
|
||||||
|
> **Aprendizado:** "cobrir a frase inteira" não é a instrução completa para
|
||||||
|
> um corte — a frase que **sobra** ao lado do corte também precisa de uma
|
||||||
|
> borda que respire. Regra prática: só cortar rente ao timestamp quando a
|
||||||
|
> borda encosta em silêncio real (`gap_before` grande) ou em conteúdo que
|
||||||
|
> também será descartado; encostando em fala mantida, sempre recuar.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Este é o documento de rota. Os outros descrevem o que **é**; este diz o que
|
|||||||
**fazer** e por onde começar quando chega uma implementação, uma melhoria ou
|
**fazer** e por onde começar quando chega uma implementação, uma melhoria ou
|
||||||
uma correção.
|
uma correção.
|
||||||
|
|
||||||
Última varredura: 2026-08-19 · 1.466 testes · lint zerado
|
Última varredura: 2026-08-21 · 1.498 testes · lint zerado (fora de server.py/ai_edit.py/llm_local.py, pré-existentes)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -33,40 +33,33 @@ vai para `fcpxml/`.
|
|||||||
|
|
||||||
Ordenado por quanto atrapalha, não por esforço.
|
Ordenado por quanto atrapalha, não por esforço.
|
||||||
|
|
||||||
### 2.1 A etapa 6 ignora a revisão de ênfases
|
### 2.1 `resolve_actions` não tolera margem no encosto de zoom/marker contra um corte
|
||||||
O usuário lapida as frases na etapa 5, o `_phrase_review.json` é gravado — e a
|
Um `zoom`/`marker` cuja borda cai exatamente em cima do `start`/`end` de um
|
||||||
etapa 6 ainda processa como antes. Falta ligar: **zoom e legenda dinâmica só
|
`cut` é descartado como "apontando para material cortado" — mesmo quando a
|
||||||
nas frases de ênfase, legenda comum no resto**. É a continuação natural do
|
intenção era ficar bem ao lado. Contornado manualmente no projeto Mastopexia
|
||||||
trabalho da etapa 5 e o item mais valioso da lista.
|
(recuando as bordas na mão); a correção estrutural é dar a `resolve_actions`
|
||||||
→ `MacApp/Sources/WizardView.swift` (`finalizeProcessing`), `admin/api/subtitles.py`,
|
uma margem de tolerância (meio frame) antes de considerar uma ação "dentro"
|
||||||
`fcpxml/phrase_review.py` (`emphasis_spans` já é produzido e ninguém consome).
|
do corte. → `fcpxml/voice_actions.py` (`resolve_actions`/`shift_after_cuts`),
|
||||||
|
`05_EXPERIENCIAS.md` #27.
|
||||||
|
|
||||||
### 2.2 Offset de ~400 ms no timing por palavra
|
### 2.2 `MacApp/` não tem teste automatizado
|
||||||
O faster-whisper sem alinhamento forçado erra o início de cada palavra em
|
|
||||||
~0,4 s. Isso desloca zoom, corte e `gap_before` de uma vez. Há paliativo
|
|
||||||
aplicado por projeto; a correção estrutural é ligar o **WhisperX** (ou
|
|
||||||
alinhamento equivalente) em `transcribe.py`, o que levaria o erro para ~30 ms.
|
|
||||||
Custo real: regerar todos os `_transcript.json` e `_voice_timeline.json`
|
|
||||||
existentes. → `05_EXPERIENCIAS.md` #14, estado `parcialmente resolvido`.
|
|
||||||
|
|
||||||
### 2.3 `MacApp/` não tem teste automatizado
|
|
||||||
5.500 linhas de Swift sem uma asserção. A rede hoje é o harness manual (§4) e
|
5.500 linhas de Swift sem uma asserção. A rede hoje é o harness manual (§4) e
|
||||||
o olho do usuário. Não é para sair criando suíte de UI — mas lógica pura que
|
o olho do usuário. Não é para sair criando suíte de UI — mas lógica pura que
|
||||||
foi parar na camada de tela (cálculo de trim, mapeamento de tempo) deveria
|
foi parar na camada de tela (cálculo de trim, mapeamento de tempo) deveria
|
||||||
descer para o Python, onde já existe rede.
|
descer para o Python, onde já existe rede.
|
||||||
|
|
||||||
### 2.4 `admin/` fica fora do lint
|
### 2.3 `admin/` fica fora do lint
|
||||||
`run_after_fix.sh` roda o ruff de dentro de `code/`, então `admin/` — 1.751
|
`run_after_fix.sh` roda o ruff de dentro de `code/`, então `admin/` — 1.751
|
||||||
linhas de código que o app depende para funcionar — nunca é verificado.
|
linhas de código que o app depende para funcionar — nunca é verificado.
|
||||||
Incluir mexe no gate, então é decisão consciente, não esquecimento.
|
Incluir mexe no gate, então é decisão consciente, não esquecimento.
|
||||||
|
|
||||||
### 2.5 Confirmações visuais pendentes no FCP
|
### 2.4 Confirmações visuais pendentes no FCP
|
||||||
Várias entradas do `05_EXPERIENCIAS.md` estão marcadas como resolvidas *no XML*
|
Várias entradas do `05_EXPERIENCIAS.md` estão marcadas como resolvidas *no XML*
|
||||||
— testes verdes, DTD válido — mas **pendentes de importação real no Final Cut**.
|
— testes verdes, DTD válido — mas **pendentes de importação real no Final Cut**.
|
||||||
XML válido não é o mesmo que XML que renderiza como o esperado. Ao mexer em
|
XML válido não é o mesmo que XML que renderiza como o esperado. Ao mexer em
|
||||||
legenda, zoom ou keyframe, a confirmação final é abrir no FCP.
|
legenda, zoom ou keyframe, a confirmação final é abrir no FCP.
|
||||||
|
|
||||||
### 2.6 Submódulo `WHISPERX` com conteúdo modificado e não commitado
|
### 2.5 Submódulo `WHISPERX` com conteúdo modificado e não commitado
|
||||||
Está fora dos commits de propósito, porque ninguém verificou o que mudou lá
|
Está fora dos commits de propósito, porque ninguém verificou o que mudou lá
|
||||||
dentro. Precisa ser olhado e resolvido — ou commitado, ou revertido.
|
dentro. Precisa ser olhado e resolvido — ou commitado, ou revertido.
|
||||||
|
|
||||||
@@ -98,7 +91,7 @@ quanto arquivo gigante.
|
|||||||
## 4. Checklist antes de dar algo por pronto
|
## 4. Checklist antes de dar algo por pronto
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd code && ./Engine/run_after_fix.sh # lint zerado + 1.466 testes
|
cd code && ./Engine/run_after_fix.sh # lint zerado + 1.498 testes
|
||||||
admin/run_app.command # se mexeu no app (padrão de revisão)
|
admin/run_app.command # se mexeu no app (padrão de revisão)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -125,7 +118,7 @@ E, além do script:
|
|||||||
| FCP recusa o arquivo ao importar | `id` inválido, ordem de filhos, timebase | `writer/validation.py`, `dtd.py` |
|
| FCP recusa o arquivo ao importar | `id` inválido, ordem de filhos, timebase | `writer/validation.py`, `dtd.py` |
|
||||||
| Título importa mas não aparece | Template/uid Motion que não resolve | `writer/titles.py` |
|
| Título importa mas não aparece | Template/uid Motion que não resolve | `writer/titles.py` |
|
||||||
| Corte no lugar errado | Tempo pós-corte usado como se fosse original | `voice_actions.py` (`shift_after_cuts`) |
|
| Corte no lugar errado | Tempo pós-corte usado como se fosse original | `voice_actions.py` (`shift_after_cuts`) |
|
||||||
| Zoom no lugar errado | Idem, ou offset de timing do Whisper | §2.2 |
|
| Zoom/marker sumindo perto de um corte | Borda encostando exatamente no `cut` | §2.1 |
|
||||||
| Legenda sobrepondo | Layout ou conteúdo antigo no arquivo | `collision.py`, `text_layout.py` |
|
| Legenda sobrepondo | Layout ou conteúdo antigo no arquivo | `collision.py`, `text_layout.py` |
|
||||||
| "Ênfase" apontando para palavra à toa | Falta renormalizar após o corte | `refine_voice_timeline` |
|
| "Ênfase" apontando para palavra à toa | Falta renormalizar após o corte | `refine_voice_timeline` |
|
||||||
| App diz que falta librosa/pyannote | `uv run` com cwd errado | `PythonBridge.swift` (§3 do doc 08) |
|
| App diz que falta librosa/pyannote | `uv run` com cwd errado | `PythonBridge.swift` (§3 do doc 08) |
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ Regras (siga rigorosamente):
|
|||||||
|
|
||||||
3. ESCOLHER A MELHOR TOMADA de cada frase quando há repetições: mantenha a mais limpa e corte as outras (cut cobrindo a frase inteira).
|
3. ESCOLHER A MELHOR TOMADA de cada frase quando há repetições: mantenha a mais limpa e corte as outras (cut cobrindo a frase inteira).
|
||||||
|
|
||||||
4. CORTE (kind "cut"): para REMOVER uma frase, cubra ela inteira (start..end = início..fim da frase). Para APARAR só uma hesitação no começo ou fim, corte só da borda até a palavra (corte de meia frase é ambíguo — passe de 60% e apaga a linha toda). Nunca corte o silêncio entre falas.
|
4. CORTE (kind "cut"): para REMOVER uma frase, cubra ela inteira (start..end = início..fim da frase). Para APARAR só uma hesitação no começo ou fim, corte só da borda até a palavra (corte de meia frase é ambíguo — passe de 60% e apaga a linha toda). Nunca corte o silêncio entre falas. Quando a borda do corte encosta em fala mantida (não em silêncio puro), recue ~0,15-0,25s para dentro do corte nos dois lados — start ~0,2s DEPOIS do fim real da última palavra mantida, end ~0,2s ANTES do início real da próxima palavra mantida — senão o corte soa seco, engolindo a palavra antes de terminar de soar. Isso vale também pro início/fim do vídeo (ar morto antes da primeira palavra e depois da última).
|
||||||
|
|
||||||
5. ZOOM (kind "zoom"): só em palavra de CONTEÚDO bem enfatizada (emphasis alto, não artigo). params.scale entre 1.0 e 3.0 (padrão 1.3 se omitido). Posicione em torno da palavra, segurando até o fim da frase.
|
5. ZOOM (kind "zoom"): só em palavra de CONTEÚDO bem enfatizada (emphasis alto, não artigo). params.scale entre 1.0 e 3.0 (padrão 1.3 se omitido). Posicione em torno da palavra, segurando até o fim da frase.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user