Communitygithub.com

read-video — Leia Vídeos por Demanda de Bytes

Lê o conteúdo de vídeos remotos sem baixar o arquivo completo. Suporta links de mais de 1700 sites (Bilibili usa adaptador personalizado, os demais usam o backend yt-dlp) e links diretos de vídeo. Usa HTTP Range e seek no ffmpeg para extrair frames ou áudio apenas dos trechos desejados; o áudio é transcrito offline com SenseVoice e os frames são enviados a modelos de visão.

O que é read-video — Leia Vídeos por Demanda de Bytes?

O skill **read-video** resolve o problema de extrair informações de vídeos remotos sem precisar baixá-los por completo. Ao invés de fazer download de todo o arquivo, ele utiliza o cabeçalho HTTP Range para solicitar apenas os bytes necessários ao redor do ponto de interesse (ex.: um frame no segundo 30 ou um trecho de áudio de 20 segundos). Isso reduz drasticamente o uso de banda e armazenamento, além de evitar problemas de copyright. O fluxo funciona em três etapas: (1) resolução do link (se for página da web, transforma em URL direta usando `resolve.py`, que suporta mais de 1700 sites via yt-dlp ou o adaptador nativo do Bilibili); (2) sondagem do servidor (`probe.py`) para verificar suporte a Range e localização do índice MP4; (3) extração pontual com `grab.py` (frames) ou `transcribe.py` (áudio transcrito com SenseVoice offline). O skill é ideal para quando um usuário pergunta "o que esse vídeo está mostrando?", "sobre o que é?", ou "o que está na tela no minuto X?". Também pode ser usado para verificar se uma URL suporta extração sob demanda. As plataformas suportadas incluem Bilibili, YouTube, Douyin, Weibo, Vimeo e muitos outros; o skill também funciona com links diretos de mídia (.mp4, .m3u8). A transição é automática: se for link de página, resolve primeiro; se for link direto, pula a resolução. O sistema mede a quantidade real de bytes baixados (via `--measure`) para garantir que nunca ultrapassa alguns por cento do arquivo total.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add RaganaBloodedge/read-video-skill

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

read-video —— Leia Vídeos por Demanda de Bytes

O que este skill resolve

O usuário envia um link de vídeo e quer entender o conteúdo. A abordagem ingênua é baixar o vídeo inteiro para processá-lo — lento, ocupa armazenamento, apresenta riscos de direitos autorais e, muitas vezes, apenas alguns segundos são necessários. Este skill faz o seguinte: busca apenas o pequeno trecho de bytes próximo ao momento desejado. A faixa de áudio é transcrita offline localmente (principal recurso para responder "sobre o que é"), e os quadros de vídeo são extraídos como keyframes e enviados a um modelo de visão para complementar a informação visual.

Referência real (MP4 de 249 MB / 9 min 56 s, sem faststart):

AçãoBytes obtidos% do total
Quadro no segundo 302,0 MB0,839%
Quadro no minuto 81,7 MB0,707%
Áudio de 20 s a partir de 480 s6,6 MB2,784%

Quando usar / Quando não usar

Use: quando o usuário fornecer um link direto de vídeo ou um link de página contendo vídeo (plataformas como Bilibili são resolvidas automaticamente por resolve.py, sem necessidade de chamadas manuais de API). Não use: para arquivos locais (leia diretamente) ou para vídeos longos que precisam de compreensão completa segundo a segundo (o custo ainda é alto; pergunte primeiro ao usuário o ponto no tempo).

Princípios rígidos

  1. Não baixe o arquivo inteiro. Em nenhuma etapa use wget/curl -O para o arquivo completo.
  2. Sonde antes de agir. Sem sondagem, o seek em servidores que não suportam Range resultará em espera inútil.
  3. -ss deve vir antes de -i. Colocar depois é seek do lado da saída, o que equivale a decodificar sequencialmente até o ponto alvo e depois descartar, degenerando para puxar o vídeo inteiro. Colocar antes é seek do lado da entrada — o demuxer pula diretamente.
  4. Mantenha a medição ativa. --measure está ativo por padrão: ele usa um proxy de streaming local para contar os bytes do upstream, sendo a única evidência concreta de que "realmente só pegou o necessário" e a prova para o usuário de que não baixou escondido o arquivo inteiro.
  5. Links de página devem ser resolvidos por script. URL de página ≠ link direto, e links diretos possuem assinaturas que expiram. Use resolve.py, ou passe o link diretamente para grab.py/transcribe.py (eles resolvem automaticamente e adicionam os cabeçalhos necessários). Não escreva chamadas de API do site na conversa — esse código sempre fica diferente a cada vez e nunca foi revisado.

Fluxo de trabalho

Passo 0: Distinguir entrada — link de página ou link direto

  • Link de página (Bilibili, YouTube, Douyin, Weibo, Vimeo… mais de 1700 sites) → resolva primeiro para link direto. Também pode passar o link diretamente para grab.py / transcribe.py, que reconhecem e resolvem automaticamente, incluindo os cabeçalhos necessários.
  • Link direto (extensões de mídia como .mp4 / .m3u8) → pode pular a resolução.

