Skip to content

Función de Grabación y Transcripción en Tiempo Real para AMD A4-9125 #10

Description

@Charly-bite

Función de Grabación y Transcripción en Tiempo Real para AMD A4-9125

Objetivo

Crear una función integrada que permita:

  1. 🎙️ Grabar audio en vivo desde micrófono
  2. Transcribir automáticamente usando whisper-amd
  3. 📝 Guardar resultados en formatos múltiples
  4. 🔄 Integrar con ProcessingAgent existente

Script Principal de Grabación y Transcripción

#!/usr/bin/env python3
# live_transcriber_amd.py

import pyaudio
import wave
import time
import subprocess
import sys
import threading
import queue
from pathlib import Path
from datetime import datetime
import os

class LiveTranscriberAMD:
    """
    Grabador y transcritor en tiempo real optimizado para AMD A4-9125.
    Usa whisper-amd nativo para máxima velocidad.
    """
    
    def __init__(self):
        self.setup_paths()
        self.setup_audio()
        self.setup_whisper()
        
        # Estado de grabación
        self.recording = False
        self.frames = []
        self.stream = None
        self.p = None
        
        # Cola para procesamiento en paralelo
        self.transcription_queue = queue.Queue()
        
        print("🎙️ LiveTranscriberAMD inicializado para AMD A4-9125")
        print(f"📁 Grabaciones: {self.recordings_dir}")
        print(f"📝 Transcripciones: {self.transcripts_dir}")
    
    def setup_paths(self):
        """Configurar directorios de trabajo."""
        self.base_dir = Path.cwd()
        self.recordings_dir = self.base_dir / "recordings" / "live"
        self.transcripts_dir = self.base_dir / "recordings" / "transcripts"
        
        # Crear directorios si no existen
        self.recordings_dir.mkdir(parents=True, exist_ok=True)
        self.transcripts_dir.mkdir(parents=True, exist_ok=True)
    
    def setup_audio(self):
        """Configurar parámetros de audio optimizados."""
        # Configuración optimizada para AMD A4-9125
        self.CHUNK = 1024
        self.FORMAT = pyaudio.paInt16
        self.CHANNELS = 1  # Mono para mejor rendimiento
        self.RATE = 16000  # 16kHz óptimo para whisper
        
        print(f"🔧 Audio: {self.RATE}Hz, {self.CHANNELS} canal, chunks de {self.CHUNK}")
    
    def setup_whisper(self):
        """Configurar whisper-amd."""
        self.whisper_binary = "/usr/local/bin/whisper-amd"
        self.models_dir = Path.home() / "whisper_models"
        
        # Modelos disponibles (en orden de velocidad)
        self.models = {
            "tiny": self.models_dir / "ggml-tiny.bin",     # Más rápido
            "base": self.models_dir / "ggml-base.bin",     # Equilibrado
            "small": self.models_dir / "ggml-small.bin"    # Más preciso
        }
        
        # Verificar whisper-amd
        if not Path(self.whisper_binary).exists():
            print(f"❌ ERROR: whisper-amd no encontrado en {self.whisper_binary}")
            sys.exit(1)
        
        print(f"✅ whisper-amd encontrado: {self.whisper_binary}")
        
        # Verificar modelos
        available_models = []
        for name, path in self.models.items():
            if path.exists():
                size_mb = path.stat().st_size // (1024 * 1024)
                available_models.append(f"{name} ({size_mb}MB)")
                print(f"✅ Modelo {name}: {path.name}")
            else:
                print(f"❌ Modelo {name} no encontrado: {path}")
        
        if not available_models:
            print("❌ ERROR: No hay modelos disponibles")
            sys.exit(1)
        
        print(f"🧠 Modelos disponibles: {', '.join(available_models)}")
    
    def get_audio_devices(self):
        """Listar dispositivos de audio disponibles."""
        p = pyaudio.PyAudio()
        print("\n🎤 DISPOSITIVOS DE AUDIO DISPONIBLES:")
        print("=" * 50)
        
        input_devices = []
        for i in range(p.get_device_count()):
            info = p.get_device_info_by_index(i)
            if info['maxInputChannels'] > 0:
                input_devices.append((i, info['name']))
                print(f"  [{i}] {info['name']}")
                print(f"      Canales: {info['maxInputChannels']}")
                print(f"      Frecuencia: {info['defaultSampleRate']}Hz")
                print()
        
        p.terminate()
        return input_devices
    
    def select_input_device(self):
        """Seleccionar dispositivo de entrada."""
        devices = self.get_audio_devices()
        
        if not devices:
            print("❌ No se encontraron dispositivos de entrada")
            return None
        
        print("💡 Selecciona un dispositivo de entrada:")
        try:
            choice = int(input("Ingresa el número del dispositivo [0]: ") or "0")
            if 0 <= choice < len(devices):
                device_index = devices[choice][0]
                device_name = devices[choice][1]
                print(f"✅ Seleccionado: [{device_index}] {device_name}")
                return device_index
            else:
                print("❌ Selección inválida, usando dispositivo por defecto")
                return None
        except ValueError:
            print("❌ Entrada inválida, usando dispositivo por defecto")
            return None
    
    def start_recording(self, device_index=None):
        """Iniciar grabación de audio."""
        if self.recording:
            print("⚠️ Ya se está grabando")
            return False
        
        try:
            self.p = pyaudio.PyAudio()
            
            # Configurar stream de audio
            stream_params = {
                'format': self.FORMAT,
                'channels': self.CHANNELS,
                'rate': self.RATE,
                'input': True,
                'frames_per_buffer': self.CHUNK
            }
            
            if device_index is not None:
                stream_params['input_device_index'] = device_index
            
            self.stream = self.p.open(**stream_params)
            self.frames = []
            self.recording = True
            self.start_time = time.time()
            
            print("🔴 GRABACIÓN INICIADA")
            print("💡 Presiona Ctrl+C para detener")
            return True
            
        except Exception as e:
            print(f"❌ Error iniciando grabación: {e}")
            return False
    
    def record_chunk(self):
        """Grabar un chunk de audio."""
        if not self.recording or not self.stream:
            return None
        
        try:
            data = self.stream.read(self.CHUNK, exception_on_overflow=False)
            self.frames.append(data)
            return data
        except Exception as e:
            print(f"❌ Error grabando chunk: {e}")
            return None
    
    def stop_recording(self):
        """Detener grabación y guardar archivo."""
        if not self.recording:
            print("⚠️ No se está grabando")
            return None
        
        try:
            self.stream.stop_stream()
            self.stream.close()
            self.p.terminate()
            self.recording = False
            
            # Calcular duración
            end_time = time.time()
            duration = end_time - self.start_time
            
            # Generar nombre de archivo único
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"live_recording_{timestamp}.wav"
            filepath = self.recordings_dir / filename
            
            # Guardar archivo WAV
            with wave.open(str(filepath), 'wb') as wf:
                wf.setnchannels(self.CHANNELS)
                wf.setsampwidth(self.p.get_sample_size(self.FORMAT))
                wf.setframerate(self.RATE)
                wf.writeframes(b''.join(self.frames))
            
            file_size = filepath.stat().st_size / 1024  # KB
            
            print(f"🟢 GRABACIÓN DETENIDA")
            print(f"📁 Archivo: {filename}")
            print(f"⏱️ Duración: {duration:.1f}s")
            print(f"💾 Tamaño: {file_size:.1f}KB")
            
            return {
                'filepath': filepath,
                'duration': duration,
                'size_kb': file_size,
                'timestamp': timestamp
            }
            
        except Exception as e:
            print(f"❌ Error deteniendo grabación: {e}")
            return None
    
    def transcribe_file(self, audio_file, model="base", language="es"):
        """Transcribir archivo de audio usando whisper-amd."""
        model_path = self.models.get(model)
        if not model_path or not model_path.exists():
            print(f"❌ Modelo {model} no disponible")
            return None
        
        print(f"\n🧠 INICIANDO TRANSCRIPCIÓN")
        print(f"📁 Archivo: {audio_file.name}")
        print(f"🤖 Modelo: {model}")
        print(f"🗣️ Idioma: {language}")
        
        # Comando whisper-amd optimizado para A4-9125
        cmd = [
            self.whisper_binary,
            "-m", str(model_path),
            "-t", "2",  # 2 threads para A4-9125
            "-p", "1",  # 1 processor
            "-l", language,
            "--output-txt",
            "--output-srt",
            "--output-vtt",
            "--output-dir", str(self.transcripts_dir),
            str(audio_file)
        ]
        
        try:
            start_time = time.time()
            
            # Ejecutar transcripción
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=300  # 5 minutos máximo
            )
            
            end_time = time.time()
            transcription_time = end_time - start_time
            
            if result.returncode == 0:
                # Buscar archivo de texto generado
                base_name = audio_file.stem
                txt_file = self.transcripts_dir / f"{base_name}.txt"
                srt_file = self.transcripts_dir / f"{base_name}.srt"
                
                if txt_file.exists():
                    with open(txt_file, 'r', encoding='utf-8') as f:
                        transcript_text = f.read().strip()
                    
                    word_count = len(transcript_text.split())
                    
                    print(f"✅ TRANSCRIPCIÓN COMPLETADA")
                    print(f"⏱️ Tiempo: {transcription_time:.1f}s")
                    print(f"📊 Palabras: {word_count}")
                    print(f"📝 Texto: {txt_file.name}")
                    if srt_file.exists():
                        print(f"🎬 Subtítulos: {srt_file.name}")
                    
                    return {
                        'text': transcript_text,
                        'txt_file': txt_file,
                        'srt_file': srt_file if srt_file.exists() else None,
                        'word_count': word_count,
                        'transcription_time': transcription_time,
                        'model': model,
                        'language': language
                    }
                else:
                    print(f"❌ Archivo de transcripción no encontrado")
            else:
                print(f"❌ Error en transcripción:")
                print(result.stderr)
                
        except subprocess.TimeoutExpired:
            print(f"❌ Timeout en transcripción (5 minutos)")
        except Exception as e:
            print(f"❌ Error durante transcripción: {e}")
        
        return None
    
    def record_and_transcribe(self, duration=None, model="base", language="es", device_index=None):
        """Grabar y transcribir en una sola función."""
        print(f"\n🚀 GRABACIÓN Y TRANSCRIPCIÓN EN VIVO")
        print(f"🧠 Modelo: {model}")
        print(f"🗣️ Idioma: {language}")
        if duration:
            print(f"⏱️ Duración: {duration}s")
        else:
            print(f"⏱️ Duración: Hasta Ctrl+C")
        print("=" * 50)
        
        # Iniciar grabación
        if not self.start_recording(device_index):
            return None
        
        try:
            if duration:
                # Grabación por tiempo fijo
                chunks_to_record = int(self.RATE / self.CHUNK * duration)
                for i in range(chunks_to_record):
                    self.record_chunk()
                    # Mostrar progreso
                    progress = (i + 1) / chunks_to_record * 100
                    print(f"\r🔴 Grabando... {progress:.1f}%", end='', flush=True)
                print()  # Nueva línea
            else:
                # Grabación hasta Ctrl+C
                while True:
                    self.record_chunk()
                    time.sleep(0.01)  # Pequeña pausa
                    
        except KeyboardInterrupt:
            print(f"\n🛑 Grabación interrumpida por usuario")
        
        # Detener y guardar
        recording_result = self.stop_recording()
        if not recording_result:
            return None
        
        # Transcribir automáticamente
        print(f"\n⚡ Iniciando transcripción automática...")
        transcription_result = self.transcribe_file(
            recording_result['filepath'],
            model=model,
            language=language
        )
        
        if transcription_result:
            print(f"\n📋 RESULTADO FINAL:")
            print(f"🎙️ Audio: {recording_result['filepath'].name}")
            print(f"📝 Transcripción: {transcription_result['txt_file'].name}")
            print(f"⏱️ Audio: {recording_result['duration']:.1f}s")
            print(f"⚡ Transcripción: {transcription_result['transcription_time']:.1f}s")
            print(f"📊 Palabras: {transcription_result['word_count']}")
            
            # Mostrar preview del texto
            preview = transcription_result['text'][:200]
            print(f"\n📖 PREVIEW:")
            print(f"'{preview}{'...' if len(transcription_result['text']) > 200 else ''}'")
            
            return {
                'recording': recording_result,
                'transcription': transcription_result
            }
        
        return {'recording': recording_result, 'transcription': None}
    
    def interactive_mode(self):
        """Modo interactivo para grabación y transcripción."""
        print(f"\n🎙️ MODO INTERACTIVO - LiveTranscriberAMD")
        print("=" * 50)
        
        while True:
            print(f"\n💡 OPCIONES:")
            print("1. 🎙️ Grabar y transcribir (duración fija)")
            print("2. 🔴 Grabar y transcribir (hasta Ctrl+C)")
            print("3. 🎤 Seleccionar dispositivo de audio")
            print("4. 📁 Ver archivos recientes")
            print("5. ❌ Salir")
            
            try:
                choice = input("\nSelecciona una opción [1-5]: ").strip()
                
                if choice == "1":
                    # Grabación con duración fija
                    try:
                        duration = int(input("Duración en segundos [30]: ") or "30")
                        model = input("Modelo [base]: ").strip() or "base"
                        language = input("Idioma [es]: ").strip() or "es"
                        
                        device_index = getattr(self, 'selected_device', None)
                        self.record_and_transcribe(duration, model, language, device_index)
                        
                    except ValueError:
                        print("❌ Duración inválida")
                
                elif choice == "2":
                    # Grabación hasta Ctrl+C
                    model = input("Modelo [base]: ").strip() or "base"
                    language = input("Idioma [es]: ").strip() or "es"
                    
                    device_index = getattr(self, 'selected_device', None)
                    self.record_and_transcribe(None, model, language, device_index)
                
                elif choice == "3":
                    # Seleccionar dispositivo
                    self.selected_device = self.select_input_device()
                
                elif choice == "4":
                    # Ver archivos recientes
                    self.show_recent_files()
                
                elif choice == "5":
                    print("👋 ¡Hasta luego!")
                    break
                
                else:
                    print("❌ Opción inválida")
                    
            except KeyboardInterrupt:
                print(f"\n👋 ¡Hasta luego!")
                break
            except Exception as e:
                print(f"❌ Error: {e}")
    
    def show_recent_files(self):
        """Mostrar archivos recientes."""
        print(f"\n📁 ARCHIVOS RECIENTES:")
        print("=" * 50)
        
        # Archivos de audio
        audio_files = sorted(
            self.recordings_dir.glob("*.wav"),
            key=lambda x: x.stat().st_mtime,
            reverse=True
        )[:5]
        
        print("🎙️ Grabaciones:")
        for f in audio_files:
            size_kb = f.stat().st_size / 1024
            mtime = datetime.fromtimestamp(f.stat().st_mtime)
            print(f"  📁 {f.name} ({size_kb:.1f}KB) - {mtime.strftime('%Y-%m-%d %H:%M')}")
        
        # Archivos de transcripción
        txt_files = sorted(
            self.transcripts_dir.glob("*.txt"),
            key=lambda x: x.stat().st_mtime,
            reverse=True
        )[:5]
        
        print("\n📝 Transcripciones:")
        for f in txt_files:
            mtime = datetime.fromtimestamp(f.stat().st_mtime)
            print(f"  📄 {f.name} - {mtime.strftime('%Y-%m-%d %H:%M')}")


