Spaces:
Sleeping
Sleeping
| """ | |
| Piper TTS — API Backend for GitHub Pages Frontend | |
| يقبل طلبات من الواجهة الخارجية ويعيد الصوت | |
| """ | |
| import os | |
| import subprocess | |
| import tempfile | |
| import gradio as gr | |
| os.makedirs("voices", exist_ok=True) | |
| VOICES = { | |
| "ar_kareem": {"model": "voices/ar.onnx", "url_onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx", "url_json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/ar/ar_JO/kareem/medium/ar_JO-kareem-medium.onnx.json"}, | |
| "en_us_lessac":{"model": "voices/en_us.onnx", "url_onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx", "url_json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx.json"}, | |
| "fr_siwis": {"model": "voices/fr.onnx", "url_onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/siwis/medium/fr_FR-siwis-medium.onnx", "url_json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/fr/fr_FR/siwis/medium/fr_FR-siwis-medium.onnx.json"}, | |
| "es_carlfm": {"model": "voices/es.onnx", "url_onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/carlfm/x_low/es_ES-carlfm-x_low.onnx", "url_json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_ES/carlfm/x_low/es_ES-carlfm-x_low.onnx.json"}, | |
| "de_thorsten": {"model": "voices/de.onnx", "url_onnx": "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/medium/de_DE-thorsten-medium.onnx", "url_json": "https://huggingface.co/rhasspy/piper-voices/resolve/main/de/de_DE/thorsten/medium/de_DE-thorsten-medium.onnx.json"}, | |
| } | |
| def download_voice(info): | |
| onnx = info["model"] | |
| if not os.path.exists(onnx): | |
| subprocess.run(["wget", "-q", "-O", onnx, info["url_onnx"]], check=True) | |
| subprocess.run(["wget", "-q", "-O", onnx + ".json", info["url_json"]], check=True) | |
| for key, info in VOICES.items(): | |
| try: | |
| download_voice(info) | |
| print(f"✅ {key} ready") | |
| except Exception as e: | |
| print(f"⚠️ {key}: {e}") | |
| def synthesize(text: str, voice_id: str): | |
| if not text or not text.strip(): | |
| raise gr.Error("⚠️ النص فارغ") | |
| if voice_id not in VOICES: | |
| raise gr.Error(f"⚠️ الصوت '{voice_id}' غير موجود") | |
| info = VOICES[voice_id] | |
| if not os.path.exists(info["model"]): | |
| raise gr.Error("الصوت غير متاح حالياً") | |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: | |
| out = tmp.name | |
| r = subprocess.run( | |
| f'echo "{text}" | python -m piper --model {info["model"]} --output_file {out}', | |
| shell=True, capture_output=True, text=True | |
| ) | |
| if r.returncode != 0: | |
| raise gr.Error(f"خطأ: {r.stderr[:200]}") | |
| return out | |
| # واجهة بسيطة للـ Space نفسه + API تلقائي عبر Gradio | |
| with gr.Blocks(title="Piper TTS API") as demo: | |
| gr.Markdown("## 🔊 Piper TTS — API Backend\nهذا الـ Space يعمل كـ API للواجهة الخارجية على GitHub Pages.") | |
| with gr.Row(): | |
| voice_dd = gr.Dropdown(choices=list(VOICES.keys()), value="ar_kareem", label="Voice ID") | |
| text_in = gr.Textbox(label="النص", value="مرحباً، هذا اختبار.") | |
| btn = gr.Button("توليد", variant="primary") | |
| audio_out = gr.Audio(label="الصوت", type="filepath", autoplay=True) | |
| btn.click(fn=synthesize, inputs=[text_in, voice_dd], outputs=audio_out) | |
| if __name__ == "__main__": | |
| demo.launch() | |