npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@adatechnology/audio-transcription-provider

v0.1.0

Published

Speech-to-text for conversational products: hosted Whisper (Groq) by default, local whisper.cpp opt-in, chainable

Readme

@adatechnology/audio-transcription-provider

Speech-to-text para produtos conversacionais. Engine hospedado (Groq/Whisper) por padrão, engine local (whisper.cpp) como opt-in, e uma cadeia para encadear os dois.

Pacote standalone: não depende de @adatechnology/meta-whatsapp-*. O que atravessa a fronteira é um buffer de áudio e um mime — qualquer canal serve.

Instalação

pnpm add @adatechnology/audio-transcription-provider

O engine padrão não tem dependências: usa fetch, FormData e Blob globais (Node 18+, Bun).

Uso

import { createGroqTranscriber } from '@adatechnology/audio-transcription-provider'

const transcriber = createGroqTranscriber({ apiKey: environment.GROQ_API_KEY })

const { text, language, durationSeconds } = await transcriber.transcribe({
  buffer: audioBuffer,
  mimeType: 'audio/ogg; codecs=opus',
})

O pacote nunca lê process.env — a chave entra por parâmetro, como todo o resto do monorepo.

Cadeia de engines

Adicionar um engine de reserva é configuração, não refactor:

import { createGroqTranscriber, createTranscriberChain } from '@adatechnology/audio-transcription-provider'
import { createWhisperLocalTranscriber } from '@adatechnology/audio-transcription-provider/whisper-local'

const transcriber = createTranscriberChain(
  [
    createGroqTranscriber({ apiKey: environment.GROQ_API_KEY }),
    createWhisperLocalTranscriber({ modelPath: '/models/ggml-large-v3-turbo.bin' }),
  ],
  { onEngineFailure: (error, { engine }) => logger.warn('transcrição degradou', { engine, error }) },
)

A cadeia tenta o próximo engine mesmo em erro definitivo, porque suporte a codec varia: o Groq recusa AMR, e o engine local com ffmpeg na frente converte AMR sem reclamar.

Erros: retriável vs. definitivo

A distinção que importa para quem consome é isRetriable.

| Erro | isRetriable | O que fazer | |---|---|---| | TranscriptionRateLimitError | true | Reenfileirar respeitando retryAfterSeconds | | TranscriptionError (5xx, rede, timeout) | true | Reenfileirar com backoff | | TranscriptionUnsupportedError | false | Encerrar — nenhum retry conserta codec | | TranscriptionError (401, arquivo grande, áudio vazio) | false | Encerrar e alertar |

Estourar cota é espera; codec desconhecido é definitivo. Sem essa distinção, quem consome só sabe "não transcreveu" e escolhe entre desistir de um áudio que funcionaria em dez minutos ou reprocessar para sempre um formato impossível.

Limites do engine hospedado (Groq)

| | Free tier | Dev tier | |---|---|---| | Requisições/min | 20 | maior | | Requisições/dia | 2.000 | maior | | Segundos de áudio/hora | 7.200 | maior | | Segundos de áudio/dia | 28.800 | maior | | Tamanho máximo | 25MB | 100MB |

Nota de voz do WhatsApp não passa de 16MB, então o teto de tamanho não encosta. A cobrança tem piso de 10 segundos por requisição — cem áudios de 2s custam o mesmo que cem de 10s.

Formatos aceitos: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, webm. OGG/Opus — o que a Meta entrega — vai direto, sem conversão.

Engine local (opt-in)

Importado de @adatechnology/audio-transcription-provider/whisper-local, subpath separado para que quem usa só o hospedado não carregue node:child_process nem a exigência de binários na imagem.

Exige na imagem: ffmpeg (o whisper.cpp só lê WAV PCM 16kHz mono) e o binário whisper-cli mais um modelo ggml. O trecho de Dockerfile Alpine está no cabeçalho de whisper-local.service.ts.

Use large-v3-turbo (~1.6GB). Os modelos menores não erram só um pouco mais em pt-BR: trocam palavras e entram em loop de repetição em áudio de celular, e o que sai não é uma transcrição pior, é uma transcrição inútil. Como o turbo tem apenas 4 camadas de decoder, ele é muito mais rápido que o large-v3 cheio. small (~488MB) segue aceitável onde a memória da imagem for o limite; base (~148MB) não vale a economia.

Medido com large-v3-turbo em áudio real de reunião gravado por celular (7min38s, m4a): 61,9s de ponta a ponta — conversão ffmpeg incluída — em Apple M3 Pro com Metal, sem nenhuma repetição consecutiva acima de 2 segmentos em 530. Esse número é de GPU de laptop; em CPU de container Linux espere bem mais lento, e é por isso que o timeout padrão é de 10 minutos. Meça no seu runner antes de dimensionar a fila.

O engine local roda com --max-context 0: cada janela de 30s decodifica sem receber o texto da anterior como prompt, que é justamente o que realimenta o loop de repetição.

Testes

bun test

fetchImplementation é injetável — a suíte do engine hospedado roda inteira sem rede.