def main():
    """Función principal."""
    print("🚀 Iniciando LiveTranscriberAMD para AMD A4-9125")
    
    try:
        transcriber = LiveTranscriberAMD()
        
        # Verificar argumentos de línea de comandos
        if len(sys.argv) > 1:
            if sys.argv[1] == "--quick":
                # Modo rápido: grabar 30 segundos
                print("⚡ Modo rápido: grabando 30 segundos...")
                transcriber.record_and_transcribe(duration=30)
            elif sys.argv[1] == "--live":
                # Modo en vivo: hasta Ctrl+C
                print("🔴 Modo en vivo: presiona Ctrl+C para detener...")
                transcriber.record_and_transcribe()
            else:
                print("❌ Argumento desconocido. Usa --quick o --live")
        else:
            # Modo interactivo
            transcriber.interactive_mode()
            
    except KeyboardInterrupt:
        print(f"\n👋 Programa terminado por usuario")
    except Exception as e:
        print(f"❌ Error fatal: {e}")

if __name__ == "__main__":
    main()

Script de Instalación y Configuración

#!/bin/bash
# setup_live_transcriber.sh

echo "🔧 CONFIGURANDO LiveTranscriberAMD"
echo "================================="

# Verificar dependencias Python
echo "📦 Verificando dependencias Python..."
python3 -c "import pyaudio, wave, subprocess, threading, queue" 2>/dev/null || {
    echo "❌ Faltan dependencias Python"
    echo "💡 Instalando pyaudio..."
    sudo apt update
    sudo apt install -y python3-pyaudio portaudio19-dev
    pip3 install pyaudio
}

