[IMPROVED] Actualizar claude_voice.py para utilizar reconocimiento de voz local
Cambiado el sistema de reconocimiento de voz de Google (online) a Vosk (offline): - Uso de reconocimiento de voz local sin dependencia de servicios en la nube - Añadido descargador automático de modelos de idioma - Soporte para listar dispositivos de audio - Nueva opción para instalar dependencias automáticamente - Mejor gestión de errores y tiempo de espera 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4ec9b41e2c
commit
9bc81ea5a8
1 changed files with 187 additions and 35 deletions
|
@ -5,16 +5,20 @@
|
|||
# [Author] : Cortana Rosero One <cortana@rosero.one>
|
||||
# [Generated] : Created by Claude Code (claude-3-7-sonnet-20250219)
|
||||
# [Created] : 2025/03/30 16:45:00
|
||||
# [Modified] : 2025/03/30 16:45:00
|
||||
# [Modified] : 2025/03/30 17:25:00
|
||||
# [Version] : 1.3.0
|
||||
# [Use Notes] : Instalar dependencias: pip install speechrecognition pydub pyaudio
|
||||
# [Use Notes] : Instalar dependencias: pip install vosk sounddevice pydub
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
import time
|
||||
import speech_recognition as sr
|
||||
import queue
|
||||
import threading
|
||||
import sounddevice as sd
|
||||
from vosk import Model, KaldiRecognizer
|
||||
from pydub import AudioSegment
|
||||
from pydub.playback import play
|
||||
|
||||
|
@ -66,36 +70,140 @@ def play_sound(sound_type):
|
|||
# Si hay algún error reproduciendo el sonido, simplemente continuamos
|
||||
pass
|
||||
|
||||
def download_model(language="es"):
|
||||
"""Descarga el modelo de Vosk si no existe"""
|
||||
# Mapeo de códigos de idioma estándar a formato de Vosk
|
||||
language_map = {
|
||||
"es-ES": "es",
|
||||
"en-US": "en-us",
|
||||
"fr-FR": "fr",
|
||||
"de-DE": "de",
|
||||
"it-IT": "it",
|
||||
"pt-PT": "pt",
|
||||
"ru-RU": "ru"
|
||||
}
|
||||
|
||||
# Obtener código de idioma para Vosk
|
||||
lang_code = language_map.get(language, language)
|
||||
if "-" in lang_code and lang_code not in language_map.values():
|
||||
lang_code = lang_code.split("-")[0]
|
||||
|
||||
model_path = os.path.expanduser(f"~/.vosk/models/vosk-model-small-{lang_code}")
|
||||
if os.path.exists(model_path):
|
||||
return model_path
|
||||
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.YELLOW}Descargando modelo de voz para {lang_code}...{Colors.END}")
|
||||
|
||||
# Crear directorio para modelos si no existe
|
||||
os.makedirs(os.path.expanduser("~/.vosk/models"), exist_ok=True)
|
||||
|
||||
# Descargar e instalar el modelo
|
||||
try:
|
||||
# Importar wget solo cuando se necesita
|
||||
import wget
|
||||
url = f"https://alphacephei.com/vosk/models/vosk-model-small-{lang_code}.zip"
|
||||
zip_path = os.path.expanduser(f"~/.vosk/models/vosk-model-small-{lang_code}.zip")
|
||||
|
||||
# Descargar el modelo
|
||||
wget.download(url, zip_path)
|
||||
print() # Nueva línea después de la barra de progreso
|
||||
|
||||
# Extraer el modelo
|
||||
import zipfile
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(os.path.expanduser("~/.vosk/models/"))
|
||||
|
||||
# Eliminar el zip
|
||||
os.remove(zip_path)
|
||||
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.GREEN}Modelo descargado y extraído correctamente{Colors.END}")
|
||||
return model_path
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error al descargar el modelo: {e}{Colors.END}")
|
||||
print(f"{Colors.YELLOW}Por favor, descargue manualmente el modelo desde: {Colors.UNDERLINE}https://alphacephei.com/vosk/models{Colors.END}")
|
||||
print(f"{Colors.YELLOW}Y colóquelo en: {model_path}{Colors.END}")
|
||||
sys.exit(1)
|
||||
|
||||
def recognize_speech(language="es-ES"):
|
||||
"""Captura audio del micrófono y lo convierte a texto"""
|
||||
recognizer = sr.Recognizer()
|
||||
"""Captura audio del micrófono y lo convierte a texto usando Vosk (local)"""
|
||||
# Descargar o verificar modelo
|
||||
model_path = download_model(language)
|
||||
|
||||
with sr.Microphone() as source:
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.YELLOW}Escuchando...{Colors.END} (Presiona Ctrl+C para detener)")
|
||||
play_sound("start")
|
||||
# Configurar el modelo
|
||||
model = Model(model_path)
|
||||
samplerate = 16000
|
||||
|
||||
# Ajustar para ruido ambiental
|
||||
recognizer.adjust_for_ambient_noise(source, duration=0.5)
|
||||
try:
|
||||
audio = recognizer.listen(source, timeout=10, phrase_time_limit=15)
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.GREEN}Procesando audio...{Colors.END}")
|
||||
# Configurar cola para recibir audio
|
||||
q = queue.Queue()
|
||||
|
||||
# Intentar reconocer usando Google Speech Recognition
|
||||
text = recognizer.recognize_google(audio, language=language)
|
||||
play_sound("stop")
|
||||
return text
|
||||
except sr.UnknownValueError:
|
||||
play_sound("error")
|
||||
print(f"{Colors.RED}No se pudo entender el audio{Colors.END}")
|
||||
return None
|
||||
except sr.RequestError as e:
|
||||
play_sound("error")
|
||||
print(f"{Colors.RED}Error en el servicio de reconocimiento: {e}{Colors.END}")
|
||||
return None
|
||||
except Exception as e:
|
||||
play_sound("error")
|
||||
print(f"{Colors.RED}Error: {e}{Colors.END}")
|
||||
return None
|
||||
# Función para callback de audio
|
||||
def callback(indata, frames, time, status):
|
||||
if status:
|
||||
print(f"{Colors.YELLOW}[Claude Voice] Status: {status}{Colors.END}")
|
||||
q.put(bytes(indata))
|
||||
|
||||
# Iniciar captura de audio
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.YELLOW}Escuchando...{Colors.END} (Presiona Ctrl+C para detener)")
|
||||
play_sound("start")
|
||||
|
||||
# Preparar reconocedor
|
||||
rec = KaldiRecognizer(model, samplerate)
|
||||
|
||||
try:
|
||||
with sd.RawInputStream(samplerate=samplerate, blocksize=8000,
|
||||
dtype='int16', channels=1, callback=callback):
|
||||
|
||||
# Variables para controlar el reconocimiento
|
||||
start_time = time.time()
|
||||
timeout = 10 # segundos
|
||||
last_text_time = time.time()
|
||||
final_result = ""
|
||||
partial_results = []
|
||||
|
||||
# Procesar audio
|
||||
while True:
|
||||
# Comprobar timeout
|
||||
if (time.time() - start_time) > timeout:
|
||||
break
|
||||
|
||||
# Comprobar si hay silencio prolongado después de hablar
|
||||
if final_result and (time.time() - last_text_time) > 1.5:
|
||||
break
|
||||
|
||||
# Obtener datos de audio
|
||||
data = q.get()
|
||||
|
||||
# Reconocer voz
|
||||
if rec.AcceptWaveform(data):
|
||||
result = json.loads(rec.Result())
|
||||
if result.get("text", ""):
|
||||
text = result["text"]
|
||||
final_result = text
|
||||
last_text_time = time.time()
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.GREEN}Procesando audio...{Colors.END}")
|
||||
else:
|
||||
# Resultados parciales
|
||||
partial = json.loads(rec.PartialResult())
|
||||
if partial.get("partial", ""):
|
||||
partial_text = partial["partial"]
|
||||
if partial_text:
|
||||
partial_results.append(partial_text)
|
||||
last_text_time = time.time()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.YELLOW}Reconocimiento interrumpido{Colors.END}")
|
||||
except Exception as e:
|
||||
play_sound("error")
|
||||
print(f"{Colors.RED}Error en el reconocimiento de voz: {e}{Colors.END}")
|
||||
return None
|
||||
finally:
|
||||
play_sound("stop")
|
||||
|
||||
# Si no hay resultado final pero hay parciales, usar el último parcial
|
||||
if not final_result and partial_results:
|
||||
final_result = partial_results[-1]
|
||||
|
||||
return final_result
|
||||
|
||||
def send_to_claude(text, silent=False):
|
||||
"""Envía el texto reconocido a Claude Code"""
|
||||
|
@ -158,21 +266,65 @@ def interactive_mode(language="es-ES", continuous=False):
|
|||
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.GREEN}¡Hasta pronto!{Colors.END}")
|
||||
|
||||
def list_audio_devices():
|
||||
"""Lista los dispositivos de audio disponibles"""
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.GREEN}Dispositivos de audio disponibles:{Colors.END}")
|
||||
devices = sd.query_devices()
|
||||
|
||||
print(f"{Colors.CYAN}{'ID':<4} {'Nombre':<30} {'Canales (E/S)':<15} {'Predeterminado':<12}{Colors.END}")
|
||||
print("-" * 65)
|
||||
|
||||
for i, device in enumerate(devices):
|
||||
default_mark = ""
|
||||
try:
|
||||
if device.get('name') == sd.query_devices(kind='input')['name']:
|
||||
default_mark = "⭐ (entrada)"
|
||||
elif device.get('name') == sd.query_devices(kind='output')['name']:
|
||||
default_mark = "⭐ (salida)"
|
||||
except:
|
||||
pass
|
||||
|
||||
channels = f"{device.get('max_input_channels', 0)}/{device.get('max_output_channels', 0)}"
|
||||
print(f"{i:<4} {device.get('name', 'Desconocido'):<30} {channels:<15} {default_mark}")
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Claude Code Voice - Convierte voz a texto para Claude Code')
|
||||
parser = argparse.ArgumentParser(description='Claude Code Voice - Convierte voz a texto para Claude Code usando reconocimiento local')
|
||||
parser.add_argument('-l', '--language', default='es-ES', help='Idioma para reconocimiento (ej. es-ES, en-US)')
|
||||
parser.add_argument('-c', '--continuous', action='store_true', help='Modo continuo - escucha constantemente hasta que digas "salir"')
|
||||
parser.add_argument('-t', '--text', help='Texto a enviar directamente (sin reconocimiento de voz)')
|
||||
parser.add_argument('-s', '--silent', action='store_true', help='Modo silencioso - no muestra mensajes extra')
|
||||
parser.add_argument('-d', '--device', type=int, help='ID del dispositivo de audio a utilizar')
|
||||
parser.add_argument('--list-devices', action='store_true', help='Listar dispositivos de audio disponibles')
|
||||
parser.add_argument('--install-deps', action='store_true', help='Instalar dependencias')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Instalar dependencias si se solicita
|
||||
if args.install_deps:
|
||||
try:
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.YELLOW}Instalando dependencias...{Colors.END}")
|
||||
import pip
|
||||
pip.main(['install', 'vosk', 'sounddevice', 'pydub', 'wget'])
|
||||
print(f"{Colors.BLUE}[Claude Voice]{Colors.END} {Colors.GREEN}Dependencias instaladas correctamente{Colors.END}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error al instalar dependencias: {e}{Colors.END}")
|
||||
sys.exit(1)
|
||||
|
||||
# Listar dispositivos si se solicita
|
||||
if args.list_devices:
|
||||
list_audio_devices()
|
||||
return
|
||||
|
||||
# Enviar texto directo si se proporciona
|
||||
if args.text:
|
||||
# Modo de texto directo
|
||||
send_to_claude(args.text, args.silent)
|
||||
else:
|
||||
# Modo interactivo con reconocimiento de voz
|
||||
interactive_mode(args.language, args.continuous)
|
||||
return
|
||||
|
||||
# Modo interactivo con reconocimiento de voz
|
||||
interactive_mode(args.language, args.continuous)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue