livecaption / README.md
Veronyka's picture
LiveCaption: legendas ao vivo e tradução locais para Mac (código-fonte, sem pesos)
f1e4744 verified
|
Raw History Blame Contribute Delete
9.13 kB
---
license: mit
language:
- pt
- es
- en
tags:
- macos
- apple-silicon
- swift
- live-captions
- subtitles
- speech-recognition
- machine-translation
- on-device
- local-first
- whisperkit
- coreml
- ollama
pipeline_tag: automatic-speech-recognition
---
# LiveCaption
**Legendas ao vivo com tradução em tempo real, 100% local, para Mac (Apple Silicon).**
Microfone → **Whisper** (WhisperKit/CoreML) → **modelo de tradução aberto** (Hy-MT2-1.8B, via Ollama) → janela flutuante só com o texto traduzido, que aparece palavra por palavra.
> **EN —** Live captions with real-time translation for macOS (Apple Silicon), fully on-device. Microphone → Whisper (WhisperKit/CoreML) → open translation model (Hy-MT2-1.8B through Ollama) → a floating window showing the translation as it is generated. Default pair is Portuguese → Spanish; the translator is any Ollama-compatible chat API, so you can swap the model. Nothing leaves your Mac. Quickstart below; commands are the same in both languages. This repository ships **source code only**: the open models are downloaded from their official repositories by a script.
Projeto pessoal, usado numa aula real de 2h44. Não é um produto: foi medido em **um** Mac (MacBook Air M2, 16 GB) e a qualidade depende dos modelos (veja "Limites").
## Por que é local
- O áudio do microfone é processado em memória, no seu Mac. Não há telemetria nem contas.
- Em execução normal a única rede usada é `localhost` (o servidor do Ollama). Os modelos são baixados uma vez pelo `fetch_models.sh`. **Exceção:** se faltar o tokenizer do Whisper na pasta de dados (por exemplo, ao apontar `LC_WHISPER_DIR` para outro modelo), o WhisperKit o baixa do Hugging Face uma vez; nenhum áudio nem texto sai.
- **Por padrão nada do que é dito é gravado em disco.** `LC_LOG=1` liga um histórico (frases PT/ES e métricas em `logs/`); `LC_SAVE_AUDIO=1` grava o áudio do microfone. Ambos são opcionais e ficam na sua pasta de dados.
- O tradutor é substituível sem recompilar: qualquer API de chat compatível com a do Ollama (`LC_MT_URL`, `LC_MT_MODEL`).
## Requisitos
- Mac com **Apple Silicon**, macOS 14 ou superior (WhisperKit/CoreML não roda em Intel).
- Só para os testes opcionais: `ffmpeg` e a voz **Luciana** (pt-BR) do macOS (Ajustes → Acessibilidade → Conteúdo falado), usadas por `gen_test_audio.sh`.
- Swift 5.9+ (Xcode Command Line Tools: `xcode-select --install`).
- [Ollama](https://ollama.com) instalado e em execução (só serve o modelo de tradução localmente).
- `hf` para baixar os modelos: `pipx install huggingface_hub` (ou `pip install -U huggingface_hub`; com o Python do Homebrew prefira `pipx`).
## Início rápido
```sh
hf download Veronyka/livecaption --local-dir livecaption && cd livecaption # ou: git clone https://huggingface.co/Veronyka/livecaption
zsh scripts/fetch_models.sh # baixa Whisper small + Hy-MT2 1.8B (~1,6 GB) e importa o tradutor no Ollama
zsh scripts/build_app.sh --install # compila e copia LiveCaption.app para /Applications
open /Applications/LiveCaption.app # aceite a permissão de microfone
```
A primeira abertura leva cerca de 1 minuto (o macOS compila o modelo para o Neural Engine); depois abre em segundos. O app é assinado só localmente (*ad hoc*): por ser compilado por você, o Gatekeeper não bloqueia.
Os modelos ficam em `~/Library/Application Support/LiveCaption/` (mude com `LC_PROJECT`). Os scripts são chamados com `zsh scripts/…` para não depender do bit de execução dos arquivos.
## Uso
Janela escura arrastável e redimensionável com o espanhol (frase mais recente em destaque). Botões **Pausar** (desliga o microfone de verdade) e **Sair**. De fora do app:
```sh
zsh scripts/livecaption.sh start | stop | pause | status # `pause` alterna pausar/retomar (SIGUSR1)
```
O rodapé mostra: tempo do Whisper, 1ª palavra da tradução, tradução completa, fim da frase→legenda e fila.
## Configuração (variáveis de ambiente)
| Variável | Padrão | Para quê |
|---|---|---|
| `LC_SRC` / `LC_DST` | `pt` / `Spanish` | idioma falado (código do Whisper) e idioma de destino (nome em inglês, vai no prompt do tradutor) |
| `LC_MT_URL` / `LC_MT_MODEL` | `http://localhost:11434/api/chat` / `hymt2-1.8b` | tradutor |
| `LC_PROJECT` | `~/Library/Application Support/LiveCaption` | pasta de dados (modelos, `glossary.json`, logs) |
| `LC_WHISPER_DIR` | `<dados>/models/whisper/openai_whisper-small` | outro modelo do Whisper |
| `LC_LOG=1` | desligado | grava histórico e métricas em `<dados>/logs/` |
| `LC_SAVE_AUDIO=1` | desligado | grava o microfone em `<dados>/logs/audio-*.f32` |
| `LC_WAV` / `LC_DURATION` | — | teste: alimenta o app com um arquivo `f32le` 16 kHz mono em tempo real |
| `LC_SNAPSHOT=<arquivo.png>` | — | depuração: desenha a janela num PNG |
Variáveis só valem ao iniciar pelo terminal: `LC_LOG=1 /Applications/LiveCaption.app/Contents/MacOS/LiveCaption`.
**`glossary.json`** (opcional, na pasta de dados; modelo em `glossary.example.json`): `pre_substitutions` (regex no texto falado antes de traduzir), `post_corrections` (regex no que o Whisper ouviu), `keep_terms` (nomes que o tradutor mantém). Não preencha `whisper_vocab`: medido, deixa o Whisper ~2x mais lento.
## Como evita o "atraso que só cresce"
- Janela de áudio **limitada** (nunca reenvia a sessão inteira ao Whisper); cada frase confirmada sai da janela.
- Só se traduz o que o Whisper já confirmou; fila "mais recente ganha" (o atraso acumulado vira um único pedido, nunca uma fila).
- Tradução em **streaming**: o espanhol só acrescenta palavras, não reescreve.
- Decodificações em loop são cortadas por limite de tokens e descartadas se o Whisper devolver segmentos demais para a duração.
## Medições (MacBook Air M2, 16 GB, macOS 26)
Uma aula real de 2h44 com voz ao vivo (pt→es), sem OBS, app em segundo plano:
- 1ª palavra do espanhol: mediana **0,17 s**; tradução completa da frase: mediana **0,60 s** (p95 3,8 s; um pico isolado de 22,6 s).
- Whisper small: **0,31 s → 0,30 s** de média entre o 1º e o último quarto da aula (sem degradação).
- App: ~90–134 MB de RAM, ~19% de um núcleo; estado térmico `nominal`/`fair`. Mais ~1,2 GB do modelo no Ollama.
Alternativas testadas e descartadas **neste Mac**: Hy-MT2 **7B** (~19 vs 42 caracteres/s, perto demais da velocidade da fala); Whisper **medium** (2,16 s vs 0,38 s por decodificação, não acompanha a fala).
Reproduzir: `zsh scripts/gen_test_audio.sh` (voz sintética pt-BR do macOS) e `zsh scripts/soak.sh 600` (usa `LC_LOG=1`; saída em `<dados>/logs/`). Os números acima **não** foram medidos em outros Macs, e os logs da aula não estão neste repositório (contêm conversas de terceiros), então não são verificáveis aqui; `soak.sh` reproduz o método com áudio sintético.
## Modelos e licenças
Este repositório **não redistribui pesos**. O script baixa dos repositórios oficiais:
| Componente | Origem | Licença (conforme o repositório de origem) |
|---|---|---|
| Whisper small (conversão CoreML) | `argmaxinc/whisperkit-coreml` (pesos originais: `openai/whisper-small`) | conversão: MIT; pesos originais: Apache-2.0 |
| Tokenizer do Whisper | `openai/whisper-small` | Apache-2.0 |
| Hy-MT2-1.8B (GGUF Q4_K_M) | `tencent/Hy-MT2-1.8B-GGUF` | Apache-2.0 (`LICENSE.txt` oficial, sem restrição territorial, conferido em 2026-11-09) |
| WhisperKit (biblioteca) | `argmaxinc/WhisperKit` | MIT |
| Ollama | `ollama/ollama` | MIT |
Atenção: relatos indicam que cópias do Hy-MT2 no catálogo do Ollama (por exemplo `kaelri/hy-mt2`) trazem a licença antiga da Tencent, que **excluía a União Europeia** (não verifiquei essas cópias diretamente). Por segurança, use o GGUF oficial: o script confere o SHA-256 `dc5f44fcf1fa496ee7ad725982c0c8c553a4de00259b53af84c4b89fb0c06699` (revisão `a0c709d9`).
Isto é uma leitura técnica das licenças, não parecer jurídico.
## Limites
- **Qualidade:** o Whisper small erra nomes próprios e termos técnicos; o Hy-MT2 1.8B às vezes acrescenta palavras que não foram ditas e traduz expressões coloquiais de forma literal (o glossário ajuda nos casos conhecidos). Revise antes de usar em contexto formal ou legal.
- Só **Apple Silicon**. Testado apenas com pt→es (o Whisper cobre outros idiomas de origem; o destino depende do que o tradutor suporta, e isso não foi testado aqui).
- Sem separação de falantes. Áudio só do microfone (não captura o áudio do sistema).
- Não foi testado com leitor de tela nem em outros Macs.
## Estrutura
- `Sources/LiveCaption/main.swift` — app (captura, laço do Whisper, fila de tradução, janela).
- `scripts/` — `fetch_models.sh`, `build_app.sh`, `livecaption.sh`, `soak.sh`, `gen_test_audio.sh`, `test_mt.py`.
- `assets/hymt2.Modelfile` — template de chat e token de parada corretos do Hy-MT2 para o Ollama (sem eles o modelo não termina a frase).
- `glossary.example.json`, `tests/live_pt.txt` (texto do áudio de teste).
## Licença
Código: **MIT** (ver `LICENSE`). Os modelos mantêm as próprias licenças, listadas acima.