# Verificar whisper-amd
echo "🤖 Verificando whisper-amd..."
if ! command -v whisper-amd >/dev/null 2>&1; then
    echo "❌ whisper-amd no encontrado"
    echo "💡 Asegúrate de haber completado los pasos anteriores"
    exit 1
fi
echo "✅ whisper-amd encontrado"

# Verificar modelos
echo "🧠 Verificando modelos..."
MODELS_DIR="$HOME/whisper_models"
if [ ! -d "$MODELS_DIR" ]; then
    echo "❌ Directorio de modelos no encontrado: $MODELS_DIR"
    exit 1
fi

model_count=0
for model in tiny base small; do
    if [ -f "$MODELS_DIR/ggml-$model.bin" ]; then
        size=$(stat -c%s "$MODELS_DIR/ggml-$model.bin" 2>/dev/null | awk '{print int($1/1024/1024)"MB"}')
        echo "✅ Modelo $model: $size"
        ((model_count++))
    else
        echo "⚠️ Modelo $model no encontrado"
    fi
done

if [ $model_count -eq 0 ]; then
    echo "❌ No hay modelos disponibles"
    exit 1
fi

# Crear directorios
echo "📁 Creando directorios..."
mkdir -p recordings/live
mkdir -p recordings/transcripts
echo "✅ Directorios creados"

