Substitua os vocais originais em inglês de um vídeo por dublagem em chinês em uma linha do tempo de identidade: o vídeo nunca é re-ajustado — cada quadro mantém seu timestamp original e a versão dublada tem exatamente a duração do vídeo bruto. O áudio sintetizado de cada sinal é ajustado à sua própria janela original (início do sinal → início do próximo sinal) com um atempo por sinal; a síntese, mais rápida ou mais lenta, cabe na janela. O resultado é uma segunda versão — mesma imagem na mesma velocidade, áudio em chinês, legendas bilíngues ZH+EN incorporadas.
Esta skill faz as duas partes criativas que a CLI não consegue: traduzir para dublagem (frases completas escritas dentro de um orçamento de caracteres, não a fragmentação de legendas) e executar os gates de qualidade (gate de comprimento, revisão a frio, gate auditivo, revisão pós-incorporação). A execução determinística (Demucs, IndexTTS2, montagem, reajuste de tempo, ffmpeg) é tratada pelo subcomando cook dub da CLI cook. Todos os scripts de estágio estão dentro do pacote cook (stages/dub/); cada invocação abaixo é um comando cook. pip install -U video-cook atualiza tudo de uma vez.
Quando usar esta skill
Você tem um vídeo que já possui:
- Um arquivo de vídeo bruto (
<output-root>/raw/<name>.raw.mp4) — o original, com vocais em inglês. - Uma execução de legendas bilíngues do
video-subtitle— especificamentetranscript/<name>.en.full.srt(a transcrição em inglês de frases completas, mesclada a partir de fragmentos do whisperX) etranscript/translations.txt.
Você quer uma versão dublada em chinês. Se você ainda não tem isso, execute video-download e depois video-subtitle primeiro — esta skill lê suas saídas.
O que você produz
Três produtos são gerados: cooked/<name>.dubbed.mp4 (vídeo bruto intacto, dublagem em chinês, legendas bilíngues incorporadas — duração idêntica ao bruto), cloud-srt/zh.dub.srt + en.dub.srt (legendas para upload). Todo o resto são arquivos de trabalho em dubbed/ — a árvore anotada abaixo mapeia cada um deles.
A execução não está concluída até que as verificações da Etapa 6 passem.
Estrutura de diretórios
Esta skill adiciona dubbed/ (diretório de trabalho) e escreve os produtos finais em cooked/ e cloud-srt/:
<output-root>/
├── raw/ ← do video-download (esta skill o lê)
│ └── <name>.raw.mp4
├── transcript/ ← do video-subtitle (esta skill lê + adiciona)
│ ├── <name>.en.full.srt ← inglês de frases completas (a origem do script de dublagem)
│ ├── translations_dub.txt ← esta skill escreve: uma linha em chinês por sinal
│ └── <name>.zh.dub.srt ← esta skill escreve
├── cooked/ ← vídeos finais estão aqui
│ ├── <name>.cooked.bar.mp4 ← do video-subtitle (intacto)
│ └── <name>.dubbed.mp4 ← produto desta skill
├── cloud-srt/ ← legendas para upload estão aqui
│ ├── zh.srt / en.srt ← do video-subtitle (intactas)
│ └── zh.dub.srt / en.dub.srt ← legendas de upload desta skill
└── dubbed/ ← diretório de trabalho desta skill
├── _reference/
│ └── ref.wav
└── _full/
├── _segments/ ← cache do IndexTTS2 por sinal
├── dub.wav ← a dublagem no relógio original
├── dubbing.srt ← arquivo de trabalho (ZH, pré-encurtamento)
├── dubbing.short.srt ← arquivo de trabalho (ZH, saída de encurtamento)
├── dubbing.merged.srt ← arquivo de trabalho (ZH, pós-encurtamento; copiado para cloud-srt)
├── dubbing.en.srt ← arquivo de trabalho (EN de frases completas)
├── dubbing.en.short.srt / dubbing.en.merged.srt ← suas saídas de encurtamento e mesclagem
├── dubbing.bilingual.srt ← arquivo de trabalho (união bilíngue; o que é incorporado)
├── dubbing.cooked.ass ← arquivo de trabalho (saída ass, copiado para burn.ass)
└── burn.ass ← o ASS realmente incorporado
Regra: dubbed/ é o diretório de trabalho; cooked/<name>.dubbed.mp4 e cloud-srt/{zh,en}.dub.srt são os produtos. Nunca toque em raw/, transcript/<name>.zh.srt, cooked/<name>.cooked.mp4 ou cloud-srt/{zh,en}.srt — esses pertencem ao video-subtitle. Se esta skill falhar no meio, o produto cozido bilíngue ainda está completo.
O pipeline
Dois estágios após o trabalho criativo: synth e depois assemble, invocados através do cook:
cook dub synth <root> <name> --python <indextts-venv>/Scripts/python.exe
cook dub assemble <root> <name> --python <indextts-venv>/Scripts/python.exe [--keep-subs]
# ou ambos:
cook dub full <root> <name> --python <indextts-venv>/Scripts/python.exe
As etapas abaixo descrevem o que cada estágio faz internamente (para que você possa verificar saídas e diagnosticar falhas); os comandos cook dub <stage> são como você os executa.
Etapa 0 — Resolver os ambientes
Existem dois ambientes Python em jogo, e confundi-los é o modo de falha que esta etapa existe para prevenir:
- Ambiente do cook (Python do sistema ou
~/.venvs/video-tools/) — onde a CLIcookreside, com whisperX/yt-dlp/torch para o pipeline de legendas. - Ambiente do IndexTTS2 (
~/Git/index-tts/.venv) — um venv separado contendoindextts,torch,demucsewhisperx(o pipeline de dublagem adiciona os dois últimos em cima do lockfile upstream). Essas dependências são pesadas e isoladas de propósito; não tente instalá-las no ambiente do cook.
O cook executa cada estágio de dublagem como um subprocesso sob o venv do IndexTTS2 via --python, então from indextts import ... resolve lá. Todo comando cook dub nesta skill leva --python <indextts-venv>/Scripts/python.exe. Resolva o caminho do venv uma vez (padrão ~/Git/index-tts/.venv) e reutilize-o para toda a execução.
0a. Verificar se ambos os ambientes estão acessíveis:
<cook-venv>/Scripts/cook doctor # whisperX/yt-dlp/ffmpeg
<indextts-venv>/Scripts/python -c "from indextts.infer_v2_5 import IndexTTS2; import demucs, whisperx; print('ok')" # indextts (v2.5) + demucs + whisperx
0b. Restrição de thread única. O IndexTTS2 deve ser executado com thread única (OMP_NUM_THREADS=1), ou produz áudio de baixa qualidade. O cook define isso internamente antes de importar o torch, então você não precisa exportá-lo.
Concluído quando o doctor do cook reporta whisperX/yt-dlp/ffmpeg instalados, o venv do IndexTTS2 importa indextts (v2.5) + demucs + whisperx, e você sabe o caminho absoluto para <indextts-venv>/Scripts/python.exe para passar como --python.
Etapa 1 — Separar vocais do vídeo bruto
O áudio original é uma faixa mixada (vocais + BGM + SFX). O Demucs o divide para que possamos extrair uma referência limpa e verificar BGM posteriormente.
cook dub separate <output-root> <name> [--model htdemucs] --python <indextts-venv>/Scripts/python.exe
O Demucs reside no venv do IndexTTS2, então --python aponta para lá. Use htdemucs (modelo único, ~3GB de RAM), não htdemucs_ft (conjunto de 4 modelos, ~20GB de RAM — estoura a memória em máquinas de 32GB). A qualidade é ligeiramente inferior, mas adequada para extração de referência.
No modo de primeiro plano (padrão), o cook move as faixas separadas para dubbed/vocals.wav + dubbed/no_vocals.wav automaticamente; no modo --detach você as move após o marcador de conclusão aparecer.
Concluído quando dubbed/vocals.wav E dubbed/no_vocals.wav existem com duração correspondente ao bruto ±0,5s.
Etapa 2 — Extrair o clipe de referência
O IndexTTS2 precisa de um clipe limpo de 14 a 30 segundos do falante original — ele lê no máximo os primeiros 15s, então uma janela de capacidade total é melhor que uma curta. O IndexTTS2 clona prosódia, não apenas timbre: o ritmo de entrega da janela se torna o ritmo de entrega de toda a dublagem. O seletor padrão pega o segmento de fala contínua mais longo, que tende a selecionar a passagem mais lenta e deliberada — muitas vezes errada. Prefira o scanner:
cook dub scan-reference <output-root> <name> \
[--full-band] [--python <indextts-venv>/Scripts/python.exe]
Ele classifica janelas fixas de 15s por taxa de sílabas (filtradas por densidade e pausa) e corta os 3 melhores candidatos (mais cópias de banda larga de 48k com --full-band). A densidade é apenas metade da história — o IndexTTS2 também clona a prosódia da janela, então classifique os candidatos também por vivacidade: desvio padrão do F0 por pyin e faixa de semitons por janela (uma janela plana dubla plano; use librosa.pyin, NÃO detect_pitch_frequency, que relata sonoridade no silêncio). Autentique qualquer arquivo de referência legado antes de reutilizá-lo: um derivado reprocessado mantém sua incorporação de falante e estatísticas de pitch, mas perde correlação de forma de onda, então correlação próxima de zero NÃO significa "falante diferente" — pontue-o contra um clipe âncora com cook dub scan-reference --score <file> --anchor <anchor.wav> (áudio real do mesmo falante chega perto de 0,94; sínteses na faixa de 0,66-0,76 é a banda normal) e compare as estatísticas pyin. De qualquer forma: o ouvido do usuário escolhe a referência, sempre — sintetize as frases piloto uma vez por candidato (Etapa 3a), pré-selecione cada take com um ASR de modelo pequeno ida e volta (palavras perdidas sinalizam confusão; saída em chinês tradicional sinaliza um sotaque que a janela sangra no take), pontue com cook dub scan-reference --score, então entregue os wavs com correspondência de volume ao usuário.
Concluído quando dubbed/_reference/ref.wav existe, tem 14-30s e não tem gap de silêncio > 0,3s (ffmpeg -af silencedetect).
Etapa 3 — Traduzir para dublagem (o agente faz isso)
Aqui é onde a dublagem diverge das legendas. Não use translations.txt (a tradução de legendas) — ela segue os cortes de fragmentos do whisperX, que dividem frases. A dublagem precisa de frases completas para que o chinês flua naturalmente quando falado.
Leia <output-root>/transcript/<name>.en.full.srt (a transcrição em inglês de frases completas). Traduza cada sinal você mesmo, escrevendo em transcript/translations_dub.txt — uma linha em chinês por sinal em inglês, linha N = sinal N.
A regra do orçamento de caracteres — o núcleo da tradução de dublagem. O orçamento de cada sinal é janela em segundos × taxa de fala normal, fixado antes de você escrever: a janela é do início do sinal ao início do próximo sinal (janela + pausa seguinte), a taxa é medida, nunca assumida — cook dub measure-rate nos pilotos que o usuário escolheu (procedimento: Etapa 3a). Uma taxa assumida passa por todos os gates e dubla com segundos de silêncio morto: uma palestra de 45 minutos orçada a uma taxa assumida de 5,0 caracteres/s contra uma taxa real de 6,93 deixou 83 sinais com >2,5s de silêncio e custou uma re-síntese de 82 sinais. Escreva dentro do orçamento: um pouco acima está ok, notavelmente abaixo não está — um take curto agora mantém seu ritmo natural e deixa uma pausa para respirar, então texto abaixo do orçamento significa silêncio morto e texto acima do orçamento significa compressão audível. Recalibre em qualquer mudança de referência ou fator de duração: um orçamento calibrado para uma referência aposentada passa por todos os gates e ainda dubla com silêncio morto. Sintetize uma sonda estratificada (5-10 sinais típicos por falante em faixas de comprimento) e re-verifique o script em relação às janelas nas taxas medidas — procedimento em REFERENCE.md → "Rate recalibration". Como preencher um orçamento legitimamente (e o que "preencher" não tem permissão para inventar): REFERENCE.md → "Filling the char budget".
Princípios de tradução (além do orçamento):
- Traduza pensamentos completos, não fragmentos. O inglês já são frases completas; seu sinal em chinês é um pensamento completo.
- Mantenha termos técnicos em inglês onde desenvolvedores chineses o fazem — spec, plan, prototype, agent, token, skill, session, branch, route, etc. Veja REFERENCE.md → "Term retention list".
- Mantenha inglês para qualquer coisa mostrada na tela — rótulos de UI, código, URLs, nomes de arquivos. Exemplos em REFERENCE.md → "Term retention list".
- Traduza conceitos que têm nomes chineses padrão quando não são mostrados na tela — os exemplos trabalhados estão na lista de retenção de termos.
- A contagem de linhas deve ser igual à contagem de sinais. A maneira sancionada de alterar contagens é o
build_merge.pyda Etapa 3a, que reescreve ambos os arquivos juntos.
Em seguida, gere o SRT de pré-montagem (timestamps herdados de en.full.srt):
cook dub make-srt <output-root> <name>
Auto-revisão — duas passagens, antes do gate mecânico: leia cada linha em voz alta em sua cabeça como uma frase falada (soa como algo que uma pessoa diria?); depois, escaneie a lista de termos abaixo em relação a cada linha (qualquer coisa na tela permanece em inglês, conceitos padrão vão para chinês). Estas são você verificando seu próprio trabalho; a revisão do subagente abaixo é a leitura a frio.
Gate de comprimento — mecânico (aritmética pura, leva um segundo):
cook dub gate <output-root> <name> --rate <medido chars/s>
Ele verifica cada linha em relação à sua janela de sinal de três maneiras: linhas curtas (≤8 sílabas — a armadilha de ritmo de narração do IndexTTS2, reescreva mais completo ou deixe a Etapa 3a fundir), erros de orçamento (fala estimada muito acima de sua janela — o texto deve ser cortado — ou muito abaixo — a janela fica com silêncio morto, preencha-a), e o total de cobertura (Σ fala estimada / Σ janelas — uma tradução uniformemente fina passa em cada verificação de linha e ainda dubla com longos silêncios; saudável 90-105%). --rate é obrigatório (o valor medido; --self-measure o deriva de takes existentes). Saída 1 lista as linhas e o veredito de cobertura; reescreva e execute novamente até passar. Uma linha acima do orçamento é corrigida reescrevendo mais curto ou fundindo-a com seu vizinho com cook dub merge — nunca deixando-a ser comprimida por atempo.
Gate de qualidade — revisão de subagente fan-out (obrigatório, antes da síntese). O gate auditivo e esta revisão são os únicos gates antes de horas de síntese. Faça um fan-out de um subagente com acesso de leitura tanto a <name>.en.full.srt quanto a translations_dub.txt, e peça para ele verificar, para cada sinal:
- Precisão da tradução — o chinês transmite fielmente o inglês? Nenhuma cláusula omitida, nenhum conteúdo adicionado (nada que o inglês não diga), nenhuma tradução incorreta.
- Ortografia de nomes próprios — nomes (pessoas, produtos, empresas) exatamente como a fonte os usa. "Claude" não "克劳德", "IndexTTS" não "索引TTS", a menos que um nome chinês padrão realmente exista.
- Legibilidade para TTS — o IndexTTS2 pronunciará isso naturalmente? Nenhuma sequência de caracteres estranha, nenhuma pontuação órfã
…
[truncado para tradução]