A regra está em resolvers.is_page_url(): links com extensão de mídia são tratados como diretos; todos os demais links HTTP são enviados para a camada de resolução.

Primeiro passo: Obter o stream acessível

python scripts/resolve.py "<link da página>"

A saída inclui video_url (para extrair quadros), audio_url (para transcrição, diferente do anterior), headers (cabeçalhos exigidos pela CDN), além do título, duração e um conjunto de pontos de tempo sugeridos para amostragem.

A linha Plataforma indicará qual backend foi usado: native (adaptador personalizado, atualmente Bilibili) ou yt-dlp (genérico, 1700+ sites). Para forçar, use --backend native|yt-dlp|auto.

Comando único para todo o percurso (resolução e extração encadeadas automaticamente, recomendado):

python scripts/transcribe.py --url "<link da página>" --seconds 90
python scripts/grab.py "<link da página>" --times 14 29 44 58 73 --prefix shot --outdir ./out

Segundo passo: Sondagem (recomendado para links diretos; pode pular para links de página)

python scripts/probe.py <link direto> --json

Se o link direto for de uma CDN, inclua a origem: --header 'Referer: https://www.bilibili.com'.

Observe três campos:

  • seekable — se false, pare. O servidor ignora Range, e a extração pontual degenera para um pull sequencial. Mude para "baixar e decodificar" (-i <url> sem -ss, pegando o quadro mais à frente do fluxo) ou informe ao usuário que não é possível.
  • container.moov_offset / faststart — se o índice está no início (faststart), o custo fixo é de algumas dezenas de KB; se está no final, é necessário buscar um trecho do final do arquivo (medido em cerca de 441 KB). Esse é o custo fixo para "pegar um quadro", inevitável.
  • content_length — denominador para calcular "% do arquivo total" posteriormente.

Terceiro passo: Extrair dados conforme a intenção do usuário

Usuário forneceu um ponto específico → extração pontual:

python scripts/grab.py <url> --time 183.5 --out shot.jpg

Usuário não forneceu ponto → extraia alguns quadros para ter uma ideia geral:

python scripts/grab.py <url> --times 5 60 180 420 --prefix shot --outdir /tmp/rv

Dica para seleção de pontos: pegue 4 a 6 quadros baseados na densidade de conteúdo, não distribua uniformemente por todo o vídeo (isso equivaleria a decodificar o vídeo inteiro). Se não tiver certeza, pegue 3 quadros primeiro e decida depois se vale a pena examinar um trecho específico.

Para entender o que está sendo dito → transcreva diretamente (recomendado fazer primeiro):

python scripts/transcribe.py --url <url> --segments 0:90 320:90 630:97

Um único comando realiza "extrair áudio → reconhecer localmente", produzindo texto em chinês com timestamps, sem necessidade de API externa ou inferência na nuvem. Na primeira execução, baixa automaticamente o modelo SenseVoice (cerca de 240 MB, do hf-mirror, apenas uma vez).

Para vídeos longos, extraia 3 a 4 segmentos de 90 segundos cada para cobrir a linha principal; não é necessário transcrever o vídeo inteiro — isso é muito mais rápido que analisar quadro a quadro e mais adequado para responder perguntas como "sobre o que é". Primeiro transcreva o áudio, depois use quadros para complementar a informação visual — esta é a ordem.

Após a transcrição, verifique a taxa de produção. Uma narração normal produz cerca de 4 a 6 caracteres/segundo; a saída do script inclui chars_per_second. Se estiver abaixo de 1 caractere/segundo, o script alertará ativamente. Nesse caso (música pura, gameplay sem comentários, demonstração silenciosa) não use o pouco texto como conteúdo; mude diretamente para uma amostragem mais densa de quadros (8 a 10 quadros) para análise visual — não há informação no áudio, apenas nos quadros.

Quando precisar apenas do arquivo de áudio em si (por exemplo, para enviar a outro serviço de transcrição):

python scripts/grab.py <url> --start 0 --seconds 60 --audio clip.wav

Se houver faixa de legenda embutida, leia a legenda primeiro (ffmpeg -i <url> para ver se há fluxo subtitle), que é mais preciso e rápido que ASR.

Quarto passo: Enviar quadros para o modelo de visão

grab.py apenas obtém os pixels localmente; não entende o conteúdo. Passe o caminho do jpg de saída para o modelo de visão fazer perguntas ("O que há neste quadro?", "Do que trata este trecho?"). Para múltiplos quadros, envie-os em ordem cronológica para que o modelo os encadeie em uma narrativa.

Como interpretar a saída

grab.py produz JSON, cada ação com um registro de bytes:

{
  "ok": true,
  "actions": [{
    "kind": "frame", "time": 30,
    "out_bytes": 48211,
    "upstream_bytes": 2097152,
    "requests": 4,
    "wall_seconds": 2.317
  }],
  "total_upstream_bytes": 2097152
}
  • upstream_bytes / content_length é a "fração do arquivo total". Se ultrapassar 5%, pare e revise: provavelmente a posição -ss está errada ou o servidor não aceita Range.
  • requests alto é normal: ffmpeg precisa fazer várias requisições (moov, do keyframe até o quadro alvo, cabeçalho de áudio).
  • Se ok: false, veja err_tail; os erros comuns são timeout e 5XX — o script já tem retry embutido; se ainda falhar, tente outro ponto no tempo.

Saída de transcribe.py:

{
  "ok": true,
  "text": "Esta é uma terra cercada por grandes rios e montanhas...",
  "segments": [{"start": 0, "end": 25, "text": "..."}],
  "audio_seconds": 276.7,
  "model": "SenseVoiceSmall-int8"
}

text é a concatenação de todos os segmentos; segments contém timestamps — consulte-o quando precisar saber "o que foi dito no segundo X".

Ambiente

  • Interpretador: Python do venv gerenciado localmente. No Windows, o caminho é algo como ~/.workbuddy/binaries/python/envs/default/Scripts/python.exe. Observe que é Scripts/, não bin/ como no Unix (escrever bin/python resultará em erro de arquivo não encontrado).
  • Dependências: httpx, imageio-ffmpeg (extração), sherpa-onnx, numpy (transcrição), yt-dlp (resolução de plataformas genéricas) — todas instaladas no mesmo venv; se faltar alguma, o script reportará o erro claramente. Sem yt-dlp, o Bilibili ainda funciona (via adaptador nativo), mas outros sites falharão na resolução.
  • ffmpeg: Fornecido pela imageio-ffmpeg como build estático, sem necessidade de instalação no sistema. Para especificar outro, use a variável de ambiente RV_FFMPEG ou --ffmpeg.
  • Cache do modelo ASR: ~/.workbuddy/cache/read-video/models (cerca de 240 MB, baixado automaticamente na primeira execução). Se o arquivo existir e o tamanho em bytes coincidir, o download é ignorado.

Limitações conhecidas

  • CDN frequentemente verifica a origem: As CDNs de vídeo de sites como Bilibili exigem Referer; sem ele, retornam 403. Usar resolve.py ou --page adiciona automaticamente; ao passar um link direto manualmente, lembre-se de --header 'Referer: https://www.bilibili.com' (os três scripts suportam, pode repetir).
  • Links diretos expiram: Os links diretos dos sites têm assinaturas e prazos de validade; use dentro do mesmo lote de operações. Assim que receber 403, execute resolve.py novamente.
  • Cobertura de plataformas = cobertura do yt-dlp (1700+ sites), não mantida separadamente. Se um site não for resolvido, use --backend yt-dlp para ver o erro bruto; sites que exigem login/Cookie falharão — nesse caso, desista ou forneça os cookies manualmente. O yt-dlp precisa ser atualizado periodicamente (versões antigas falham após mudanças nos sites) — pip install -U yt-dlp.
  • "Resolução bem-sucedida" ≠ "consegue extrair": A camada de resolução apenas fornece a URL. Se a extração falhar, primeiro distinga se é falha de resolução ou de rede — em testes, a máquina local não conseguia acessar YouTube/Vimeo (proxy 502), enquanto Douyin/Weibo/Xigua eram acessíveis.
  • HLS / m3u8: Não é um único arquivo; a dissecação de boxes do probe.py falhará (campo error presente). Nesse caso, grab.py ainda funciona — o ffmpeg puxa os segmentos necessários por conta própria; content_length fica vazio, impossibilitando calcular a porcentagem.
  • DASH com áudio e vídeo separados: Muitos sites (Bilibili, etc.) fornecem URLs diferentes para fluxo de vídeo e áudio. Use o fluxo de vídeo para extrair quadros e o fluxo de áudio para transcrição — usar o errado resultará em resultado vazio silenciosamente.
  • Precisão do -ss limitada pelo GOP: Para extrair um quadro de um segundo específico, é necessário decodificar a partir do keyframe mais próximo; portanto, a unidade mínima de extração é "do keyframe até o quadro alvo", não "um intervalo de um byte".
  • Homófonos no ASR: O SenseVoiceSmall funciona bem para chinês, mas nomes de pessoas, lugares e palavras antigas costumam gerar erros de homofonia (exemplo real: "阪泉之野" → "版圈之也", "涿鹿" → "涿陆"). Corrija nomes próprios antes de citá-los; não trate o texto transcrito como fato.
  • Dependências: httpx + imageio-ffmpeg (extração) + sherpa-onnx + numpy (transcrição). Se faltar, o script reportará o erro e indicará qual instalar.

Para princípios mais detalhados, registro de problemas e dados de medição, consulte references/methodology.md. A relação deste skill com o plugin dsh dsh-readvideo está em references/dsh-dual-form.md.

Habilidades Relacionadas