# Hacer ejecutable el script
echo "🔧 Configurando permisos..."
chmod +x live_transcriber_amd.py

# Crear alias útiles
echo "⚡ Creando aliases..."
cat >> ~/.bashrc << 'EOF'

# LiveTranscriberAMD aliases
alias transcribe-quick="python3 live_transcriber_amd.py --quick"
alias transcribe-live="python3 live_transcriber_amd.py --live"
alias transcribe-interactive="python3 live_transcriber_amd.py"
EOF

echo "✅ Configuración completada"
echo ""
echo "🚀 COMANDOS DISPONIBLES:"
echo "========================"
echo "python3 live_transcriber_amd.py              # Modo interactivo"
echo "python3 live_transcriber_amd.py --quick      # Grabar 30s"
echo "python3 live_transcriber_amd.py --live       # Hasta Ctrl+C"
echo ""
echo "💡 Recarga tu terminal o ejecuta: source ~/.bashrc"

Integración con ProcessingAgent

# agents/processing_agent_live.py

import sys
from pathlib import Path

# Importar el LiveTranscriberAMD
sys.path.append(str(Path(__file__).parent.parent))
from live_transcriber_amd import LiveTranscriberAMD

# Importar ProcessingAgent original
from agents.processing_agent import ProcessingAgent

