DEV Community

chanoch kamelhar
chanoch kamelhar

Posted on

Rodando Whisper no navegador para transcrever áudios do WhatsApp Web, sem servidor

Eu mantenho o WA Folders, uma extensão do Chrome para o WhatsApp Web (pastas, lembretes, busca, exportação). Na versão 1.2.6, que entrou na Chrome Web Store no dia 8 de outubro, coloquei um botão Transcrever embaixo das mensagens de voz. A regra era não ter servidor: o áudio não podia sair do computador da pessoa, a não ser que ela escolhesse usar a própria chave de API. Este post conta como ficou e o que deu errado no caminho.

A arquitetura

  • Motor: Whisper via transformers.js, com ONNX Runtime em WebAssembly. Nada de WebGPU por enquanto.
  • Onde roda: num documento offscreen da extensão (chrome.offscreen, motivo WORKERS), com a inferência num Worker. Testei outras opções antes. O service worker não cria Workers nem decodifica áudio com a API de áudio do DOM. Um iframe da extensão dentro da página do WhatsApp não fica com isolamento de origem cruzada, então não tem threads. O offscreen serve todas as abas do WhatsApp com uma instância do modelo e sobrevive a um reload da página.
  • Manifest: a CSP das páginas da extensão precisa de 'wasm-unsafe-eval', senão o WebAssembly é recusado. Coloquei também COOP same-origin e COEP require-corp, para o offscreen ficar com crossOriginIsolated: true e o ONNX Runtime poder usar threads. A permissão offscreen não mostra aviso para o usuário, então quem já tinha a extensão não precisou aprovar nada de novo.
  • Ciclo de vida: o documento offscreen só é criado no primeiro clique em Transcrever, e o modelo é descarregado depois de alguns minutos sem uso, para devolver a memória.

O modelo e o download

São dois modelos quantizados (q8):

Opção na interface Modelo Tamanho
Preciso (padrão) whisper-small 251,8 MB
Leve whisper-base 79,7 MB

No primeiro uso, antes de baixar qualquer coisa, a extensão mostra o tamanho e pergunta: "Baixar e transcrever", "Usar o Leve (80 MB)" ou "Cancelar". Muita gente no Brasil usa internet móvel ou plano limitado, e baixar 250 MB sem avisar seria falta de educação.

A caixa de consentimento embaixo de um áudio de demonstração: tamanho do download, aviso de que o áudio pode aparecer como reproduzido, e os botões

Os arquivos vêm do Hugging Face, fixados num commit (revision) específico, e cada arquivo tem o SHA-256 conferido depois do download. Os arquivos em cache são conferidos de novo uma vez por sessão. O Hugging Face não está nas host_permissions. Os pesos são carregados como dados, nunca como código, o que importa para a regra do Manifest V3 que proíbe código remoto.

O que deu errado

1. O chunk_length_s: 30 do pipeline não serviu. Com áudios mais longos, o base entrava em loop repetindo frases, e o small pulava pedaços de um áudio de 72 segundos. Troquei por segmentação própria: detecto as regiões com fala, transcrevo só elas, com 0,25 s de folga e janelas de no máximo 28 s.

2. O Whisper inventa texto no silêncio. Num áudio só com silêncio ou ruído, o base escreveu frases inteiras e o small devolveu coisas como [Música]. Quem já viu legenda automática conhece o famoso "Legendas pela comunidade Amara.org". A proteção ficou assim: um limiar adaptativo, max(−50 dBFS, p10 + 12 dB). Se tiver menos de 0,3 s de fala, a extensão mostra "Nenhuma fala detectada" e não conta no limite diário. Janelas que só têm texto entre colchetes ou parênteses são descartadas, e tem uma lista de frases de crédito conhecidas. Por segurança, tem também uma checagem de loop: se mais de 35% dos trigramas se repetem, roda de novo com repetition_penalty 1.2 e no_repeat_ngram_size 4.

3. Pegar o áudio sem tocar a mensagem. O WhatsApp Web descriptografa a mensagem de voz e cria um blob audio/ogg antes de alguém apertar play. Um script no MAIN world guarda as referências desses blobs (no máximo 20, 20 MB e 15 minutos, e nada sai da página). No clique, ele escolhe o blob cuja duração no cabeçalho Ogg bate com a duração mostrada no balão, e só usa se a correspondência for única. Quando não dá, o plano B é clicar no play do próprio WhatsApp com o som cortado. Num teste real, o play() do WhatsApp chegou 6 ms depois do meu pause(), e restaurar o volume 400 ms depois fez o áudio tocar inteiro com som. Agora todo play() daquele áudio fica mudo, pausado e rebobinado até 3 s depois da captura, e o volume só volta com o áudio parado. O efeito colateral que não dá para evitar: o WhatsApp pode marcar a mensagem como reproduzida, e a extensão avisa isso antes.

4. Português de verdade é mais difícil que benchmark. Nos áudios de teste do FLEURS em português, o small errou bem menos palavras que o base, por isso ele é o padrão. Mas mensagem de WhatsApp tem ruído, gíria, gente falando rápido e compressão Opus. Na prática erra nomes e uma palavra ou outra, e a interface tem um botão "Transcrever de novo".

Transcrição real feita com o modelo Leve de um áudio de demonstração sintetizado:

Tradução, já que estava ali

A mesma versão traduz mensagens entre português, inglês e espanhol, com a Translator API e a LanguageDetector API que vêm no Chrome. Elas rodam no computador. Duas coisas me pegaram:

  • No primeiro uso num perfil novo, o Translator.create() quase sempre falhava com NotSupportedError enquanto o Chrome baixava a biblioteca do tradutor, cerca de 29 MB (16 de 18 perfis nos meus testes). A interface trata isso como "preparando o tradutor, clique de novo em alguns segundos", e não como erro.
  • Às vezes o tradutor devolve uma frase pela metade, com parte ainda no idioma original. A extensão detecta isso e tenta de novo até três vezes, a última com um tradutor novo. Ficou mais raro, mas ainda acontece.

Para traduzir o rascunho, o texto traduzido entra na caixa de mensagem do WhatsApp, que usa o editor Lexical. Escrever direto no DOM não funciona, porque o Lexical desfaz. A solução foi selecionar tudo e colar pelo caminho que o próprio editor trata, e depois conferir o resultado. O botão Desfazer usa o mesmo caminho e devolve o texto original exatamente como estava. A extensão nunca aperta Enter: a pessoa confere e envia.

Limites e o que falta

  • Testei no Linux e num Chrome real com o WhatsApp Web. No Windows e no Mac, que é onde está a maioria dos usuários, ainda não medi nada.
  • Mensagens com mais de 15 minutos não são transcritas.
  • O plano grátis tem 5 transcrições e 20 traduções por dia. O Pro não tem limite.

Se quiser testar ou olhar o comportamento: https://chromewebstore.google.com/detail/hahfpdlfieloofgjhanljbbnakinfhil?hl=pt-BR&utm_source=devto&utm_medium=article&utm_campaign=ptbr-whisper

Eu sou o autor da extensão. Ela não tem conta, não tem servidor nosso e nunca envia mensagens. Se alguém já rodou Whisper no navegador com WebGPU em máquina modesta, queria saber como foi. É o próximo teste que quero fazer.

Top comments (0)