testing / interview /transcription_engine.py
Danielsz's picture
Update for HuggingFace
00fb203
Raw
History Blame Contribute Delete
7.31 kB
import os
import time
import threading
import logging
from .models import TranscriptionProviderConfig
log = logging.getLogger(__name__)
# --- Global caches para los modelos cargados en memoria ---
# (Esto evita cargar los modelos desde cero en cada peticion)
_whisper_model = None
_whisper_lock = threading.Lock()
_nemotron_model = None
_nemotron_lock = threading.Lock()
def get_whisper_model(model_size="base"):
global _whisper_model
if _whisper_model is None:
try:
import whisper
with _whisper_lock:
if _whisper_model is None:
log.info(f"Cargando modelo Whisper ({model_size}) en memoria...")
_whisper_model = whisper.load_model(model_size)
except ImportError:
log.error("La librer铆a 'whisper' no est谩 instalada. Ejecuta: pip install openai-whisper")
return None
return _whisper_model
def get_nemotron_model(model_name="nvidia/nemotron-3.5-asr-streaming-0.6b"):
global _nemotron_model
if _nemotron_model is None:
try:
import torch
# Fix PyTorch 2.4+ strict weights_only loading breaking NeMo
_orig_load = torch.load
def patched_load(*args, **kwargs):
kwargs['weights_only'] = False
return _orig_load(*args, **kwargs)
torch.load = patched_load
import nemo.collections.asr as nemo_asr
with _nemotron_lock:
if _nemotron_model is None:
log.info(f"Cargando modelo Nemotron ({model_name}) en memoria...")
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
_nemotron_model = nemo_asr.models.ASRModel.from_pretrained(model_name, map_location=device)
# Restore original load
torch.load = _orig_load
except ImportError:
log.error("La librer铆a 'nemo_toolkit' no est谩 instalada.")
return None
except Exception as e:
log.error(f"Error cargando Nemotron: {e}")
if 'torch' in locals() and hasattr(torch, 'load') and '_orig_load' in locals():
torch.load = _orig_load
return None
return _nemotron_model
def transcribe_audio_file(audio_path, provider_type="whisper_local"):
"""
Toma la ruta de un archivo de audio y el tipo de proveedor de transcripcion.
Retorna (texto_transcrito, tiempo_de_ejecucion_ms, error_msg)
"""
start_time = time.time()
transcribed_text = ""
error_msg = None
try:
config = TranscriptionProviderConfig.objects.filter(provider_type=provider_type, is_active=True).first()
if provider_type == "whisper_local":
model_size = config.model if config and config.model else "base"
model = get_whisper_model(model_size)
if not model:
raise RuntimeError("Motor Whisper Local no disponible (faltan dependencias o fall贸 la carga).")
with _whisper_lock:
result = model.transcribe(audio_path, language="es", fp16=False)
transcribed_text = result["text"]
elif provider_type == "whisper_api":
try:
from openai import OpenAI
except ImportError:
raise RuntimeError("La librer铆a 'openai' no est谩 instalada.")
if not config or not config.api_key:
raise RuntimeError("API Key no configurada para Whisper API.")
client = OpenAI(api_key=config.api_key)
with open(audio_path, "rb") as audio_file:
transcription = client.audio.transcriptions.create(
model="whisper-1",
file=audio_file,
language="es"
)
transcribed_text = transcription.text
elif provider_type == "nemotron":
model_name = config.model if config and config.model else "nvidia/nemotron-3.5-asr-streaming-0.6b"
model = get_nemotron_model(model_name)
if not model:
log.warning("Nemotron fall贸. Haciendo fallback autom谩tico a whisper_local.")
return transcribe_audio_file(audio_path, provider_type="whisper_local")
with _nemotron_lock:
import json
import tempfile
import json
import tempfile
# ponytail: get duration, NeMo's Lhotse adapter crashes without it
try:
import librosa
duration = float(librosa.get_duration(path=audio_path))
except Exception:
try:
import soundfile as sf
duration = float(sf.info(audio_path).duration)
except Exception:
duration = 10000.0 # safe fallback if both fail
# ponytail: write the dict to a manifest file, since NeMo doesn't accept dicts natively in audio[]
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as f:
manifest_data = {
"audio_filepath": audio_path,
"duration": duration,
"target_lang": "es",
"lang": "es", # LazyNeMoIterator uses 'lang' by default to set supervision.language!
"text": ""
}
f.write(json.dumps(manifest_data) + '\n')
manifest_path = f.name
try:
# ponytail: pass as positional arg without kwargs to avoid API signature mismatches
results = model.transcribe([manifest_path])
if isinstance(results, tuple):
results = results[0]
if isinstance(results, list) and len(results) > 0:
if isinstance(results[0], tuple):
transcribed_text = results[0][0]
else:
transcribed_text = results[0]
# ponytail: NeMo can return Hypothesis objects instead of strings! Extract the text.
if hasattr(transcribed_text, 'text'):
transcribed_text = transcribed_text.text
elif not isinstance(transcribed_text, str):
transcribed_text = str(transcribed_text)
except Exception as e:
raise e
finally:
import os
if os.path.exists(manifest_path):
os.remove(manifest_path)
else:
raise ValueError(f"Proveedor de transcripci贸n '{provider_type}' no soportado en el backend.")
except Exception as e:
log.exception(f"Error en transcribe_audio_file ({provider_type})")
error_msg = str(e)
end_time = time.time()
execution_time_ms = int((end_time - start_time) * 1000)
return transcribed_text, execution_time_ms, error_msg