| import os |
| from pathlib import Path |
| from typing import Dict, Optional |
|
|
| import soundfile as sf |
|
|
| from backend.synthesis_catalog import MAGPIE_LANGUAGES, MAGPIE_MODEL, MAGPIE_SPEAKERS |
| from backend.types import VoiceConfig |
|
|
| try: |
| import spaces |
| except ImportError: |
| class _SpacesShim: |
| @staticmethod |
| def GPU(fn=None, **_kwargs): |
| def decorate(inner): |
| return inner |
|
|
| if fn is not None: |
| return decorate(fn) |
| return decorate |
|
|
| spaces = _SpacesShim() |
|
|
| MAGPIE_SPEAKER_IDS = { |
| "John": 0, |
| "Sofia": 1, |
| "Aria": 2, |
| "Jason": 3, |
| "Leo": 4, |
| } |
| MAGPIE_SUPPORTED_LANGUAGES = {language["value"] for language in MAGPIE_LANGUAGES} |
|
|
|
|
| class MagpieAdapter: |
| def __init__( |
| self, |
| repo_id: str = "nvidia/magpie_tts_multilingual_357m", |
| checkpoint_filename: str = "magpie_tts_multilingual_357m.nemo", |
| codec_model_path: str = "nvidia/nemo-nano-codec-22khz-1.89kbps-21.5fps", |
| ) -> None: |
| self.repo_id = repo_id |
| self.checkpoint_filename = checkpoint_filename |
| self.codec_model_path = codec_model_path |
| self._model = None |
| self._engine = "unloaded" |
| self._load_error: Optional[Exception] = None |
|
|
| def _checkpoint_path(self) -> str: |
| from huggingface_hub import hf_hub_download |
|
|
| return hf_hub_download( |
| repo_id=self.repo_id, |
| filename=self.checkpoint_filename, |
| token=os.environ.get("HF_TOKEN"), |
| ) |
|
|
| def _load_model(self): |
| if self._model is not None: |
| return self._model |
| try: |
| import torch |
| from nemo.collections.tts.modules.magpietts_inference.utils import ( |
| ModelLoadConfig, |
| load_magpie_model, |
| ) |
| except Exception as exc: |
| self._engine = "load_failed" |
| self._load_error = exc |
| return None |
|
|
| config = ModelLoadConfig( |
| nemo_file=self._checkpoint_path(), |
| codecmodel_path=self.codec_model_path, |
| legacy_codebooks=False, |
| legacy_text_conditioning=False, |
| hparams_from_wandb=None, |
| ) |
| model, _ = load_magpie_model(config) |
| model.eval() |
| if torch.cuda.is_available(): |
| model.cuda() |
| self._model = model |
| self._engine = "magpie" |
| self._load_error = None |
| return self._model |
|
|
| def speaker_index_for(self, speaker: str) -> int: |
| if speaker not in MAGPIE_SPEAKER_IDS: |
| raise ValueError(f"Unsupported Magpie speaker: {speaker}") |
| return MAGPIE_SPEAKER_IDS[speaker] |
|
|
| @spaces.GPU(duration=300) |
| def synthesize( |
| self, |
| *, |
| text: str, |
| output_path: Path, |
| voice_config: VoiceConfig, |
| diffusion_steps: int, |
| speed: float, |
| language: Optional[str] = None, |
| ) -> Dict[str, object]: |
| del diffusion_steps, speed |
| output_path.parent.mkdir(parents=True, exist_ok=True) |
| speaker = voice_config.speaker or "Sofia" |
| speaker_index = self.speaker_index_for(speaker) |
| target_language = voice_config.language or language or "en" |
| if target_language not in MAGPIE_SUPPORTED_LANGUAGES: |
| raise ValueError(f"Unsupported Magpie language: {target_language}") |
|
|
| model = self._load_model() |
| if model is None: |
| detail = f"{type(self._load_error).__name__}: {self._load_error}" if self._load_error else "unknown error" |
| raise ValueError( |
| "Magpie TTS runtime is unavailable. " |
| "This app must install NVIDIA NeMo Magpie dependencies to synthesize real speech. " |
| f"Model load failed with {detail}." |
| ) |
|
|
| cleaned_text = text.strip() |
| if cleaned_text and cleaned_text[-1] not in ".?!": |
| cleaned_text = f"{cleaned_text}." |
| audio, audio_len = model.do_tts( |
| cleaned_text, |
| language=target_language, |
| apply_TN=voice_config.apply_text_normalization, |
| speaker_index=speaker_index, |
| ) |
| waveform = audio[0, : audio_len[0]].detach().cpu().numpy() |
| sample_rate = int(getattr(model, "sample_rate", 22050)) |
| sf.write(str(output_path), waveform, sample_rate) |
| duration_seconds = int(round(len(waveform) / sample_rate)) |
| return { |
| "duration_seconds": max(1, duration_seconds), |
| "sample_rate": sample_rate, |
| "backend": "local", |
| "model": MAGPIE_MODEL, |
| "engine": self._engine, |
| } |
|
|