Spaces:
Paused
Paused
| """ | |
| Audio processing utilities for CritterCalm. | |
| """ | |
| import numpy as np | |
| import soundfile as sf | |
| from pathlib import Path | |
| from typing import Optional, Tuple | |
| def load_audio(path: str, target_sr: int = 24000) -> Tuple[np.ndarray, int]: | |
| """Load audio file and resample if needed. Returns (samples, sample_rate).""" | |
| samples, sr = sf.read(path) | |
| if sr != target_sr: | |
| import librosa | |
| samples = librosa.resample(samples, orig_sr=sr, target_sr=target_sr) | |
| sr = target_sr | |
| # Convert to mono if stereo | |
| if samples.ndim > 1: | |
| samples = samples.mean(axis=1) | |
| return samples, sr | |
| def save_audio( | |
| samples: np.ndarray, | |
| path: str, | |
| sample_rate: int = 24000, | |
| normalize: bool = True, | |
| ) -> str: | |
| """Save audio samples to file. Normalizes to prevent clipping.""" | |
| if normalize: | |
| peak = np.abs(samples).max() | |
| if peak > 0.99: | |
| samples = samples / peak * 0.95 | |
| sf.write(path, samples, sample_rate) | |
| return path | |
| def get_audio_duration(path: str) -> float: | |
| """Return duration of audio file in seconds.""" | |
| samples, sr = sf.read(str(path)) | |
| if samples.ndim > 1: | |
| samples = samples.mean(axis=1) | |
| return len(samples) / sr | |
| def validate_voice_sample( | |
| path: str, | |
| min_duration: float = 3.0, | |
| max_duration: float = 30.0, | |
| ) -> Optional[str]: | |
| """ | |
| Validate a voice sample for cloning. | |
| Returns None if valid, or an error message string. | |
| """ | |
| import os | |
| if not os.path.exists(path): | |
| return "Audio file not found." | |
| try: | |
| duration = get_audio_duration(path) | |
| if duration < min_duration: | |
| return ( | |
| f"Recording is too short ({duration:.1f}s). " | |
| f"Please record at least {min_duration:.0f} seconds." | |
| ) | |
| if duration > max_duration: | |
| return ( | |
| f"Recording is too long ({duration:.1f}s). " | |
| f"Please keep it under {max_duration:.0f} seconds." | |
| ) | |
| return None # valid | |
| except Exception as exc: | |
| return f"Could not read audio file: {exc}" | |
| def estimate_tokens_per_second(text: str) -> float: | |
| """ | |
| Rough estimate of speech duration based on word count. | |
| Average English speech is ~150 words per minute (2.5 wps). | |
| Returns estimated seconds. | |
| """ | |
| words = len(text.split()) | |
| return words / 2.5 | |