| import librosa |
| import numpy as np |
| import os |
| import soundfile as sf |
| import tempfile |
| import torch |
|
|
| |
| ASR_MODEL = None |
| ASR_PROCESSOR = None |
| DEVICE = "cpu" |
|
|
|
|
| def set_asr_globals(model, processor, device): |
| global ASR_MODEL, ASR_PROCESSOR, DEVICE |
| ASR_MODEL = model |
| ASR_PROCESSOR = processor |
| DEVICE = device |
|
|
|
|
| def reduce_noise(audio_array: np.ndarray, sr: int = 16000, noise_reduce_strength: float = 0.7) -> np.ndarray: |
| try: |
| stft = librosa.stft(audio_array, n_fft=2048, hop_length=512) |
| magnitude, phase = librosa.magphase(stft) |
| frame_energies = np.sum(magnitude, axis=0) |
| noise_threshold = np.percentile(frame_energies, 10) |
| noise_frames = magnitude[:, frame_energies <= noise_threshold] |
| if noise_frames.shape[1] > 0: |
| noise_profile = np.mean(noise_frames, axis=1, keepdims=True) |
| else: |
| noise_profile = np.min(magnitude, axis=1, keepdims=True) |
| magnitude_denoised = magnitude - (noise_reduce_strength * noise_profile) |
| magnitude_denoised = np.maximum(magnitude_denoised, 0.0) |
| smoothing_factor = 0.05 |
| magnitude_denoised = (1 - smoothing_factor) * magnitude_denoised + smoothing_factor * magnitude |
| stft_denoised = magnitude_denoised * phase |
| audio_denoised = librosa.istft(stft_denoised, hop_length=512, length=len(audio_array)) |
| original_peak = np.abs(audio_array).max() |
| denoised_peak = np.abs(audio_denoised).max() |
| if denoised_peak > 0: |
| audio_denoised = audio_denoised * (original_peak / denoised_peak) |
| return audio_denoised |
| except Exception as e: |
| print(f"[WARNING] Noise reduction failed: {e}, returning original audio") |
| return audio_array |
|
|
|
|
| def should_reduce_noise() -> bool: |
| return os.environ.get("USE_NOISE_REDUCTION", "false").lower() == "true" |
|
|
|
|
| def convert_audio_to_wav(audio_bytes: bytes, target_sr: int = 16000, filename: str = None) -> np.ndarray: |
| if not audio_bytes or len(audio_bytes) == 0: |
| raise ValueError("No audio data received") |
| if filename: |
| ext = os.path.splitext(filename)[1].lower() |
| if not ext: |
| ext = ".wav" |
| else: |
| ext = ".wav" |
| with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as tmp_file: |
| tmp_file.write(audio_bytes) |
| tmp_path = tmp_file.name |
| try: |
| try: |
| audio_array, sr = sf.read(tmp_path, dtype="float32") |
| if len(audio_array.shape) > 1: |
| audio_array = audio_array.mean(axis=1) |
| if sr != target_sr: |
| audio_array = librosa.resample(audio_array, orig_sr=sr, target_sr=target_sr) |
| except Exception: |
| audio_array, sr = librosa.load( |
| tmp_path, |
| sr=target_sr, |
| mono=True, |
| res_type="kaiser_best", |
| ) |
| if audio_array is None or len(audio_array) == 0: |
| raise ValueError("Audio file is empty or unreadable") |
| max_val = np.abs(audio_array).max() |
| if max_val > 0: |
| if max_val > 1.0: |
| audio_array = audio_array / max_val |
| else: |
| raise ValueError("Audio contains only silence") |
| if should_reduce_noise(): |
| audio_array = reduce_noise(audio_array, sr=target_sr, noise_reduce_strength=0.7) |
| return audio_array |
| except Exception as e: |
| raise ValueError(f"Failed to convert audio file '{filename or 'unknown'}': {str(e)}") |
| finally: |
| if os.path.exists(tmp_path): |
| try: |
| os.unlink(tmp_path) |
| except Exception: |
| pass |
|
|
|
|
| def validate_audio_duration(audio_array: np.ndarray, sr: int = 16000) -> bool: |
| duration = len(audio_array) / sr |
| if duration < 0.5: |
| raise ValueError(f"Audio too short: {duration:.2f}s (minimum 0.5s)") |
| if duration > 20: |
| raise ValueError(f"Audio too long: {duration:.2f}s (maximum 20s)") |
| return True |
|
|
|
|
| def transcribe_audio(audio_array: np.ndarray, sr: int = 16000, return_ctc_data: bool = False): |
| if ASR_MODEL is None or ASR_PROCESSOR is None: |
| raise RuntimeError("ASR model not loaded") |
| try: |
| inputs = ASR_PROCESSOR( |
| audio_array, |
| sampling_rate=sr, |
| return_tensors="pt", |
| padding=True, |
| ) |
| inputs = {k: v.to(DEVICE) for k, v in inputs.items()} |
| with torch.inference_mode(): |
| logits = ASR_MODEL(**inputs).logits |
| predicted_ids = torch.argmax(logits, dim=-1) |
| transcription = ASR_PROCESSOR.batch_decode(predicted_ids)[0] |
| probs = torch.nn.functional.softmax(logits, dim=-1) |
| confidence_scores = torch.max(probs, dim=-1)[0].cpu().numpy()[0] |
| if return_ctc_data: |
| return transcription.strip(), confidence_scores, logits, predicted_ids |
| return transcription.strip(), confidence_scores |
| except Exception as e: |
| raise RuntimeError(f"ASR transcription failed: {str(e)}") |
|
|