O que localizing-video faz?
Transforme um vídeo em língua estrangeira em inglês — dublagem sincronizada + legendas em inglês + texto na tela traduzido — 100% local/grátis no Apple Silicon. Um agente capaz rederivará a canalização (yt-dlp, whisper, ffmpeg, scene-detect, mux); este skill existe para as escolhas de ferramentas e footguns silenciosos que a rederivação erra, e para a implementação de referência comprovada.
Implementação de referência (comece aqui)
Este repositório entrega o pipeline funcional: engine/ é o motor genérico e executável (build_dub.py — ancorado por bloco, sem drift (veja o footgun da dublagem), make_ass.py, dense_ocr.py, dedup_spans.py, render_overlay.py, run.sh); example/ é um conjunto de dados completo e trabalhado. Leia engine/SETUP.md + example/README.md e execute o motor — não escreva do zero. Por vídeo, o único trabalho novo é produzir en.srt + translations.json (a etapa de tradução). O motor assume uma fonte 1920×1080.
Pipeline + a decisão em cada etapa
- Download
yt-dlp(tente--extractor-args "youtube:player_client=web_safari,ios,tv"em 403; o formato HLS frequentemente funciona quando o DASH está com DRM). - ASR — mas
yt-dlp --list-subsPRIMEIRO (veja footgun): uma faixa manual/criador pode vencer o Whisper de graça, a tag-origrevela o verdadeiro idioma de origem (evita que você use o Whisper por 16 min para nada se o vídeo já estiver no idioma alvo), e uma legenda automática-origé um segundo ASR gratuito para verificação cruzada. Depois, openai-whisperwhisper source.mp4 --model large-v3-turbo --language <zh/ja> --word_timestamps True --output_format all(mantenha o JSON). Fixe o idioma. (torch não tem wheel para py3.14 → execute o whisper a partir de um venv py3.12.) - Traduza a transcrição VOCÊ MESMO (o agente, em-loop) — grátis, com noção de tom, consistência de jargão. Mantenha o mapeamento 1:1 segmento↔timestamp. Corrija nomes próprios com conhecimento (o ASR os ouve errado). Preserve a piada, não apenas as palavras — trocadilhos raramente sobrevivem à tradução literal; trans-crie um trocadilho em inglês que carregue o mesmo mecanismo, e entregue a conclusão na legenda (portadora principal da piada — a dublagem passa por cima do original com volume reduzido). Trocadilhos homofônicos são invisíveis no ASR (veja footgun), então faça OCR da legenda para encontrá-los.
- Dublagem — Kokoro-onnx TTS (veja footguns). Síntese por bloco de frase, ancore cada bloco ao seu timestamp verdadeiro (NÃO persiga o drift por segmento — ele acumula atraso), ajuste de tempo, mix. Retemporize os visuais para encontrar a dublagem, não apenas a dublagem para o relógio — veja Time-density.
- Legendas — queime o inglês em uma barra letterbox, NÃO sobre o quadro (veja footguns).
- OCR visual —
ocrmac(Apple Vision), denso a 1fps, deduplicado em intervalos de tempo. - Traduza as strings visuais VOCÊ MESMO; sobreponha o inglês sobre as caixas apenas em chinês.
- Mux —
ffmpeg-full(libass), overlay → letterbox → queima de legenda → áudio da dublagem.
Footguns — a razão real deste skill existir
| Armadilha | Realidade / correção |
|---|---|
ffmpeg não tem filtro subtitles | O ffmpeg padrão do brew não tem libass. Use /opt/homebrew/opt/ffmpeg-full/bin/ffmpeg. (skill: hardsubbing-video) |
TTS = say é monótono/robótico | Padrão: Kokoro-onnx (grátis, neural, compatível com py3.14 via onnxruntime). Voz masculina am_michael/etc. |
Kokoro falha silenciosamente: phontab ... No such file | O caminho dos dados do espeak-ng está quebrado. brew install espeak-ng, depois no script sobrescreva espeakng_loader.get_data_path/get_library_path → /opt/homebrew/share/espeak-ng-data + /opt/homebrew/lib/libespeak-ng.dylib. |
| Inglês é mais denso de dizer do que ZH/JA → dublagem atrasa | NÃO use atempo (>1,25× = artefatos de vocoder) — re-sintetize na velocidade nativa do Kokoro. NÃO persiga drift com um cursor contínuo (cursor-start catch-up): empiricamente ele acumula ~+5s no meio do vídeo em fala densa consecutiva. Em vez disso: (a) ancore cada enunciado ao seu início verdadeiro (= a linha do tempo da legenda/vídeo) para que o drift não possa acumular; (b) agrupe segmentos ASR adjacentes em enunciados do tamanho de frases para que uma linha longa em inglês pegue tempo emprestado de um vizinho curto — o ajuste por segmento truncou 59% das linhas em um limite de 1,9×; o agrupamento em frases reduziu isso para ~0. Ancorar mata o drift, NÃO a ultrapassagem — um bloco cujo áudio excede seu intervalo ainda colide com o próximo ponto de ancoragem. Ordem de fallback para estouro: (1) tradução mais concisa, (2) re-síntese mais rápida dentro do limite de ~1,9× ("compressão" = re-síntese com speed nativo OU texto mais curto — NÃO atempo), (3) congelar e estender o slide abaixo dele (veja Time-density), (4) pegar emprestada a folga de um vizinho de baixa densidade; último recurso, limite rígido + fade de 30 ms no final (a legenda ainda carrega a linha completa). |
| Legendas em inglês aparecem SOBRE o vídeo, enormes | O padrão SRT→libass usa PlayResY=288 → a fonte/margem escala ~4,4×. Emita ASS com PlayResY explícito = altura com padding. E letterbox (adicione uma barra inferior, Alignment=2) para que o inglês não colida com as legendas nativas queimadas da fonte (você não pode removê-las). |
| OCR Vision "precisa de um script personalizado" | Não — pip install ocrmac; ocrmac.OCR(f, language_preference=["zh-Hans","en-US"]).recognize() retorna (text, conf, (x,y,w,h)) normalizado, origem canto inferior esquerdo. |
| Reconciliar ASR com a legenda na tela | A legenda inferior É a verdade fundamental autoritativa para a narração — ótima verificação cruzada para o ASR. MAS os criadores colocam legendas de piada (absurdo ≠ fala); nunca substitua cegamente o ASR pelo OCR. Sinalize divergências, julgue cada uma. |
| ASR achata silenciosamente um trocadilho homofônico | A piada pode ser o homófono: 人工智能 (IA) falado como 人工痔能 ("tecnologia de hemorroidas"), ou 搁置 ("engavetar o projeto") como 割痔疮 ("remoção de hemorroida"). O Whisper "corrige" o trocadilho raro para a palavra comum, então a piada nunca chega à transcrição — e nenhuma correção de nome próprio a recupera. Os criadores geralmente escrevem o trocadilho na legenda queimada, então aqui o OCR não é uma verificação cruzada — é geralmente o único caminho de recuperação confiável (para um homófono escrito, a legenda vence; o oposto do caso de legenda de piada acima). Sinal: uma linha parece estranhamente literal, ou a reação/imagem não corresponde às palavras → faça OCR daquela legenda antes de traduzir. |
| Usar Whisper antes de verificar as legendas do YouTube | yt-dlp --list-subs custa 5s e compensa de três maneiras. (1) A tag <lang>-orig = o verdadeiro idioma de origem do vídeo; se já for o seu ALVO (ex.: en-orig), todo o pipeline é desnecessário — pare antes de baixar e usar o Whisper. (2) Uma faixa manual/criador (idioma de origem) geralmente vence o Whisper: pontuação real, nomes próprios corretos, sem [music] injetado. (3) Uma legenda automática -orig é um segundo ASR gratuito — diferencie-a contra o Whisper para revelar exatamente as linhas que precisam de correção humana. Medido em uma palestra de 16 min em inglês: 98,2% de concordância de palavras, mas os dois mecanismos divergiram em Arthur Andersen (YT, correto) vs Anderson (Whisper, errado) e ambos erraram um nome de marca (Saily → "Saylite"/"Saley") — a verificação cruzada sinaliza isso; nenhum mecanismo sozinho detecta. Armadilha: os outros 100+ idiomas em --list-subs são auto-TRADUÇÕES do YouTube, não faixas do criador — não confunda uma linha zh-Hant lá com uma fonte real em chinês. |
| Sobrepor cada caixa em chinês | Em vídeos de comentário, muito do "conteúdo" na tela (artigos, palestras incorporadas, tweets) já está em inglês com chinês sobreposto — sobrepor é redundante e poluído. Escopo para elementos apenas em chinês. |
| Sobreposição estática em texto rolante | A sobreposição por intervalo (posição média) não consegue rastrear rolagem contínua — o chinês fica descoberto. Precisa de OCR quadro a quadro + rastreamento de caixa; geralmente não vale a pena (esse conteúdo é do tipo já em inglês). |
| Manter clipes originais em inglês audíveis | Onde a fonte reproduz clipes reais em inglês (entrevistas/demos), passe o áudio original em volume total nas lacunas; reduza-o (~0,1) apenas sob a dublagem. |
| Reutilizar scripts em um NOVO vídeo | Adapte, mas verifique comprimentos/caminhos codificados. render_overlay.py derivava a duração do overlay de uma contagem de quadros fixa — em um vídeo mais longo, o overlay parava silenciosamente no meio. Derive a duração de len(glob('f1/f_*.png')), não de uma constante. Retraduza en.srt + translations.json por vídeo; a asserção de pareamento de índice + uma verificação pontual lado a lado detecta desalinhamento de tradução/chave. |
Time-density: retemporize a linha do tempo, não apenas a dublagem
O inglês carrega ~1,5–2× menos bits/segundo que o mandarim/japonês, então uma dublagem fiel está sempre lutando por tempo de áudio em uma fonte densa. Comprimir a dublagem (velocidade nativa) é uma alavanca e atinge o limite de ~1,9× antes de soar apressado. A maior: a linha do tempo visual é elástica — retemporize-a para encontrar a dublagem. Três alavancas, classificadas da mais barata primeiro:
- Tradução mais concisa primeiro (denso→mais curto). Reformule as linhas mais densas de forma mais curta. Grátis, não perde nada que o espectador veja, e a legenda queimada ainda carrega o significado completo se a dublagem cortar um pouco. Esta é a principal alavanca para tomadas de cabeça falante (você não pode congelar um rosto em movimento sem trepidação; re-cortar com B-roll geralmente está fora do escopo para um trabalho de localização no lugar).
- Congele e estenda slides estáticos (compre tempo onde a pressão está). A dublagem corre mais forte exatamente quando o narrador está lendo uma captura de tela/slide — e esse quadro é estático, então segure-o por uma fração a mais em uma pausa natural para ganhar tempo de áudio. Quase invisível SE você respeitar três condições: (i) é uma edição de linha do tempo — tudo a jusante se desloca pela pausa, então insira o congelamento e regenere o tempo das legendas/dublagem/overlay contra a linha do tempo com pausa (ou segure todas as faixas juntas no mux), não apenas ancore ao tempo original; (ii) congele apenas onde o fundo de áudio também pausa — uma pausa sobre música contínua/som ambiente/áudio original dessincroniza o fundo (estenda/cruzfade, ou congele apenas sob a dublagem onde o fundo está baixo/silencioso); (iii) "estático" deve ser verdadeiramente estático — um cursor, cursor piscante, ponteiro laser ou webcam inserida no quadro quebra a ilusão. Faça muitas pausas pequenas, nunca um único arrasto longo (uma pausa de vários segundos parece um travamento/buffer).
- Recupere folga de trechos de baixa densidade (o inverso — menos denso→denso). Introduções, encerramentos, panorâmicas lentas têm espaço extra; deixe a dublagem respirar lá, ou pegue essa folga emprestada para um trecho denso.
Por que isso funciona: densidade e congelabilidade se correlacionam. A narração é mais densa porque ele está explicando um slide — e slides são exatamente o que é congelável. Então a maior parte da pressão de tempo tende a cair onde você pode absorvê-la. Meça, não assuma: marque cada segundo como estático por movimento quadro a quadro (mean|quadro − anterior| em escala de cinza reduzida) — mas o limite é uma calibração por vídeo (ruído de codec, bitrate, fps, granulação tudo o move; ≈4/255 foi o valor de um vídeo), e uma média de quadro inteiro perde movimento localizado (cursor/cursor/ponteiro) → mascare a faixa de legenda e verifique uma região estática, não o quadro inteiro. Em seguida, intersecte a máscara estática com os blocos de dublagem rápida para ver quanta pressão é congelável. Em um vídeo explicativo de 10 min medido, um teto calmo de 1,4× precisava de ~14s recuperados e 74% disso estava sobre slides estáticos congeláveis (26% sobre a cabeça falante não congelável → texto mais conciso) — mas essa divisão é vídeo por vídeo. Ultrapassar o tempo de execução da fonte é aceitável para um arquivo independente; nada externo sincroniza com ele.
Pontos de decisão para levantar com o operador
- Escopo visual: direcionado (apenas chinês, recomendado) vs completo (também parágrafos de artigo — degrada em rolagem).
- Voz da dublagem: qual voz Kokoro grátis; ofereça pago (ElevenLabs) apenas se eles relaxarem a restrição de sem faturamento.
- Áudio: substituição completa vs manter o original com volume reduzido sob a dublagem.
- Esforço de ritmo: aceitar uma dublagem rápida (legendas carregam o significado completo) vs retemporizar para um teto calmo (texto mais conciso + congelar e estender slides — custa uma medição + re-mux). Ofereça o número da medição para que eles possam decidir.
Verifique (não confie — veja)
Amostre quadros do mp4 FINAL (Leia os JPGs) em: um momento de narração, um momento de slide/overlay, uma lacuna de clipe em inglês e a última linha — confirme sincronia da legenda, posicionamento do overlay, letterbox e duração A/V correspondente. Pixels queimam permanentemente. Amostre um momento de slide DENSO especificamente (onde a dublagem corre) — é aí que a sincronia/drift quebra primeiro; a legenda chinesa queimada naquele timestamp é seu oráculo de sincronia. (Você não pode testar o áudio sozinho — a sincronia é estrutural via a linha do tempo ancorada; diga isso e peça ao operador para a última verificação auditiva.)
Compõe com
hardsubbing-video (os footguns de queima ffmpeg/libass — etapa 8 deste skill).