2026-05-25 22:14:58 -07:00
|
|
|
import logging
|
|
|
|
|
|
2026-05-26 21:48:30 -07:00
|
|
|
import numpy as np
|
|
|
|
|
import whisperx
|
2026-05-25 22:14:58 -07:00
|
|
|
|
2026-05-26 21:07:58 -07:00
|
|
|
# Do not call basicConfig here, as it's called in the orchestrator
|
2026-05-25 22:14:58 -07:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Transcriber:
|
|
|
|
|
"""
|
2026-06-06 20:52:04 -07:00
|
|
|
Converts audio chunks (numpy arrays) into text using WhisperX.
|
2026-05-25 22:14:58 -07:00
|
|
|
"""
|
|
|
|
|
|
2026-05-26 21:48:30 -07:00
|
|
|
def __init__(
|
|
|
|
|
self, model_size="base", device="cpu", compute_type="int8", language="en"
|
|
|
|
|
):
|
2026-05-25 22:14:58 -07:00
|
|
|
"""
|
2026-06-06 20:52:04 -07:00
|
|
|
Initializes the WhisperX model.
|
2026-05-25 22:14:58 -07:00
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
model_size (str): The size of the model to use (e.g., "tiny", "base", "small").
|
|
|
|
|
device (str): The device to run the model on ("cpu" or "cuda").
|
|
|
|
|
compute_type (str): The compute type to use (e.g., "int8", "float16").
|
2026-05-26 21:48:30 -07:00
|
|
|
language (str): The language code for alignment (e.g., "en").
|
2026-05-25 22:14:58 -07:00
|
|
|
"""
|
2026-05-26 21:48:30 -07:00
|
|
|
self.device = device
|
|
|
|
|
self.compute_type = compute_type
|
|
|
|
|
self.language = language
|
|
|
|
|
|
2026-05-25 22:14:58 -07:00
|
|
|
logger.info(
|
2026-05-26 21:48:30 -07:00
|
|
|
f"Loading WhisperX model: {model_size} on {device} ({compute_type})..."
|
2026-05-25 22:14:58 -07:00
|
|
|
)
|
|
|
|
|
try:
|
2026-05-26 21:48:30 -07:00
|
|
|
# Load transcription model
|
|
|
|
|
self.model = whisperx.load_model(
|
2026-05-25 22:14:58 -07:00
|
|
|
model_size, device=device, compute_type=compute_type
|
|
|
|
|
)
|
2026-05-26 21:48:30 -07:00
|
|
|
|
2026-06-06 20:52:04 -07:00
|
|
|
logger.info("WhisperX model loaded successfully.")
|
2026-05-25 22:14:58 -07:00
|
|
|
except Exception as e:
|
2026-05-26 21:48:30 -07:00
|
|
|
logger.error(f"Failed to load WhisperX models: {e}")
|
2026-05-25 22:14:58 -07:00
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
def transcribe(self, audio_chunk):
|
|
|
|
|
"""
|
2026-06-06 20:52:04 -07:00
|
|
|
Transcribes an audio chunk.
|
2026-05-25 22:14:58 -07:00
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
audio_chunk (np.ndarray): The audio data as a numpy array.
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-06-06 20:52:04 -07:00
|
|
|
list: A list of tuples (speaker_id, text, start, end).
|
2026-05-25 22:14:58 -07:00
|
|
|
"""
|
|
|
|
|
if audio_chunk is None:
|
2026-05-26 21:48:30 -07:00
|
|
|
return []
|
2026-05-25 22:14:58 -07:00
|
|
|
|
|
|
|
|
try:
|
2026-05-26 21:48:30 -07:00
|
|
|
# WhisperX expects audio in float32 and 1D array
|
|
|
|
|
audio = audio_chunk.astype("float32").flatten()
|
|
|
|
|
|
|
|
|
|
# 1. Perform transcription
|
|
|
|
|
# batch_size is set to 16 for efficiency; can be adjusted based on VRAM
|
|
|
|
|
result = self.model.transcribe(audio, batch_size=16)
|
|
|
|
|
|
2026-06-06 20:52:04 -07:00
|
|
|
# Extract ("Unknown", text, start, end) tuples from the transcription result
|
2026-05-26 21:48:30 -07:00
|
|
|
output = []
|
2026-06-06 20:52:04 -07:00
|
|
|
for segment in result.get("segments", []):
|
2026-05-26 21:48:30 -07:00
|
|
|
text = segment.get("text", "").strip()
|
|
|
|
|
start = segment.get("start", 0.0)
|
|
|
|
|
end = segment.get("end", 0.0)
|
|
|
|
|
if text:
|
2026-06-06 20:52:04 -07:00
|
|
|
output.append(("Unknown", text, start, end))
|
2026-05-25 22:14:58 -07:00
|
|
|
|
2026-05-26 21:48:30 -07:00
|
|
|
return output
|
2026-05-25 22:14:58 -07:00
|
|
|
except Exception as e:
|
2026-06-06 20:52:04 -07:00
|
|
|
logger.error(f"Transcription error: {e}")
|
2026-05-26 21:48:30 -07:00
|
|
|
return []
|
2026-05-25 22:14:58 -07:00
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
|
"""
|
|
|
|
|
Explicitly release model resources if necessary.
|
|
|
|
|
"""
|
|
|
|
|
pass
|