Spaces:
Running on Zero
Running on Zero
| """ | |
| Utility helpers for the improved AICoverGen. | |
| """ | |
| import os | |
| import re | |
| from typing import Any, Dict | |
| def format_time(seconds: float) -> str: | |
| """Format seconds as M:SS or H:MM:SS.""" | |
| if seconds is None or seconds < 0: | |
| return "0:00" | |
| seconds = int(seconds) | |
| hours = seconds // 3600 | |
| minutes = (seconds % 3600) // 60 | |
| secs = seconds % 60 | |
| if hours > 0: | |
| return f"{hours}:{minutes:02d}:{secs:02d}" | |
| return f"{minutes}:{secs:02d}" | |
| def get_audio_info(path: str) -> Dict[str, Any]: | |
| """Return basic info about an audio file (duration, sample rate, channels).""" | |
| info: Dict[str, Any] = { | |
| "path": path, | |
| "exists": False, | |
| "duration": 0.0, | |
| "sample_rate": 0, | |
| "channels": 0, | |
| "size_bytes": 0, | |
| } | |
| if not path or not os.path.exists(path): | |
| return info | |
| info["exists"] = True | |
| info["size_bytes"] = os.path.getsize(path) | |
| try: | |
| import soundfile as sf | |
| with sf.SoundFile(path) as f: | |
| info["duration"] = len(f) / f.samplerate | |
| info["sample_rate"] = f.samplerate | |
| info["channels"] = f.channels | |
| except Exception: | |
| try: | |
| import librosa | |
| y, sr = librosa.load(path, sr=None, mono=False) | |
| info["sample_rate"] = sr | |
| if y.ndim > 1: | |
| info["channels"] = y.shape[0] | |
| info["duration"] = y.shape[1] / sr | |
| else: | |
| info["channels"] = 1 | |
| info["duration"] = y.shape[0] / sr | |
| except Exception: | |
| pass | |
| return info | |
| def human_size(num_bytes: int) -> str: | |
| """Convert bytes to human-readable string.""" | |
| if num_bytes is None or num_bytes < 0: | |
| return "0 B" | |
| units = ["B", "KB", "MB", "GB", "TB"] | |
| size = float(num_bytes) | |
| idx = 0 | |
| while size >= 1024 and idx < len(units) - 1: | |
| size /= 1024 | |
| idx += 1 | |
| return f"{size:.1f} {units[idx]}" | |