class ProcessingAgentLive(ProcessingAgent):
    """
    ProcessingAgent extendido con capacidades de grabación en vivo.
    """
    
    def __init__(self):
        super().__init__()
        self.live_transcriber = LiveTranscriberAMD()
        print(f"[{self.agent_name}] Extendido con capacidades de grabación en vivo")
    
    def record_and_transcribe_live(self, duration=None, model="base", language="es"):
        """
        Grabar y transcribir en una sola operación.
        Se integra con el flujo de ProcessingAgent.
        """
        print(f"[{self.agent_name}] Iniciando grabación y transcripción en vivo...")
        
        # Usar LiveTranscriberAMD para grabar y transcribir
        result = self.live_transcriber.record_and_transcribe(
            duration=duration,
            model=model,
            language=language
        )
        
        if result and result.get('transcription'):
            # Integrar con el flujo del ProcessingAgent
            transcription = result['transcription']
            
            # Crear resultado compatible con ProcessingAgent
            return {
                "text": transcription['text'],
                "path": str(transcription['txt_file']),
                "language": transcription['language'],
                "method": "live-whisper-amd",
                "model": transcription['model'],
                "duration_seconds": result['recording']['duration'],
                "transcription_time": transcription['transcription_time'],
                "word_count": transcription['word_count']
            }
        
        return None
    
    def run_live_demo(self):
        """Demo de grabación en vivo."""
        print(f"[{self.agent_name}] Ejecutando demo de grabación en vivo...")
        
        # Grabar 15 segundos como demo
        result = self.record_and_transcribe_live(duration=15, model="tiny", language="es")
        
        if result:
            print(f"[{self.agent_name}] Demo completado exitosamente:")
            print(f"  Texto: {result['text'][:100]}...")
            print(f"  Archivo: {result['path']}")
            print(f"  Duración audio: {result['duration_seconds']:.1f}s")
            print(f"  Tiempo transcripción: {result['transcription_time']:.1f}s")
            print(f"  Palabras: {result['word_count']}")
        else:
            print(f"[{self.agent_name}] Demo falló")


if __name__ == "__main__":
    agent = ProcessingAgentLive()
    agent.run_live_demo()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions