Grave, transcreva e gere atas de reunião — tudo 100% local, em português brasileiro, com identificação de falantes (diarização).
O projeto começou como uma CLI (transcrever) para transcrever arquivos de
áudio de reunião localmente, com WhisperX:
faster-whisper (transcrição), wav2vec2 (alinhamento por palavra) e
pyannote.audio (diarização). Depois evoluiu para uma extensão Chrome
que grava a reunião ao vivo (Meet/Teams/Zoom), transcreve pelo backend
local — que reusa o mesmo pipeline da CLI — e abre uma página de revisão com
player, edição de falantes e ata gerada por IA.
Seu áudio nunca sai da máquina 🔒 — a única etapa que usa um serviço externo é
a ata (opcional), gerada pelo claude CLI da sua conta.
| Componente | O que faz | Código | Instalação / docs |
|---|---|---|---|
CLI transcrever |
Transcreve qualquer arquivo de áudio (m4a, mp3, wav, mp4…) com diarização | transcrever/ |
Setup rápido abaixo |
| Backend FastAPI (porta 8765) | Fila de transcrição + geração de ata; usado pela extensão | server/ |
server/README.md |
| Extensão Chrome | Grava a reunião (aba + mic), revisão completa, ata, biblioteca de reuniões | extension/ |
extension/README.md |
| Contratos & prompt da ata | Como as partes se integram; prompt padrão da ata | docs/ |
CONTRACTS.md · PROMPT_ATA.md |
Roteiro de embarque:
- Todo mundo começa igual: instale
ffmpeg+ rode./scripts/install.sh- login no Hugging Face (Setup rápido) — isso habilita o pipeline de transcrição usado por CLI e backend.
- Só quer transcrever um arquivo? Use a CLI (Uso) e pare aqui.
- Quer gravar reuniões no Chrome? Suba o backend
(
./scripts/install_backend_service.sh) e carregue a extensão (como). - Quer a ata automática? Configure o
claudeCLI (como).
brew install ffmpeggit clone git@github.com:felipemaion/MeetingTranscriptor.git
cd MeetingTranscriptor
./scripts/install.sh
source .venv/bin/activateO
install.shcria o venv e instala tudo. WhisperX puxa PyTorch (~2 GB), então a primeira instalação demora alguns minutos.
huggingface-cli login
# cole seu token ReadSem token, a diarização não funciona (a transcrição funciona normalmente).
Logado na HF, abra estas duas páginas e clique em "Agree and access repository":
- https://huggingface.co/pyannote/speaker-diarization-3.1
- https://huggingface.co/pyannote/segmentation-3.0
Pronto. Da próxima vez não precisa mais.
transcrever caminho/para/reuniao.m4aGera em ./saidas/:
reuniao.txt— texto separado por falante e com timestampsreuniao.srt— legendas comFALANTE 1: ...reuniao.json— payload completo com segmentos, palavras e speakers
Exemplo do .txt:
[00:00:01] FALANTE 1: Olá pessoal, vamos começar a reunião.
[00:00:04] FALANTE 2: Beleza, eu trouxe os dados de ontem.
[00:00:09] FALANTE 1: Perfeito. Pode compartilhar a tela?
Você ouve o início da reunião e mapeia:
transcrever reuniao.m4a \
--mapeamento "SPEAKER_00=Felipe,SPEAKER_01=Ana,SPEAKER_02=Bruno"Saída:
[00:00:01] Felipe: Olá pessoal, vamos começar a reunião.
[00:00:04] Ana: Beleza, eu trouxe os dados de ontem.
Também aceita um arquivo JSON:
{ "SPEAKER_00": "Felipe", "SPEAKER_01": "Ana" }transcrever reuniao.m4a --mapeamento ./mapa.jsonSe você sabe quantos falantes tem na reunião, diga ao modelo — a precisão melhora muito:
transcrever reuniao.m4a -n 3 # exatamente 3 falantes
transcrever reuniao.m4a --falantes-min 2 --falantes-max 5# Sem diarização (mais rápido, áudio só com você)
transcrever reuniao.m4a --sem-diarizar
# Trocar modelo
transcrever reuniao.m4a -m medium # mais leve
transcrever reuniao.m4a -m distil-large-v3 # 6× mais rápido que large-v3
# Pasta de saída diferente
transcrever reuniao.m4a -o ~/Desktop/transcricoes
# Apenas TXT
transcrever reuniao.m4a -f txt
# Detectar idioma automaticamente
transcrever reuniao.m4a -l auto
# Ajudar com nomes próprios e jargões
transcrever reuniao.m4a --prompt "Reunião com Felipe Maion sobre o projeto Cation, GraphQL, FastAPI."
# Inspecionar config + checar dependências
transcrever --infoTudo que está nas flags pode ir no .env para virar default:
WHISPER_MODEL=large-v3
WHISPER_LANGUAGE=pt
WHISPER_DEVICE=cpu
WHISPER_COMPUTE_TYPE=int8
WHISPER_BATCH_SIZE=8
DIARIZAR=1
FALANTES_MIN=2
FALANTES_MAX=6WhisperX no Mac roda em CPU (CTranslate2 ainda não tem backend Metal). Os tempos típicos para 60 minutos de áudio em M1/M2 com int8 são:
| Modelo | Transcrição | Diarização | Total |
|---|---|---|---|
small |
~3 min | ~3 min | ~6 min |
medium |
~7 min | ~3 min | ~10 min |
distil-large-v3 ⭐ |
~6 min | ~3 min | ~9 min |
large-v3 |
~12 min | ~3 min | ~15 min |
Para reuniões longas, distil-large-v3 costuma ser o melhor custo/benefício em pt-br: qualidade quase idêntica ao large-v3 e bem mais rápido.
Extensão que grava reuniões (Meet/Teams/Zoom) e abre uma página de revisão completa. O que ela faz hoje:
Gravação e falantes
- Grava aba + microfone em canais separados (estéreo): você é identificado deterministicamente pelo canal do mic.
- Pedindo mais de 2 falantes, a diarização vira híbrida: o pyannote subdivide só o canal remoto (os participantes da chamada) — ver CONTRACTS §2.1.
- Renomear falantes (persistido por reunião), reatribuir falante por linha, edição inline do texto, localizar e substituir.
Ata de reunião (via claude CLI — requer instalado e autenticado)
- Prompt padrão profissional: resumo executivo, decisões e tabela de tarefas com responsável e prazo ("em aberto" quando não dito).
- Cabeçalho automático com título, data, duração e participantes.
- Prompt configurável na tela ⚙️: mostra o prompt em uso, salva prompts nomeados e alterna entre eles e o padrão.
- Exporta copiar / .md / .html / PDF; histórico de atas por reunião.
Persistência e revisão
- Biblioteca de reuniões (IndexedDB): transcrição, ata e histórico de versões reaparecem ao reabrir. Player sincronizado com a transcrição.
- Configurações: URL do backend, modelo e prompt da ata, idioma, nº de falantes padrão, ganho do microfone, transcrições em paralelo (MAX_JOBS).
A ata é gerada pelo Claude Code CLI rodando na sua máquina, com a sua conta Claude (Pro/Max) — sem API key e sem custo por token além da assinatura. Configuração única:
# 1) instalar o CLI (uma das opções)
npm install -g @anthropic-ai/claude-code
# ou: curl -fsSL https://claude.ai/install.sh | bash
# 2) autenticar: rode `claude` e siga o login no navegador
claude
# 3) testar (deve responder no terminal)
claude -p "diga oi"- O backend chama o binário
claudepelo PATH do processo. Se o serviço launchd não o encontrar (ex.: instalado via nvm), definaCLAUDE_BINcom o caminho completo (which claude) no ambiente do backend. - Sem o CLI configurado, só o botão "Gerar ata" falha — gravação e transcrição continuam funcionando (essas são 100% locais).
- O modelo usado (Sonnet/Opus/Haiku) e o prompt da ata são ajustáveis na tela ⚙️ de configurações da extensão.
Documentação por componente:
- Backend (FastAPI, porta 8765): reusa o pacote
transcrever/. Vejaserver/README.md. - Extensão (TS, Vite + @crxjs, Manifest V3): build e uso em
extension/README.md. - Contratos entre as partes:
docs/CONTRACTS.md.
# 1) backend — sempre disponível via launchd (sobe no login, reinicia sozinho)
./scripts/install_backend_service.sh
# (ou, pontual: uvicorn server.app:app --host 127.0.0.1 --port 8765 --reload)
# 2) extensão
cd extension && npm install && npm run build # carregue extension/dist/ no ChromePara remover o serviço do backend: ./scripts/uninstall_backend_service.sh.
MeetingTranscriptor/
├── transcrever/ # pipeline WhisperX + CLI
│ ├── cli.py # CLI (Typer + Rich)
│ ├── transcriber.py # pipeline (diarização por canal + híbrida + pyannote)
│ ├── diarizacao_canais.py # diarização por canal (aba=L/mic=R) + combinar_falantes
│ └── formatters.py # exportadores TXT / SRT / VTT / JSON com falantes
├── server/ # API FastAPI (porta 8765) — fila de jobs, ata via claude CLI
├── extension/ # extensão Chrome (MV3) — captura + revisão (React)
│ └── src/review/ # página de revisão: player, falantes, ata, histórico
├── docs/
│ ├── CONTRACTS.md # contratos de integração entre os componentes
│ └── PROMPT_ATA.md # prompt padrão da ata (sobrescritível na tela ⚙️)
├── tests/ # pytest (pipeline + server); testes da extensão em extension/tests/
├── scripts/ # install.sh, serviço launchd do backend
├── exemplos/ # áudios para testar
└── saidas/ # arquivos gerados pela CLI (ignorado pelo git)
| Componente | Licença | Custo |
|---|---|---|
| Whisper (modelo) | MIT (OpenAI) | grátis |
| faster-whisper / CTranslate2 | MIT | grátis |
| WhisperX | BSD-2 | grátis |
| pyannote.audio | MIT | grátis |
| pyannote/speaker-diarization-3.1 | MIT (gated) | grátis |
| ffmpeg | LGPL/GPL | grátis |
Could not download model from Hugging Face
→ Você ainda não aceitou os termos de pyannote/speaker-diarization-3.1 ou pyannote/segmentation-3.0. Volte para o passo 4 do setup.
401 Unauthorized ou Token is invalid
→ Token HF não está disponível. Rode huggingface-cli login ou exporte HF_TOKEN.
Diarização junta dois falantes em um só
→ Sotaques muito parecidos ou áudio de baixa qualidade. Use --falantes-min e --falantes-max para forçar a faixa correta.
→ Em gravações da extensão (estéreo aba/microfone), informe o Nº de
falantes na tela de revisão: com >2, a diarização vira híbrida — o canal
garante você (mic) × remotos (aba) e o pyannote subdivide só os remotos. Vários
remotos chegam mixados na mesma aba do Meet, então separar um do outro é
imperfeito; ajuste o que sobrar pelo seletor de falante por linha.
Diarização separa um único falante em vários
→ Idem: --falantes 1 força um único falante.
Travou em "Identificando falantes"
→ Diarização em CPU é mais lenta — espere alguns minutos. Se passar de 10 min para áudio de 30 min, tem algo errado: rode com --sem-diarizar para validar a transcrição primeiro.
Aviso torchcodec is not installed correctly
→ Inofensivo. O pipeline entrega o áudio pré-carregado em memória para o pyannote, então o torchcodec não chega a ser usado. O aviso já vem silenciado no código.