--- 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` | `/models/whisper/openai_whisper-small` | outro modelo do Whisper | | `LC_LOG=1` | desligado | grava histórico e métricas em `/logs/` | | `LC_SAVE_AUDIO=1` | desligado | grava o microfone em `/logs/audio-*.f32` | | `LC_WAV` / `LC_DURATION` | — | teste: alimenta o app com um arquivo `f32le` 16 kHz mono em tempo real | | `LC_SNAPSHOT=` | — | 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 `/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.