Spaces:
Paused
Paused
Download apps/api/src/app/hf_models.py from nathycodes/presentercoach: direct link, hf CLI and curl.
- Browser
- Download file 11.6 kB
-
https://huggingface.co/spaces/nathycodes/presentercoach/resolve/main/apps/api/src/app/hf_models.py
- Command line
-
hf download hf://spaces/nathycodes/presentercoach/apps/api/src/app/hf_models.py
-
curl -L -o hf_models.py https://huggingface.co/spaces/nathycodes/presentercoach/resolve/main/apps/api/src/app/hf_models.py
11.6 kB
| from __future__ import annotations | |
| import inspect | |
| import json | |
| import re | |
| import shutil | |
| from pathlib import Path | |
| from typing import Any, Callable | |
| from app.config import settings | |
| SERVER_BACKEND = "server" | |
| MLX_AUDIO_BACKEND = "mlx_audio" | |
| DOWNLOAD_MARKER = ".sparks-model-download.json" | |
| SERVER_WHISPER_MODELS: tuple[dict[str, str], ...] = ( | |
| { | |
| "id": "openai/whisper-tiny", | |
| "name": "Whisper Tiny", | |
| "repo_id": "openai/whisper-tiny", | |
| "runtime_model_id": "tiny", | |
| "description": "OpenAI Whisper Tiny served through the faster-whisper runtime.", | |
| "languages": "99+ languages", | |
| "backend": SERVER_BACKEND, | |
| }, | |
| { | |
| "id": "openai/whisper-base", | |
| "name": "Whisper Base", | |
| "repo_id": "openai/whisper-base", | |
| "runtime_model_id": "base", | |
| "description": "OpenAI Whisper Base served through the faster-whisper runtime.", | |
| "languages": "99+ languages", | |
| "backend": SERVER_BACKEND, | |
| }, | |
| { | |
| "id": "openai/whisper-small", | |
| "name": "Whisper Small", | |
| "repo_id": "openai/whisper-small", | |
| "runtime_model_id": "small", | |
| "description": "OpenAI Whisper Small served through the faster-whisper runtime.", | |
| "languages": "99+ languages", | |
| "backend": SERVER_BACKEND, | |
| }, | |
| { | |
| "id": "openai/whisper-medium", | |
| "name": "Whisper Medium", | |
| "repo_id": "openai/whisper-medium", | |
| "runtime_model_id": "medium", | |
| "description": "OpenAI Whisper Medium served through the faster-whisper runtime.", | |
| "languages": "99+ languages", | |
| "backend": SERVER_BACKEND, | |
| }, | |
| { | |
| "id": "openai/whisper-large-v3", | |
| "name": "Whisper Large v3", | |
| "repo_id": "openai/whisper-large-v3", | |
| "runtime_model_id": "large-v3", | |
| "description": "OpenAI Whisper Large v3 served through the faster-whisper runtime.", | |
| "languages": "99+ languages", | |
| "backend": SERVER_BACKEND, | |
| }, | |
| { | |
| "id": "openai/whisper-large-v3-turbo", | |
| "name": "Whisper Large v3 Turbo", | |
| "repo_id": "openai/whisper-large-v3-turbo", | |
| "runtime_model_id": "turbo", | |
| "description": "OpenAI Whisper Large v3 Turbo served through the faster-whisper runtime.", | |
| "languages": "99+ languages", | |
| "backend": SERVER_BACKEND, | |
| }, | |
| ) | |
| MLX_AUDIO_STT_MODELS: tuple[dict[str, str], ...] = ( | |
| { | |
| "id": "mlx-community/whisper-large-v3-turbo-asr-fp16", | |
| "name": "Whisper", | |
| "repo_id": "mlx-community/whisper-large-v3-turbo-asr-fp16", | |
| "description": "OpenAI's robust speech-to-text model for multilingual transcription.", | |
| "languages": "99+ languages", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "distil-whisper/distil-large-v3", | |
| "name": "Distil-Whisper", | |
| "repo_id": "distil-whisper/distil-large-v3", | |
| "description": "Distilled Whisper variant optimized for faster English transcription.", | |
| "languages": "English", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "mlx-community/Qwen3-ASR-1.7B-8bit", | |
| "name": "Qwen3-ASR", | |
| "repo_id": "mlx-community/Qwen3-ASR-1.7B-8bit", | |
| "description": "Alibaba's multilingual ASR model for speech recognition across major languages.", | |
| "languages": "ZH, EN, JA, KO, and more", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "mlx-community/Qwen3-ForcedAligner-0.6B-8bit", | |
| "name": "Qwen3-ForcedAligner", | |
| "repo_id": "mlx-community/Qwen3-ForcedAligner-0.6B-8bit", | |
| "description": "Word-level audio alignment model for timestamp refinement and transcript sync.", | |
| "languages": "ZH, EN, JA, KO, and more", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "mlx-community/parakeet-tdt-0.6b-v3", | |
| "name": "Parakeet", | |
| "repo_id": "mlx-community/parakeet-tdt-0.6b-v3", | |
| "description": "NVIDIA's accurate speech recognizer with strong multilingual coverage.", | |
| "languages": "25 European languages", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "mlx-community/Voxtral-Mini-3B-2507-bf16", | |
| "name": "Voxtral", | |
| "repo_id": "mlx-community/Voxtral-Mini-3B-2507-bf16", | |
| "description": "Mistral's compact speech model for general-purpose transcription.", | |
| "languages": "Multiple languages", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "mlx-community/Voxtral-Mini-4B-Realtime-2602-4bit", | |
| "name": "Voxtral Realtime 4bit", | |
| "repo_id": "mlx-community/Voxtral-Mini-4B-Realtime-2602-4bit", | |
| "description": "Streaming-friendly Voxtral realtime model quantized for lighter local use.", | |
| "languages": "Multiple languages", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "mlx-community/Voxtral-Mini-4B-Realtime-2602-fp16", | |
| "name": "Voxtral Realtime fp16", | |
| "repo_id": "mlx-community/Voxtral-Mini-4B-Realtime-2602-fp16", | |
| "description": "Higher precision realtime Voxtral transcription model for local speech capture.", | |
| "languages": "Multiple languages", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| { | |
| "id": "mlx-community/VibeVoice-ASR-bf16", | |
| "name": "VibeVoice-ASR", | |
| "repo_id": "mlx-community/VibeVoice-ASR-bf16", | |
| "description": "Microsoft's ASR model with diarization and timestamp support.", | |
| "languages": "Multiple languages", | |
| "backend": MLX_AUDIO_BACKEND, | |
| }, | |
| ) | |
| def get_models_root() -> Path: | |
| storage_dir = Path(settings.storage_dir).expanduser() | |
| return storage_dir.parent / "models" / "hf" | |
| def _sanitize_repo_id(repo_id: str) -> str: | |
| return re.sub(r"[^A-Za-z0-9._-]+", "-", repo_id).strip("-") | |
| def _model_dir(repo_id: str) -> Path: | |
| return get_models_root() / _sanitize_repo_id(repo_id) | |
| def _download_marker_path(repo_id: str) -> Path: | |
| return _model_dir(repo_id) / DOWNLOAD_MARKER | |
| def _is_model_downloaded(repo_id: str) -> bool: | |
| model_dir = _model_dir(repo_id) | |
| return model_dir.exists() and _download_marker_path(repo_id).exists() | |
| def _build_model_status(model: dict[str, str]) -> dict[str, Any]: | |
| local_path = str(_model_dir(model["repo_id"])) if _is_model_downloaded(model["repo_id"]) else None | |
| return { | |
| "id": model["id"], | |
| "name": model["name"], | |
| "repo_id": model["repo_id"], | |
| "description": model["description"], | |
| "languages": model["languages"], | |
| "available": local_path is not None, | |
| "local_path": local_path, | |
| } | |
| def _prepare_model_dir(repo_id: str) -> Path: | |
| target_dir = _model_dir(repo_id) | |
| target_dir.parent.mkdir(parents=True, exist_ok=True) | |
| if target_dir.exists() and not _download_marker_path(repo_id).exists(): | |
| shutil.rmtree(target_dir, ignore_errors=True) | |
| return target_dir | |
| def _finalize_download(repo_id: str) -> None: | |
| _download_marker_path(repo_id).write_text( | |
| json.dumps({"repo_id": repo_id}, indent=2), | |
| encoding="utf-8", | |
| ) | |
| def _download_with_marker(model: dict[str, Any], downloader: Callable[[Path], None]) -> dict[str, Any]: | |
| if _is_model_downloaded(model["repo_id"]): | |
| return _build_model_status(model) | |
| target_dir = _prepare_model_dir(model["repo_id"]) | |
| try: | |
| downloader(target_dir) | |
| except Exception as exc: # pragma: no cover - defensive wrapper around third-party download errors | |
| if target_dir.exists() and not _download_marker_path(model["repo_id"]).exists(): | |
| shutil.rmtree(target_dir, ignore_errors=True) | |
| raise RuntimeError(f"Failed to download model '{model['repo_id']}': {exc}") from exc | |
| _finalize_download(model["repo_id"]) | |
| return _build_model_status(model) | |
| def get_server_model_ids() -> tuple[str, ...]: | |
| return tuple(model["id"] for model in SERVER_WHISPER_MODELS) | |
| def get_server_model_keys() -> set[str]: | |
| keys: set[str] = set() | |
| for model in SERVER_WHISPER_MODELS: | |
| keys.add(model["id"]) | |
| keys.add(model["repo_id"]) | |
| keys.add(model["runtime_model_id"]) | |
| return keys | |
| def get_server_model_catalog() -> list[dict[str, Any]]: | |
| return [_build_model_status(model) for model in SERVER_WHISPER_MODELS] | |
| def resolve_server_model_entry(model_name: str | None = None) -> dict[str, Any]: | |
| selected_model = model_name or settings.whisper_model | |
| for model in SERVER_WHISPER_MODELS: | |
| if selected_model in {model["id"], model["repo_id"], model["runtime_model_id"]}: | |
| return {**_build_model_status(model), "runtime_model_id": model["runtime_model_id"]} | |
| raise ValueError( | |
| f"Unsupported server transcription model '{selected_model}'. " | |
| f"Choose one of: {', '.join(get_server_model_ids())}" | |
| ) | |
| def download_server_model(model_name: str) -> dict[str, Any]: | |
| model = resolve_server_model_entry(model_name) | |
| try: | |
| import faster_whisper | |
| from faster_whisper import utils as faster_whisper_utils | |
| from faster_whisper.utils import download_model | |
| except ImportError as exc: | |
| raise RuntimeError( | |
| "faster-whisper is not installed in this runtime. " | |
| "Install it before downloading Whisper models." | |
| ) from exc | |
| supported_models = getattr(faster_whisper_utils, "_MODELS", {}) | |
| if isinstance(supported_models, dict) and model["runtime_model_id"] not in supported_models: | |
| runtime_version = getattr(faster_whisper, "__version__", "installed") | |
| raise ValueError( | |
| f"Whisper model '{model['name']}' is not supported by faster-whisper {runtime_version}. " | |
| f"Upgrade faster-whisper to a version that supports '{model['runtime_model_id']}' " | |
| "before downloading this model." | |
| ) | |
| parameters = inspect.signature(download_model).parameters | |
| def _download(target_dir: Path) -> None: | |
| if "output_dir" in parameters: | |
| download_model(model["runtime_model_id"], output_dir=str(target_dir)) | |
| return | |
| download_model(model["runtime_model_id"], str(target_dir)) | |
| return _download_with_marker(model, _download) | |
| def get_mlx_audio_model_ids() -> tuple[str, ...]: | |
| return tuple(model["id"] for model in MLX_AUDIO_STT_MODELS) | |
| def get_mlx_audio_model_keys() -> set[str]: | |
| keys: set[str] = set() | |
| for model in MLX_AUDIO_STT_MODELS: | |
| keys.add(model["id"]) | |
| keys.add(model["repo_id"]) | |
| return keys | |
| def get_mlx_audio_model_catalog() -> list[dict[str, Any]]: | |
| return [_build_model_status(model) for model in MLX_AUDIO_STT_MODELS] | |
| def resolve_mlx_audio_model_entry(model_name: str | None = None) -> dict[str, Any]: | |
| selected_model = model_name or settings.mlx_audio_model | |
| for model in MLX_AUDIO_STT_MODELS: | |
| if selected_model in {model["id"], model["repo_id"]}: | |
| return _build_model_status(model) | |
| raise ValueError( | |
| f"Unsupported MLX Audio model '{selected_model}'. " | |
| f"Choose one of: {', '.join(get_mlx_audio_model_ids())}" | |
| ) | |
| def download_mlx_audio_model(model_name: str) -> dict[str, Any]: | |
| model = resolve_mlx_audio_model_entry(model_name) | |
| try: | |
| from huggingface_hub import snapshot_download | |
| except ImportError as exc: | |
| raise RuntimeError( | |
| "huggingface_hub is not installed in this desktop runtime. " | |
| "Install it in the desktop environment to download MLX models." | |
| ) from exc | |
| def _download(target_dir: Path) -> None: | |
| snapshot_download(repo_id=model["repo_id"], local_dir=str(target_dir)) | |
| return _download_with_marker(model, _download) | |