documentar integracion Alpha ↔ Handy para transcripcion offline con Parakeet V3
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
# Alpha + Handy — Integración de Transcripción Offline
|
||||
|
||||
> **Inspiración**: Handy (github.com/cjpais/Handy) — 22.4k ⭐, Tauri v2, MIT.
|
||||
> Alpha toma de Handy su arquitectura de transcripción local con modelos Whisper/Parakeet.
|
||||
|
||||
**Versión**: 1.0 | **Fecha**: 2026-05-26 | **Autor**: Ricardo Gonzalez (Teloprax)
|
||||
|
||||
---
|
||||
|
||||
## 1. ¿Qué es Handy?
|
||||
|
||||
Handy es una app Tauri v2 que captura audio del micrófono, lo transcribe con modelos locales (Whisper o Parakeet) y pega el texto donde esté el cursor. Todo offline. Es la referencia perfecta para integrar transcripción en RUMBO/Alpha.
|
||||
|
||||
| Aspecto | Handy | Alpha (a integrar) |
|
||||
|---------|-------|---------------------|
|
||||
| Shell | Tauri v2 | Tauri v2 ✅ |
|
||||
| Frontend | React + TS + Tailwind | Vue 3 + TS + Tailwind 🔄 |
|
||||
| Transcripción | transcribe-rs (Whisper + Parakeet) | transcribe-rs |
|
||||
| Captura audio | cpal (micrófono) | cpal (micrófono + archivos) |
|
||||
| VAD (silencio) | vad-rs (Silero) | vad-rs |
|
||||
| Atajos globales | rdev + tauri-plugin-global-shortcut | rdev + tauri-plugin-global-shortcut |
|
||||
| Permisos macOS | tauri-plugin-macos-permissions | Ídem |
|
||||
| Permisos Windows | Win32 APIs (Windows crate) | Ídem |
|
||||
|
||||
---
|
||||
|
||||
## 2. Arquitectura de transcripción (basada en Handy)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ Alpha (Tauri) │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Vue 3 UI │ │ Rust Backend │ │
|
||||
│ │ (activar │◄──►│ │ │
|
||||
│ │ mic, │ │ ┌───────────────────┐ │ │
|
||||
│ │ modelos, │ │ │ Audio Capture │ │ │
|
||||
│ │ transc.) │ │ │ (cpal) │ │ │
|
||||
│ └─────────────┘ │ │ → micrófono │ │ │
|
||||
│ │ │ → archivo .wav │ │ │
|
||||
│ │ │ → archivo .mp3 │ │ │
|
||||
│ │ │ → video (ffmpeg │ │ │
|
||||
│ │ │ extrae audio) │ │ │
|
||||
│ │ └───────┬───────────┘ │ │
|
||||
│ │ │ audio raw │ │
|
||||
│ │ ┌───────▼───────────┐ │ │
|
||||
│ │ │ VAD (vad-rs) │ │ │
|
||||
│ │ │ → silencio OUT │ │ │
|
||||
│ │ │ → voz → buffer │ │ │
|
||||
│ │ └───────┬───────────┘ │ │
|
||||
│ │ │ segmentos │ │
|
||||
│ │ ┌───────▼───────────┐ │ │
|
||||
│ │ │ transcribe-rs │ │ │
|
||||
│ │ │ → Parakeet V3 │ │ │
|
||||
│ │ │ → Whisper Small │ │ │
|
||||
│ │ │ → Whisper Turbo │ │ │
|
||||
│ │ └───────┬───────────┘ │ │
|
||||
│ │ │ texto │ │
|
||||
│ │ ┌───────▼───────────┐ │ │
|
||||
│ │ │ Post-procesado │ │ │
|
||||
│ │ │ → pegar en app │ │ │
|
||||
│ │ │ → guardar en DB │ │ │
|
||||
│ │ │ → pipeline IA │ │ │
|
||||
│ │ └───────────────────┘ │ │
|
||||
│ └─────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Dependencias Rust necesarias
|
||||
|
||||
Inspiradas en el `Cargo.toml` de Handy (v0.8.3):
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
# Transcripción (core)
|
||||
transcribe-rs = { version = "0.3", features = ["whisper-cpp", "onnx"] }
|
||||
|
||||
# Audio
|
||||
cpal = "0.16" # Captura de micrófono
|
||||
hound = "3.5" # Lectura/escritura de archivos .wav
|
||||
|
||||
# VAD (Voice Activity Detection)
|
||||
vad-rs = { git = "https://github.com/cjpais/vad-rs", default-features = false }
|
||||
|
||||
# Atajos globales
|
||||
rdev = { git = "https://github.com/rustdesk-org/rdev" }
|
||||
tauri-plugin-global-shortcut = "2.3"
|
||||
|
||||
# Permisos
|
||||
tauri-plugin-macos-permissions = "2.3"
|
||||
|
||||
# Resampling (si se necesita cambiar sample rate)
|
||||
rubato = "0.16"
|
||||
|
||||
# Audio playback (feedback sonoro)
|
||||
rodio = "0.16"
|
||||
|
||||
# Utilidades
|
||||
anyhow = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
# ─── Plataforma-específicas ───
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
transcribe-rs = { version = "0.3", features = ["whisper-metal"] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
transcribe-rs = { version = "0.3", features = ["whisper-vulkan", "ort-directml"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
transcribe-rs = { version = "0.3", features = ["whisper-vulkan"] }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Modelos de transcripción
|
||||
|
||||
### 4.1 Parakeet V3 (RECOMENDADO)
|
||||
|
||||
| Característica | Detalle |
|
||||
|---------------|---------|
|
||||
| Tamaño | ~478 MB (comprimido) |
|
||||
| Velocidad | ~5x tiempo real en CPU (i5) |
|
||||
| GPU | No requiere — CPU-optimizado |
|
||||
| Idioma | Detección automática (sin selección manual) |
|
||||
| Precisión | Excelente en español e inglés |
|
||||
| Descarga | `https://blob.handy.computer/parakeet-v3-int8.tar.gz` |
|
||||
| Backend | ONNX Runtime (vía transcribe-rs feature `onnx`) |
|
||||
|
||||
**Por qué Parakeet V3**: No requiere GPU, funciona en cualquier CPU moderna (Skylake+), velocidad adecuada para transcripción interactiva, y detección automática de idioma (esencial para equipos bilingües español/inglés).
|
||||
|
||||
### 4.2 Whisper (alternativa)
|
||||
|
||||
| Modelo | Tamaño | Velocidad | GPU |
|
||||
|--------|--------|-----------|-----|
|
||||
| Small | 487 MB | Rápido | Opcional |
|
||||
| Medium | 492 MB | Medio | Recomendada |
|
||||
| Turbo | 1.6 GB | Rápido | Recomendada |
|
||||
| Large | 1.1 GB | Lento | Necesaria |
|
||||
|
||||
Whisper es más preciso pero más pesado y requiere GPU para velocidad aceptable. Parakeet V3 es mejor opción para Alpha por su eficiencia CPU.
|
||||
|
||||
---
|
||||
|
||||
## 5. ¿Parakeet V3 para video/audio pregrabado?
|
||||
|
||||
**Sí**, pero con un paso intermedio: extraer el audio del video.
|
||||
|
||||
### Flujo para video
|
||||
|
||||
```
|
||||
video.mp4 → ffmpeg (extraer pista de audio) → audio.wav → transcribe-rs → texto
|
||||
```
|
||||
|
||||
### Flujo para audio pregrabado
|
||||
|
||||
```
|
||||
grabacion.mp3/.ogg/.flac/.m4a → decodificar a PCM/WAV → transcribe-rs → texto
|
||||
```
|
||||
|
||||
### Implementación en Rust
|
||||
|
||||
```rust
|
||||
// Opción A: Usar ffmpeg CLI (sidecar)
|
||||
use std::process::Command;
|
||||
Command::new("ffmpeg")
|
||||
.args(["-i", "video.mp4", "-vn", "-ar", "16000", "-ac", "1", "-f", "wav", "audio.wav"])
|
||||
.output()?;
|
||||
|
||||
// Opción B: Usar crate ffmpeg-sidecar (más portátil)
|
||||
// Opción C: Usar crate symphonia para decodificar audio sin ffmpeg
|
||||
```
|
||||
|
||||
### Formatos soportados
|
||||
|
||||
| Fuente | Método | Librería Rust |
|
||||
|--------|--------|---------------|
|
||||
| Micrófono (vivo) | cpal → buffer → transcribe-rs | cpal |
|
||||
| .wav | hound → samples → transcribe-rs | hound |
|
||||
| .mp3, .ogg, .flac, .m4a | symphonia → decode → transcribe-rs | symphonia |
|
||||
| Video (.mp4, .mov, .mkv) | ffmpeg → extract audio → transcribe-rs | ffmpeg-sidecar |
|
||||
|
||||
---
|
||||
|
||||
## 6. Permisos del sistema
|
||||
|
||||
### 6.1 macOS
|
||||
|
||||
| Permiso | Propósito | Cómo solicitarlo |
|
||||
|---------|-----------|-----------------|
|
||||
| **Microphone** | Capturar audio del micrófono | Info.plist: `NSMicrophoneUsageDescription` |
|
||||
| **Accessibility** | Pegar texto en cualquier app | System Preferences → Privacy → Accessibility |
|
||||
| **Screen Recording** | (futuro) Capturar audio del sistema | Info.plist: `NSScreenCaptureUsageDescription` |
|
||||
|
||||
**Código Info.plist**:
|
||||
```xml
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Alpha necesita acceso al micrófono para transcribir tus reuniones.</string>
|
||||
```
|
||||
|
||||
**Detección de permiso (Rust)**:
|
||||
```rust
|
||||
use tauri_plugin_macos_permissions;
|
||||
|
||||
// Verificar si el permiso está concedido
|
||||
let authorized = tauri_plugin_macos_permissions::check_accessibility();
|
||||
|
||||
// Solicitar permiso
|
||||
tauri_plugin_macos_permissions::request_accessibility();
|
||||
```
|
||||
|
||||
### 6.2 Windows
|
||||
|
||||
| Permiso | Propósito | Cómo solicitarlo |
|
||||
|---------|-----------|-----------------|
|
||||
| **Microphone** | Capturar audio | Windows Settings → Privacy → Microphone |
|
||||
| **Global shortcuts** | Atajos de teclado | Automático con rdev |
|
||||
|
||||
Windows solicita el permiso automáticamente la primera vez que se usa el micrófono.
|
||||
|
||||
### 6.3 Linux
|
||||
|
||||
| Requisito | Propósito |
|
||||
|-----------|-----------|
|
||||
| PulseAudio / ALSA | Captura de audio |
|
||||
| Grupo `input` | Atajos globales (algunos WMs) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Flujo de transcripción paso a paso
|
||||
|
||||
### 7.1 Micrófono (tiempo real)
|
||||
|
||||
```rust
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use transcribe_rs::Transcriber;
|
||||
|
||||
// 1. Obtener dispositivo de audio por defecto
|
||||
let host = cpal::default_host();
|
||||
let device = host.default_input_device().unwrap();
|
||||
let config = device.default_input_config().unwrap();
|
||||
|
||||
// 2. Configurar stream de audio
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let stream = device.build_input_stream(&config, move |data, _| {
|
||||
tx.send(data.to_vec()).unwrap();
|
||||
}, |err| eprintln!("{}", err), None).unwrap();
|
||||
stream.play().unwrap();
|
||||
|
||||
// 3. Buffer de audio + VAD
|
||||
let mut buffer: Vec<f32> = Vec::new();
|
||||
let mut vad = vad_rs::Vad::new(config.sample_rate().0);
|
||||
|
||||
// 4. Acumular voz, ignorar silencio
|
||||
while let Ok(samples) = rx.recv_timeout(Duration::from_millis(100)) {
|
||||
for &sample in &samples {
|
||||
if vad.is_voice(sample) {
|
||||
buffer.push(sample);
|
||||
}
|
||||
}
|
||||
if buffer.len() > config.sample_rate().0 as usize * 3 {
|
||||
break; // 3 segundos de voz → transcribir
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Transcribir con Parakeet V3
|
||||
let transcriber = Transcriber::new("parakeet-tdt-0.6b-v3-int8")?;
|
||||
let text = transcriber.transcribe(&buffer, config.sample_rate().0)?;
|
||||
println!("{}", text);
|
||||
```
|
||||
|
||||
### 7.2 Archivo de audio/video
|
||||
|
||||
```rust
|
||||
// 1. Si es video, extraer audio con ffmpeg
|
||||
// 2. Leer archivo .wav
|
||||
use hound::WavReader;
|
||||
let mut reader = WavReader::open("audio.wav")?;
|
||||
let samples: Vec<f32> = reader.samples::<i16>()
|
||||
.map(|s| s.unwrap() as f32 / i16::MAX as f32)
|
||||
.collect();
|
||||
|
||||
// 3. Transcribir
|
||||
let transcriber = Transcriber::new("parakeet-tdt-0.6b-v3-int8")?;
|
||||
let text = transcriber.transcribe(&samples, reader.spec().sample_rate)?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Descarga y gestión de modelos
|
||||
|
||||
Inspirado en Handy, los modelos se almacenan en:
|
||||
|
||||
| SO | Ruta |
|
||||
|----|------|
|
||||
| macOS | `~/Library/Application Support/com.teloprax.alpha/models/` |
|
||||
| Windows | `%APPDATA%\com.teloprax.alpha\models\` |
|
||||
| Linux | `~/.config/com.teloprax.alpha/models/` |
|
||||
|
||||
### Estructura esperada
|
||||
|
||||
```
|
||||
models/
|
||||
├── parakeet-tdt-0.6b-v3-int8/ ← Parakeet V3 (recomendado)
|
||||
│ ├── model.onnx
|
||||
│ ├── config.json
|
||||
│ └── tokenizer.json
|
||||
├── ggml-small.bin ← Whisper Small (alternativo)
|
||||
└── ggml-large-v3-turbo.bin ← Whisper Turbo (alternativo)
|
||||
```
|
||||
|
||||
### Descarga programática
|
||||
|
||||
```rust
|
||||
use reqwest;
|
||||
use flate2::read::GzDecoder;
|
||||
use tar::Archive;
|
||||
|
||||
async fn download_model(url: &str, dest: &Path) -> Result<()> {
|
||||
let response = reqwest::get(url).await?;
|
||||
let bytes = response.bytes().await?;
|
||||
|
||||
// Extraer si es .tar.gz (Parakeet), copiar si es .bin (Whisper)
|
||||
if url.ends_with(".tar.gz") {
|
||||
let gz = GzDecoder::new(&bytes[..]);
|
||||
let mut archive = Archive::new(gz);
|
||||
archive.unpack(dest)?;
|
||||
} else {
|
||||
std::fs::write(dest.join(url.split('/').last().unwrap()), &bytes)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Diferencia clave Alpha vs Handy
|
||||
|
||||
Handy es una app de propósito general (transcribir y pegar). Alpha lo integra en el flujo de gestión de proyectos:
|
||||
|
||||
| Capacidad | Handy | Alpha |
|
||||
|-----------|-------|-------|
|
||||
| Transcribir micrófono | ✅ | ✅ (para reuniones) |
|
||||
| Pegar texto en cualquier app | ✅ | ⬜ (no necesario) |
|
||||
| Transcribir archivos de audio | ⬜ | ✅ (.mp3, .wav, .ogg) |
|
||||
| Extraer audio de video | ⬜ | ✅ (vía ffmpeg) |
|
||||
| Pipeline IA post-transcripción | ⬜ | ✅ (→ análisis → HUs → KAPPA) |
|
||||
| Guardar transcripción en proyecto | ⬜ | ✅ (Turso + contexto de proyecto) |
|
||||
| Memoria de proyecto | ⬜ | ✅ (contexto_transcripciones.md) |
|
||||
| Integración con KAPPA | ⬜ | ✅ (crear HUs automáticamente) |
|
||||
|
||||
---
|
||||
|
||||
## 10. Roadmap de integración
|
||||
|
||||
| Fase | Alcance | Prioridad |
|
||||
|------|---------|-----------|
|
||||
| **T1** | Agregar dependencias Rust (transcribe-rs, cpal, vad-rs) | 🔴 Alta |
|
||||
| **T2** | Captura de micrófono + transcripción Parakeet V3 | 🔴 Alta |
|
||||
| **T3** | Descarga de modelos desde UI | 🟡 Media |
|
||||
| **T4** | Transcripción de archivos (.wav, .mp3) | 🟡 Media |
|
||||
| **T5** | Transcripción de video (extracción audio) | 🟢 Baja |
|
||||
| **T6** | Pipeline completo: audio → transcripción → IA → HUs | 🔴 Alta |
|
||||
| **T7** | Permisos macOS/Windows UI | 🟡 Media |
|
||||
|
||||
---
|
||||
|
||||
## 11. Referencias
|
||||
|
||||
- [Handy GitHub](https://github.com/cjpais/Handy)
|
||||
- [transcribe-rs crate](https://crates.io/crates/transcribe-rs)
|
||||
- [cpal crate](https://crates.io/crates/cpal)
|
||||
- [vad-rs (fork de Handy)](https://github.com/cjpais/vad-rs)
|
||||
- [Parakeet models (HuggingFace)](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3)
|
||||
- [Tauri Plugin macOS Permissions](https://github.com/ahkohd/tauri-plugin-macos-permissions)
|
||||
Reference in New Issue
Block a user