| import io |
| import base64 |
| import logging |
| import subprocess |
| import sys |
|
|
| import numpy as np |
| import soundfile as sf |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def _install_deps(): |
| """Install OmniVoice and voicetut-tts with --no-deps to avoid conflicts |
| with the HF Inference Endpoint base image. |
| |
| Then upgrade transformers (WITH deps) because OmniVoice needs |
| HiggsAudioV2TokenizerModel which only exists in transformers>=4.50. |
| The HF Inference Toolkit already loaded successfully with the old |
| transformers at this point, so upgrading is safe.""" |
| |
| pkgs = [ |
| "git+https://github.com/k2-fsa/OmniVoice.git", |
| "voicetut-tts>=0.1.0", |
| ] |
| for pkg in pkgs: |
| try: |
| subprocess.check_call( |
| [sys.executable, "-m", "pip", "install", "--no-deps", pkg], |
| stdout=subprocess.DEVNULL, |
| stderr=subprocess.PIPE, |
| ) |
| logger.info(f"Installed {pkg}") |
| except subprocess.CalledProcessError as e: |
| stderr = e.stderr.decode() if e.stderr else "" |
| if "already satisfied" not in stderr.lower(): |
| logger.warning(f"Failed to install {pkg}: {stderr[:200]}") |
|
|
| |
| try: |
| subprocess.check_call( |
| [sys.executable, "-m", "pip", "install", "--upgrade", |
| "transformers>=4.50.0", "tokenizers>=0.21.0"], |
| stdout=subprocess.DEVNULL, |
| stderr=subprocess.PIPE, |
| ) |
| logger.info("Upgraded transformers for OmniVoice compatibility") |
| except subprocess.CalledProcessError as e: |
| stderr = e.stderr.decode() if e.stderr else "" |
| logger.warning(f"Failed to upgrade transformers: {stderr[:200]}") |
|
|
|
|
| |
| _install_deps() |
|
|
| from voicetut_tts import VoiceTutTTS, GenerationParams |
|
|
|
|
| class EndpointHandler: |
| def __init__(self, model_dir=""): |
| logger.info(f"Loading VoiceTut-TTS from {model_dir or 'mohammedaly22/VoiceTut-TTS'}...") |
| self.tts = VoiceTutTTS.from_pretrained( |
| model_dir or "mohammedaly22/VoiceTut-TTS", |
| dtype="float16", |
| ) |
| self.sampling_rate = self.tts.sampling_rate |
| logger.info(f"VoiceTut-TTS loaded. Sampling rate: {self.sampling_rate}") |
|
|
| def __call__(self, data: dict) -> dict: |
| inputs = data.get("inputs", data) |
|
|
| text = inputs.get("text", "").strip() |
| if not text: |
| return {"error": "text is required"} |
|
|
| voice = inputs.get("voice", "Mohamed") |
| language = inputs.get("language", "arz") |
| normalize = inputs.get("normalize", True) |
| num_step = int(inputs.get("num_step", 32)) |
| guidance_scale = float(inputs.get("guidance_scale", 2.0)) |
| speed = float(inputs.get("speed", 1.0)) |
|
|
| params = GenerationParams( |
| num_step=num_step, |
| guidance_scale=guidance_scale, |
| speed=speed, |
| ) |
|
|
| try: |
| wav = self.tts.synthesize( |
| text, |
| speaker=voice, |
| language=language, |
| normalize=normalize, |
| params=params, |
| ) |
| except Exception as e: |
| logger.exception("Synthesis failed") |
| return {"error": str(e)} |
|
|
| buf = io.BytesIO() |
| sf.write(buf, wav, self.sampling_rate, format="WAV") |
| wav_bytes = buf.getvalue() |
| wav_b64 = base64.b64encode(wav_bytes).decode("utf-8") |
|
|
| return { |
| "audio_base64": wav_b64, |
| "sampling_rate": self.sampling_rate, |
| "duration_sec": float(len(wav) / self.sampling_rate), |
| } |