import asyncio import html import json import os import tempfile import edge_tts import gradio as gr import requests STYLE_PROMPTS = { "رومانسي": "اكتب بصياغة شاعرية رومانسية رقيقة ومؤثرة", "حزين": "اكتب بصياغة شاعرية حزينة وعميقة ومليئة بالحنين", "فصيح كلاسيكي": "اكتب بأسلوب عربي فصيح مائل إلى النبرة الكلاسيكية والبيان الجميل", "شعر حر": "اكتب بأسلوب شعر حر حديث، مكثف، وصوري", "فخم / ملحمي": "اكتب بنبرة فخمة وقوية ومهيبة مع صور بلاغية واضحة", } # Arabic voices for Edge TTS ARABIC_VOICES = { "هديل (أنثى - السعودية)": "ar-SA-HamedNeural", "سعيد (ذكر - السعودية)": "ar-SA-HamedNeural", "نورة (أنثى - مصر)": "ar-SA-Neural", "شاكر (ذكر - مصر)": "ar-EG-ShakirNeural", "سلمى (أنثى - الإمارات)": "ar-AE-FatimaNeural", "حمد (ذكر - الإمارات)": "ar-AE-HamdanNeural", "ليلى (أنثى - العراق)": "ar-IQ-RanaNeural", "باسم (ذكر - العراق)": "ar-IQ-BasselNeural", "توفيق (ذكر - الأردن)": "ar-JO-TaimNeural", "سناء (أنثى - لبنان)": "ar-LB-LaylaNeural", "كريم (ذكر - لبنان)": "ar-LB-RamiNeural", } DEFAULT_VOICE = "ar-SA-HamedNeural" EXAMPLES = [ ["اشتقت إليها كثيرًا هذه الليلة", "رومانسي", 0.7], ["أشعر بوحدة كبيرة منذ رحيله", "حزين", 0.8], ["أريد عبارة مؤثرة عن الأمل بعد التعب", "شعر حر", 0.6], ["قلبي متعلق بها ولا أستطيع نسيانها", "فصيح كلاسيكي", 0.7], ] OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions" DEFAULT_MODEL = os.getenv("OPENROUTER_MODEL", "google/gemma-3-27b-it:free") APP_URL = os.getenv("SPACE_HOST", "https://huggingface.co/spaces/IsemIsCold/IsemPoeticVoice") APP_TITLE = os.getenv("OPENROUTER_APP_TITLE", "IsemPoeticVoice") # UI Translations for multiple languages UI_TRANSLATIONS = { "العربية": { "title": "IsemPoeticVoice", "subtitle": "حوّل النص العربي العادي إلى صياغات شعرية قصيرة باستخدام الذكاء الاصطناعي، ثم استمع إليها بصوت عربي طبيعي.", "user_text_label": "النص العادي", "user_text_placeholder": "اكتب جملة عربية بسيطة مثل: اشتقت إليها كثيرًا هذه الليلة", "style_label": "الأسلوب", "creativity_label": "مستوى الإبداع", "generate_button": "توليد الصياغات الشعرية", "prompt_preview_label": "معاينة البرومبت المرسل إلى النموذج", "verse_1_label": "النص 1", "verse_2_label": "النص 2", "verse_3_label": "النص 3", "verse_choice_label": "اختر النص الذي تريد تحويله إلى صوت", "verse_choice_1": "النص 1", "verse_choice_2": "النص 2", "verse_choice_3": "النص 3", "selected_verse_label": "النص المختار", "voice_label": "الصوت", "rate_label": "سرعة الصوت", "voice_button": "توليد الصوت الشعري 🎵", "audio_output_label": "الصوت الناتج", "share_card_label": "بطاقة المعاينة", "no_text_selected": "لا يوجد نص مختار بعد.", "write_first": "اكتب جملة عربية أولًا لتوليد الأبيات.", "error_message": "حدث خطأ أثناء الاتصال بـ OpenRouter", "examples_label": "أمثلة", }, "English": { "title": "IsemPoeticVoice", "subtitle": "Transform ordinary Arabic text into short poetic verses using AI, then listen to them in natural Arabic voice.", "user_text_label": "Plain Text", "user_text_placeholder": "Write a simple Arabic sentence like: I missed her so much tonight", "style_label": "Style", "creativity_label": "Creativity Level", "generate_button": "Generate Poetic Verses", "prompt_preview_label": "Prompt Preview Sent to Model", "verse_1_label": "Verse 1", "verse_2_label": "Verse 2", "verse_3_label": "Verse 3", "verse_choice_label": "Select verse to convert to speech", "verse_choice_1": "Verse 1", "verse_choice_2": "Verse 2", "verse_choice_3": "Verse 3", "selected_verse_label": "Selected Verse", "voice_label": "Voice", "rate_label": "Speech Rate", "voice_button": "Generate Poetic Voice 🎵", "audio_output_label": "Audio Output", "share_card_label": "Preview Card", "no_text_selected": "No text selected yet.", "write_first": "Write an Arabic sentence first to generate verses.", "error_message": "An error occurred while connecting to OpenRouter", "examples_label": "Examples", }, "Français": { "title": "IsemPoeticVoice", "subtitle": "Transformez un texte arabe ordinaire en courts vers poétiques grâce à l'IA, puis écoutez-les avec une voix arabe naturelle.", "user_text_label": "Texte simple", "user_text_placeholder": "Écrivez une phrase arabe simple comme: Elle m'a beaucoup manqué ce soir", "style_label": "Style", "creativity_label": "Niveau de créativité", "generate_button": "Générer des vers poétiques", "prompt_preview_label": "Aperçu du prompt envoyé au modèle", "verse_1_label": "Vers 1", "verse_2_label": "Vers 2", "verse_3_label": "Vers 3", "verse_choice_label": "Sélectionnez le vers à convertir en parole", "verse_choice_1": "Vers 1", "verse_choice_2": "Vers 2", "verse_choice_3": "Vers 3", "selected_verse_label": "Vers sélectionné", "voice_label": "Voix", "rate_label": "Vitesse de parole", "voice_button": "Générer la voix poétique 🎵", "audio_output_label": "Sortie audio", "share_card_label": "Carte d'aperçu", "no_text_selected": "Aucun texte sélectionné pour le moment.", "write_first": "Écrivez d'abord une phrase en arabe pour générer des vers.", "error_message": "Une erreur s'est produite lors de la connexion à OpenRouter", "examples_label": "Exemples", }, "Español": { "title": "IsemPoeticVoice", "subtitle": "Transforma texto árabe ordinario en cortos versos poéticos usando IA, luego escúchalos con voz árabe natural.", "user_text_label": "Texto simple", "user_text_placeholder": "Escribe una frase árabe simple como: La extrañé mucho esta noche", "style_label": "Estilo", "creativity_label": "Nivel de creatividad", "generate_button": "Generar versos poéticos", "prompt_preview_label": "Vista previa del prompt enviado al modelo", "verse_1_label": "Verso 1", "verse_2_label": "Verso 2", "verse_3_label": "Verso 3", "verse_choice_label": "Selecciona el verso para convertir a voz", "verse_choice_1": "Verso 1", "verse_choice_2": "Verso 2", "verse_choice_3": "Verso 3", "selected_verse_label": "Verso seleccionado", "voice_label": "Voz", "rate_label": "Velocidad de habla", "voice_button": "Generar voz poética 🎵", "audio_output_label": "Salida de audio", "share_card_label": "Tarjeta de vista previa", "no_text_selected": "No hay texto seleccionado aún.", "write_first": "Escribe primero una frase en árabe para generar versos.", "error_message": "Ocurrió un error al conectar con OpenRouter", "examples_label": "Ejemplos", }, "Türkçe": { "title": "IsemPoeticVoice", "subtitle": "Sıradan Arapça metni yapay zeka kullanarak kısa şiirsel dizelere dönüştürün, ardından doğal Arapça sesle dinleyin.", "user_text_label": "Düz Metin", "user_text_placeholder": "Bu gece onu çok özledim gibi basit bir Arapça cümle yazın", "style_label": "Stil", "creativity_label": "Yaratıcılık Seviyesi", "generate_button": "Şiirsel Dizeler Oluştur", "prompt_preview_label": "Modele Gönderilen İstem Önizlemesi", "verse_1_label": "Dize 1", "verse_2_label": "Dize 2", "verse_3_label": "Dize 3", "verse_choice_label": "Sese dönüştürülecek dizeyi seçin", "verse_choice_1": "Dize 1", "verse_choice_2": "Dize 2", "verse_choice_3": "Dize 3", "selected_verse_label": "Seçilen Dize", "voice_label": "Ses", "rate_label": "Konuşma Hızı", "voice_button": "Şiirsel Ses Oluştur 🎵", "audio_output_label": "Ses Çıktısı", "share_card_label": "Önizleme Kartı", "no_text_selected": "Henüz seçili metin yok.", "write_first": "Dize oluşturmak için önce Arapça bir cümle yazın.", "error_message": "OpenRouter'a bağlanırken bir hata oluştu", "examples_label": "Örnekler", }, } # Style name translations STYLE_TRANSLATIONS = { "العربية": { "رومانسي": "رومانسي", "حزين": "حزين", "فصيح كلاسيكي": "فصيح كلاسيكي", "شعر حر": "شعر حر", "فخم / ملحمي": "فخم / ملحمي", }, "English": { "رومانسي": "Romantic", "حزين": "Sad", "فصيح كلاسيكي": "Classical", "شعر حر": "Free Verse", "فخم / ملحمي": "Epic", }, "Français": { "رومانسي": "Romantique", "حزين": "Triste", "فصيح كلاسيكي": "Classique", "شعر حر": "Vers Libre", "فخم / ملحمي": "Épique", }, "Español": { "رومانسي": "Romántico", "حزين": "Triste", "فصيح كلاسيكي": "Clásico", "شعر حر": "Verso Libre", "فخم / ملحمي": "Épico", }, "Türkçe": { "رومانسي": "Romantik", "حزين": "Hüzünlü", "فصيح كلاسيكي": "Klasik", "شعر حر": "Serbest Nazım", "فخم / ملحمي": "Destansı", }, } def get_ui_text(lang: str, key: str) -> str: """Get UI text for a given language and key""" return UI_TRANSLATIONS.get(lang, UI_TRANSLATIONS["العربية"]).get(key, key) def get_style_choices(lang: str) -> list: """Get style choices with translated display names""" style_trans = STYLE_TRANSLATIONS.get(lang, STYLE_TRANSLATIONS["العربية"]) return [f"{trans} ({arabic})" for arabic, trans in style_trans.items()] def get_style_from_choice(choice: str) -> str: """Extract Arabic style name from choice string""" # Format is "Translation (العربية)" if "(" in choice and ")" in choice: start = choice.rfind("(") + 1 end = choice.rfind(")") return choice[start:end] return choice def build_prompt(user_text: str, style: str, creativity: float) -> str: style_instruction = STYLE_PROMPTS.get(style, STYLE_PROMPTS["شعر حر"]) return ( "حوّل النص العربي العادي الآتي إلى 3 صيغ شعرية قصيرة وجميلة.\n" f"{style_instruction}.\n" f"مستوى الإبداع المطلوب: {creativity:.1f} من 1.0.\n" "الشروط:\n" "- حافظ على المعنى الأساسي.\n" "- اجعل المخرجات مناسبة للإلقاء الصوتي.\n" "- لا تكتب أي شرح خارج JSON.\n" "- استخدم العربية الفصحى السلسة.\n" "- اجعل كل صيغة مختلفة عن الأخرى بوضوح.\n\n" f"النص الأصلي: {user_text.strip()}" ) def call_openrouter(user_text: str, style: str, creativity: float): api_key = ( os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENROUTERAPIKEY") or os.getenv("OPEN_ROUTER_API_KEY") ) if not api_key: all_env_names = sorted( [key for key in os.environ.keys() if "OPENROUTER" in key or "ROUTER" in key] ) debug_names = ", ".join(all_env_names) if all_env_names else "No related env vars found" raise RuntimeError( "Missing OpenRouter API key. Expected one of: OPENROUTER_API_KEY, " f"OPENROUTERAPIKEY, OPEN_ROUTER_API_KEY. Detected env names: {debug_names}" ) prompt = build_prompt(user_text, style, creativity) system_message = ( "أنت مساعد متخصص في إعادة كتابة النصوص العربية العادية إلى صياغات شعرية عربية جميلة. " "أعد النتيجة فقط بصيغة JSON صحيحة وبدون أي شرح إضافي. " 'صيغة JSON المطلوبة هي: {"variants":["النص الأول","النص الثاني","النص الثالث"]}. ' "يجب أن تحتوي variants على 3 نصوص شعرية قصيرة فقط." ) payload = { "model": DEFAULT_MODEL, "messages": [ {"role": "system", "content": system_message}, {"role": "user", "content": prompt}, ], "temperature": creativity, "max_tokens": 350, "response_format": {"type": "json_object"}, } headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", "HTTP-Referer": APP_URL, "X-OpenRouter-Title": APP_TITLE, } response = requests.post(OPENROUTER_API_URL, headers=headers, json=payload, timeout=60) # Handle rate limiting (429) more gracefully if response.status_code == 429: raise RuntimeError("Rate limit exceeded. Please wait a moment and try again, or add credits to your OpenRouter account.") response.raise_for_status() data = response.json() content = data["choices"][0]["message"]["content"] parsed = json.loads(content) variants = parsed.get("variants", []) if not isinstance(variants, list) or len(variants) < 3: raise RuntimeError("OpenRouter returned an unexpected format. Expected 3 variants in JSON.") cleaned = [str(item).strip() for item in variants[:3]] while len(cleaned) < 3: cleaned.append("") return prompt, cleaned def generate_poetry(user_text: str, style: str, creativity: float, lang: str = "العربية"): user_text = user_text.strip() if not user_text: empty = "" return ( get_ui_text(lang, "write_first"), empty, empty, empty, empty, None, f"