""" Bark TTS microservice for Ebonex AI — Hugging Face Spaces deployment. Bark (by Suno) generates highly expressive, emotional audio from text. It supports laughter, breathing, whispering, and dramatic storytelling — perfect for the Ebonex Kids Mode. Arabic voice presets: v2/ar_speaker_0 through v2/ar_speaker_9 English voice presets: v2/en_speaker_0 through v2/en_speaker_9 Recommended for storytelling: Arabic: v2/ar_speaker_9 (warm, expressive narrator) English: v2/en_speaker_6 (clear, warm storyteller) """ import base64 import io import os import numpy as np import soundfile as sf from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from bark import SAMPLE_RATE, generate_audio, preload_models # Pre-load models on startup (takes ~30s, only once) # Set SUNO_USE_SMALL_MODELS=True for faster but lower-quality output on CPU use_small = os.environ.get("SUNO_USE_SMALL_MODELS", "True").lower() == "true" preload_models( text_use_small=use_small, coarse_use_small=use_small, fine_use_small=use_small, ) app = FastAPI( title="Bark TTS for Ebonex", description="Storytelling TTS powered by Suno Bark", version="1.0.0", ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["POST", "GET"], allow_headers=["*"], ) class TtsRequest(BaseModel): text: str voice_preset: str = "v2/ar_speaker_9" @app.get("/") def root(): return {"status": "ok", "service": "Bark TTS for Ebonex"} @app.get("/health") def health(): return {"status": "ok"} @app.post("/tts") def text_to_speech(req: TtsRequest): if not req.text or not req.text.strip(): raise HTTPException(status_code=400, detail="text is required") # Bark works best with texts under 400 characters. # For longer texts, split and concatenate (handled by TtsService on Node side). text = req.text.strip()[:400] try: audio_array = generate_audio(text, history_prompt=req.voice_preset) except Exception as e: raise HTTPException(status_code=500, detail=f"Bark generation failed: {str(e)}") # Convert numpy float32 array → WAV bytes → base64 string buffer = io.BytesIO() sf.write(buffer, audio_array, SAMPLE_RATE, format="WAV", subtype="PCM_16") buffer.seek(0) audio_b64 = base64.b64encode(buffer.read()).decode("utf-8") return { "audio": audio_b64, "sample_rate": SAMPLE_RATE, "format": "wav", "voice_preset": req.voice_preset, }