Pequena ferramenta de linha de comando que baixa legendas automáticas em português de vídeos do YouTube, transforma-as em texto corrido legível, detecta o formato do vídeo (filme / entrevista / outro) e prepara um sumário — usando um LLM (qualquer endpoint compatível com a API da OpenAI) ou, na falta de credenciais, um template para preenchimento manual.
Pensado para apoiar trabalho de sala de aula, pesquisa e arquivamento de conteúdo audiovisual: a partir de uma URL, o usuário obtém uma transcrição limpa em prosa e um sumário (automático ou template) estruturado conforme o formato detectado.
Idiomas de legenda configuráveis (
ptpor padrão). O texto de saída acompanha o idioma da legenda.
O pipeline tem quatro estágios independentes — cada um pode ser executado isoladamente e re-aproveita artefatos já baixados.
| Estágio | Entrada | Saída | Descrição |
|---|---|---|---|
download |
URL do YouTube | output/<ID>.<lang>.vtt |
Baixa apenas as legendas (automáticas ou manuais, sem o vídeo), via yt-dlp. Se nenhuma legenda existir no idioma pedido, cai para o fallback de transcrição local de áudio (ver abaixo). |
clean |
.vtt |
output/<ID>.<lang>.txt |
Limpa marcações VTT, remove duplicatas de rolagem, une linhas curtas em parágrafos legíveis. |
detect |
metadados do vídeo | rótulo filme / entrevista / outro |
Heurística por palavras-chave em título e descrição, com regra especial para transmissões ao vivo. |
summarise |
metadados + formato + texto | output/<ID>.<lang>.summary.md |
Se um endpoint LLM compatível com OpenAI estiver configurado, gera o sumário final via API; caso contrário, emite um template com placeholders para preenchimento manual. |
ℹ️ Sobre o estágio
summarise: quando o LLM está configurado (OPENAI_BASE_URL+OPENAI_API_KEY, ou as flags--llm-base-url/--llm-api-key), o sumário é produzido automaticamente. Sem credenciais, o estágio emite um template com marcadores a preencher. As regras de conteúdo por formato estão descritas abaixo.
- Entrevista: resumo dos principais pontos levantados, com contexto dos entrevistados e do meio de comunicação (canal do vídeo).
- Filme: busca externa de sinopses e críticas; levantamento de pontos para uso em sala de aula e de fragilidades/críticas a pontos fracos.
- Outro: sem template específico; revisão manual.
- Python 3.10+ (usa
list[str],Path | None,from __future__ import annotations). - yt-dlp — recomendada a versão mais recente (via
pip/conda). Versões antigas, como a empacotada em algumas distribuições Linux, falham em vídeos atuais do YouTube com erros do tipoRequested format is not availableouDid not get any data blocks, causados pelo novo streaming SABR e pela flagxpedo timedtext. - Navegador com sessão ativa no YouTube (opcional, mas necessário para muitos vídeos). O padrão é ler cookies do Chrome (
--cookies-from-browser chrome); Firefox também funciona. - ffmpeg e um backend Whisper — apenas para o fallback de transcrição local (opcional):
whisper-cli(whisper.cpp) ou o CLI do openai-whisper. Sem eles, vídeos sem legenda apenas geram um aviso.
Versões antigas do yt-dlp (como as empacotadas em algumas distribuições Linux via apt) falham em vídeos atuais do YouTube com erros do tipo Requested format is not available ou Did not get any data blocks, causados pelo novo streaming SABR e pela flag xpe do timedtext. Por isso, prefira instalar a versão mais recente por um gerenciador atualizado:
# Via pip (qualquer ambiente)
pip install -U yt-dlp
# Via conda
conda install -c conda-forge yt-dlpO estágio summarise produz o sumário automaticamente se houver um endpoint LLM compatível com a API Chat Completions da OpenAI configurado. Funciona com a própria OpenAI, com serviços autodomesticáveis (Ollama, vLLM, LM Studio), com provedores nacionais/regionais e com qualquer gateway que fale esse protocolo. Sem credenciais, cai graciosamente num template para preenchimento manual.
A configuração pode ser feita por variáveis de ambiente (recomendado) ou por flags na linha de comando:
| Item | Variável de ambiente | Flag CLI | Padrão |
|---|---|---|---|
| URL-base da API | OPENAI_BASE_URL |
--llm-base-url |
vazio (ex.: https://api.openai.com/v1) |
| Chave de API | OPENAI_API_KEY |
--llm-api-key |
vazio |
| Modelo | OPENAI_MODEL |
--llm-model |
gpt-4o-mini |
| Máx. de tokens da resposta | OPENAI_MAX_TOKENS |
--llm-max-tokens |
2000 |
| Timeout (s) | OPENAI_TIMEOUT |
--llm-timeout |
180 |
Exemplos:
# OpenAI oficial
export OPENAI_API_KEY="sk-..."
python3 yt_sumarios.py "<url>" --stage summarise
# Provedor compatível qualquer
export OPENAI_BASE_URL="https://llm.exemplo.com/v1"
export OPENAI_API_KEY="..."
export OPENAI_MODEL="meu-modelo"
python3 yt_sumarios.py "<url>"
# Tudo via flags (sem alterar o ambiente)
python3 yt_sumarios.py "<url>" --stage summarise \
--llm-base-url "https://llm.exemplo.com/v1" \
--llm-api-key "..." \
--llm-model "meu-modelo"A implementação usa apenas a stdlib (urllib.request), sem nenhuma dependência adicional. As instruções enviadas ao modelo variam conforme o formato detectado (entrevista/filme/outro), conforme as regras abaixo. Se a chamada ao LLM falhar (rede, auth, contexto excedido), o estágio avisa no stderr e grava o template.
⚠️ Vídeos longos: um vídeo de ~107 min gera ~90 KB de transcrição. Verifique se o modelo escolhido tem janela de contexto suficiente, ou use um modelo de contexto longo.
Quando o vídeo não tem legenda alguma no idioma pedido, o estágio download emite um aviso (não um erro) e, se um backend de transcrição estiver disponível, baixa apenas o áudio (bestaudio, sem vídeo) e transcreve localmente com Whisper. O resultado é gravado como output/<ID>.whisper.txt e segue direto para o estágio summarise (não passa pelo clean, que é específico de VTT).
O backend é detectado automaticamente: prefere whisper-cli/whisper-cpp (whisper.cpp) e, na ausência deste, usa o CLI whisper do pacote openai-whisper. Ambos são invocados como binários externos via subprocess, como o yt-dlp — o projeto continua sem dependências Python.
| Item | Variável de ambiente | Flag CLI | Padrão |
|---|---|---|---|
| Binário do Whisper | WHISPER_BIN |
--whisper-bin |
auto-detetado |
| Modelo | WHISPER_MODEL |
--whisper-model |
large-v3-turbo (CUDA) / small (CPU) |
| Dispositivo | WHISPER_DEVICE |
--whisper-device |
auto |
| Desativar fallback | — | --no-whisper |
fallback ativo |
Sobre o modelo padrão: large-v3-turbo (809M parâmetros, ~3 GB de VRAM em fp16) é o maior modelo que cabe com folga numa GPU NVIDIA de 4 GB — o medium da mesma família precisa de ~5 GB e estoura. Em CPU, o padrão cai para small (244M), o teto razoável para transcrição em tempo aceitável. Para pinar um modelo fixo:
# Sempre usar turbo (baixa sozinho na primeira execução, se necessário)
export WHISPER_MODEL=large-v3-turbo
# Ou via flag
python3 yt_sumarios.py "<url>" --whisper-model small
# Desligar o fallback por completo
python3 yt_sumarios.py "<url>" --no-whisperpython3 yt_sumarios.py "https://www.youtube.com/watch?v=<ID>"Executa os quatro estágios em sequência e grava os três artefatos em output/.
python3 yt_sumarios.py "<url>" --stage download # só baixa a legenda
python3 yt_sumarios.py "<url>" --stage clean # limpa o .vtt existente
python3 yt_sumarios.py "<url>" --stage detect # só classifica o formato
python3 yt_sumarios.py "<url>" --stage summarise # gera o sumário (LLM) ou template .md# Mudar o idioma da legenda (default: pt)
python3 yt_sumarios.py "<url>" --lang en
# Mudar o diretório de saída (default: output/)
python3 yt_sumarios.py "<url>" --work /tmp/ytO módulo clean_subtitle.py também roda sozinho sobre qualquer arquivo .vtt:
python3 clean_subtitle.py entrada.vtt -o saida.txtyt_sumarios.py # Orquestrador com 4 estágios, cada um executável isoladamente
└── clean_subtitle.py # Limpeza pura de VTT -> texto corrido (importado pelo orquestrador)
output/ # Artefatos por vídeo: <ID>.<lang>.vtt, .<lang>.txt, .<lang>.summary.md
fetch_metadata(url)→VideoInfo(título, canal, duração, categorias, descrição, idiomas de legenda disponíveis)download_subs(url)→output/<ID>.<lang>.vtt— tenta o cliente padrão do yt-dlp e, se necessário, o cliente embutido (web_embedded), que serve legendas automáticas bloqueadas por PO token; sem legendas no idioma, retorna avisotranscribe_audio(url)(fallback) →output/<ID>.whisper.txtquando não há legendasclean_vtt_file(vtt)→output/<ID>.<lang>.txt(prosa corrida, parágrafos por minuto)detect_format(info)→"filme" | "entrevista" | "outro"summarise(info, fmt, txt)→output/<ID>.<lang>.summary.md(sumário via LLM, se configurado; caso contrário, template)
Legendas automáticas do YouTube vêm com:
- marcações de karaokê (
<c>...</c>, timestamps inline); - linhas duplicadas a cada rolagem (scroll-in: cada cue repete a linha anterior e acrescenta conteúdo novo);
- marcadores de mudança de locutor (
>>,>>).
O limpador, em ordem: remove marcações, converte linhas de timestamp HH:MM:SS.mmm --> ... em marcadores HH:MM, descarta o cabeçalho VTT, remove duplicatas atravessando fronteiras de timestamp e une parágrafos curtos em blocos legíveis (~320 caracteres), preferindo pontos de fim de frase.
Para cada vídeo, em output/:
| Arquivo | Conteúdo |
|---|---|
<ID>.<lang>.vtt |
Legenda original baixada. |
<ID>.<lang>.txt |
Transcrição limpa em prosa corrida (produto principal). |
<ID>.whisper.txt |
Transcrição local via Whisper (fallback, quando não há legendas). |
<ID>.<lang>.summary.md |
Sumário (via LLM, se configurado) ou template estruturado pelo formato detectado. |
- Chrome fechado: em alguns sistemas o
--cookies-from-browser chromeprecisa que o Chrome esteja fechado (o yt-dlp lê o banco SQLite de cookies). Se falhar, tentefirefox— perfis do Firefox também funcionam. - Legendas bloqueadas por PO token: alguns vídeos anunciam legendas automáticas no idioma pedido, mas o YouTube recusa entregá-las ao cliente padrão (aviso
missing subtitles languages because a PO token was not provided). O pipeline tenta automaticamente o clienteweb_embedded, que costuma contornar a restrição; se mesmo assim não houver legenda, entra o fallback de transcrição local. - Qualidade das legendas automáticas: legendas
kind=asr(reconhecimento de fala) têm qualidade variável; espere erros de transcrição (nomes próprios, siglas). O limpador não faz correção ortográfica. A transcrição via Whisper tende a ser mais precisa que as legendasasr, a custo de tempo de processamento. - Vídeos longos: um vídeo de ~107 min gera ~90 KB de
.txt. Para sumarização por LLM considere chunking ou um modelo de contexto longo. No fallback de transcrição, o áudio baixado e o WAV 16 kHz intermediário são apagados ao final; reste só o.txt. - Detecção de formato: heurística por palavras-chave (listas
FILME_KEYWORDS,ENTREVISTA_KEYWORDSno topo deyt_sumarios.py). Transmissões ao vivo nunca são classificadas como filme. Para melhorar a detecção, estenda as listas em vez de adicionar checagens inline.
Distribuído sob a licença MIT. Veja o arquivo LICENSE.
A única dependência externa em runtime é o yt-dlp, que é Unlicense (domínio público) e é invocado via subprocess — portanto sem acoplamento de licença. Todo o código do projeto usa apenas a biblioteca padrão do Python (licença PSF).