Spaces:
Paused
Paused
| import os | |
| import re | |
| import json | |
| import uuid | |
| import gc | |
| import asyncio | |
| import threading | |
| import signal | |
| import numpy as np | |
| from scipy.io import wavfile | |
| from pydub import AudioSegment | |
| import gradio as gr | |
| import edge_tts | |
| from huggingface_hub import hf_hub_download, HfApi | |
| # ----------------- ุญู ู ุดููุฉ ุฃู ุงู PyTorch ูุงูู Checkpoint (ุทุฑููุฉ Monkey-patch) ----------------- | |
| import torch | |
| _orig_torch_load = torch.load | |
| def _patched_torch_load(*args, **kwargs): | |
| kwargs.setdefault("weights_only", False) | |
| return _orig_torch_load(*args, **kwargs) | |
| torch.load = _patched_torch_load | |
| # ุงูู ูุงููุฉ ุงูุชููุงุฆูุฉ ุนูู ุดุฑูุท ุงุณุชุฎุฏุงู Coqui ูุชูุงุฏู ุชููู ุงูุณูุฑูุฑ | |
| os.environ['COQUI_TOS_AGREED'] = '1' | |
| from TTS.api import TTS | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุฅุนุฏุงุฏุงุช ุงูุจูุฆุฉ ูุงูุฎุฒูุฉ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| HF_TOKEN = os.environ.get("HF_TOKEN", "").strip() | |
| # ๐ ู ูุชุงุญ ุณุฑู ู ุดุชุฑู ูุญู ุงูุฉ ููุทุฉ API ุงูุฎุงุฑุฌูุฉ ููุท (ุงููุงุฌูุฉ ุงูุนุงู ุฉ ุชุจูู | |
| # ุนุงู ุฉ ุจุงููุงู ู ูุชุณุฌูู ุฏุฎูู VIP ุงูุทุจูุนู). ููุถุจุท ูู: | |
| # Settings โ Variables and secrets โ INTERNAL_API_KEY | |
| # ููุฌุจ ุถุจุท ููุณ ุงูููู ุฉ ุจุงูุถุจุท ูู ู ุณุงุญุฉ "ุงูุฃุณุชุงุฐ ุนุจูุฏ" (teacher_app) ุฃูุถุงู. | |
| INTERNAL_API_KEY = os.environ.get("INTERNAL_API_KEY", "").strip() | |
| VOICES_DIR = "./platform_voices" | |
| AUDIO_FOLDER = "./audio" # ุงูู ุฌูุฏ ุงูุฎุงุต ุจุงูุฃุตูุงุช ุงูุนุฑุจูุฉ ุงูู ุฑุฌุนูุฉ ุงูุซุงุจุชุฉ (ููุงุณุชูุณุงุฎ) | |
| DATASET_REPO = "Asem75/aiocr_asistant" | |
| USER_REGISTRY_PATH = "system/user_registry.json" | |
| def ensure_dir(path: str): | |
| """ุชูุดุฆ ุงูู ุฌูุฏุ ูุฅุฐุง ูุงู ู ูุฌูุฏุงู ูู ูู ุนุงุฏู (ูููุณ ู ุฌูุฏุงู) ุชุญุฐูู ุฃููุงู | |
| ุซู ุชูุดุฆ ุงูู ุฌูุฏ ุจุฃู ุงู.""" | |
| if os.path.exists(path) and not os.path.isdir(path): | |
| os.remove(path) | |
| os.makedirs(path, exist_ok=True) | |
| ensure_dir(VOICES_DIR) | |
| ensure_dir(AUDIO_FOLDER) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 1) ุจูุงุจุฉ ุงูุญู ุงูุฉ ุงูุตุงุฑู ุฉ (VIP Only Gate) | |
| # | |
| # ุงูุดูู ุงูุญูููู ุงูู ุคูููุฏ ูู ูู "system/user_registry.json" (ู ู ุงูุฎุฒูุฉ): | |
| # { | |
| # "Divid": { | |
| # "email": "eduassistant75@gmail.com", | |
| # "device_token": "SECURE-FPR-23726480", | |
| # "rank": "free", | |
| # "registered_at": "2026-06-20 13:51:58" | |
| # }, | |
| # "ุงุณู _ู ุณุชุฎุฏู _ุขุฎุฑ": { ... } | |
| # } | |
| # ุงูู ูุชุงุญ ุงูุฎุงุฑุฌู ููู ุนูุตุฑ ูู ุงุณู ุงูู ุณุชุฎุฏู ููุณู (user_name) โ ูููุณ ุญููุงู | |
| # ุฏุงุฎููุงู. ุงูููู ุฉ ุงูุฏุงุฎููุฉ ูุง ุชุญุชูู ุนูู user_name ุฅุทูุงูุงู. | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def load_user_registry(): | |
| """ููุฑุฃ ุณุฌู ุงูู ุณุชุฎุฏู ูู ู ู ุงูุฎุฒูุฉ ููุงู ูุณ {user_name: {email, device_token, | |
| rank, registered_at}}. ุนูุฏ ุฃู ูุดู ููุฑุฌุน ูุงู ูุณุงู ูุงุฑุบุงู โ ูุดู ุขู ู | |
| (Fail-Closed): ูุง ุฃุญุฏ ูุฏุฎู ุจุฏู ุฃู ูุฏุฎู ุงูุฌู ูุน ุจุงูุฎุทุฃ.""" | |
| try: | |
| path = hf_hub_download( | |
| repo_id=DATASET_REPO, | |
| filename=USER_REGISTRY_PATH, | |
| repo_type="dataset", | |
| token=HF_TOKEN or None, | |
| force_download=True, # ูุถู ุงู ุณุญุจ ุขุฎุฑ ูุณุฎุฉ ู ู ุงูุณุฌู ู ุน ูู ู ุญุงููุฉ ุฏุฎูู | |
| ) | |
| with open(path, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| if isinstance(data, dict): | |
| return data | |
| return {} | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดู ุชุญู ูู ุณุฌู ุงูู ุณุชุฎุฏู ูู (ุฑูุถ ุขู ู ููู ุงูุฏุฎูู): {e}") | |
| return {} | |
| def authenticate_user(email: str, device_token: str): | |
| """ูุทุงุจู ุงูุจุฑูุฏ ุงูุฅููุชุฑููู ูุจุตู ุฉ ุงูุฌูุงุฒ ุนุจุฑ ูู ุนูุงุตุฑ ุงูุณุฌู. ููุฑุฌุน | |
| ูุงู ูุณุงู ูุญุชูู user_name (ุงูู ูุชุงุญ ุงูุฎุงุฑุฌู) + ุจุงูู ุจูุงูุงุช ุงูู ุณุชุฎุฏู ุ ููุท | |
| ุฅุฐุง ูุงูุช ุงูุฑุชุจุฉ 'vip' ุจุงูุถุจุท. ุฃู ุญุงูุฉ ุฃุฎุฑู (ุบูุฑ ู ุณุฌูุ ุฑุชุจุฉ 'free'ุ | |
| ุชุทุงุจู ุฌุฒุฆู) ุชูุฑุฌุน None โ ุญุธุฑ ูุงู ู.""" | |
| email_n = (email or "").strip().lower() | |
| token_n = (device_token or "").strip() | |
| if not email_n or not token_n: | |
| return None | |
| registry = load_user_registry() | |
| for user_name, info in registry.items(): | |
| if (info.get("email", "").strip().lower() == email_n | |
| and info.get("device_token", "").strip() == token_n): | |
| if info.get("rank") == "vip": | |
| return {"user_name": user_name, **info} | |
| return None # ู ูุฌูุฏ ููู ุฑุชุจุชู ููุณุช vip โ ุญุธุฑ | |
| return None # ุบูุฑ ู ุณุฌู ุฃุตูุงู โ ุญุธุฑ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 2) ุงูู ููุงุช ุงููุตูุฉ ุงูู ุฎุตุตุฉ ููู ุนู ูู + ุญูุธ ุงูุฃุตูุงุช ุจุงุณู ู | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def list_user_text_files(user_name: str): | |
| """ูุณุฑุฏ ููุท ุงูู ููุงุช ุงูู ูุฌูุฏุฉ ุฏุงุฎู Final_Arabic_Files/{user_name}/ ูู | |
| ุงูุฎุฒูุฉุ ุจุญูุซ ูุง ูุฑู ุฃู ุนู ูู ุฅูุง ู ููุงุชู ุงูุฎุงุตุฉ.""" | |
| if not user_name: | |
| return [] | |
| try: | |
| api = HfApi() | |
| all_files = api.list_repo_files(repo_id=DATASET_REPO, repo_type="dataset", token=HF_TOKEN or None) | |
| prefix = f"Final_Arabic_Files/{user_name}/" | |
| return [f for f in all_files if f.startswith(prefix) and not f.endswith("/")] | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดู ุณุฑุฏ ู ููุงุช ุงูู ุณุชุฎุฏู {user_name}: {e}") | |
| return [] | |
| def load_user_text_file(file_path: str) -> str: | |
| if not file_path: | |
| return "" | |
| try: | |
| local_path = hf_hub_download(repo_id=DATASET_REPO, filename=file_path, repo_type="dataset", token=HF_TOKEN or None) | |
| with open(local_path, "r", encoding="utf-8") as f: | |
| return f.read() | |
| except Exception as e: | |
| return f"โ ๏ธ ุชุนุฐุฑ ุชุญู ูู ุงูู ูู: {e}" | |
| def upload_audio_to_vault(local_path: str, user_name: str = None): | |
| """ูุฑูุน ุงูู ูู ุงูุตูุชู ููุฎุฒูุฉ. ุฅุฐุง ุชู ุชู ุฑูุฑ user_name ููุญูุธ ุชุญุช | |
| voices/{user_name}/{filename}ุ ูุฅูุง ููุญูุธ ูู voices/{filename} (ููุญุงูุงุช | |
| ุงูุนุงู ุฉ/ุงูุงุฎุชุจุงุฑูุฉ ุจุฏูู ู ุณุชุฎุฏู ู ุญุฏุฏ).""" | |
| if not HF_TOKEN: | |
| return " (โ ๏ธ ูุง ููุฌุฏ ุฑู ุฒ HF_TOKEN)" | |
| try: | |
| api = HfApi() | |
| filename = os.path.basename(local_path) | |
| path_in_repo = f"voices/{user_name}/{filename}" if user_name else f"voices/{filename}" | |
| api.upload_file( | |
| path_or_fileobj=local_path, | |
| path_in_repo=path_in_repo, | |
| repo_id=DATASET_REPO, | |
| repo_type="dataset", | |
| token=HF_TOKEN, | |
| ) | |
| return f" (๐พ ุชู ุงูุญูุธ ูู: {path_in_repo})" | |
| except Exception as e: | |
| return f" (โ ๏ธ ูุดู ุงูู ุฒุงู ูุฉ: {e})" | |
| # ุฌูุจ ูุงุฆู ุฉ ุงูู ููุงุช ุงูุตูุชูุฉ ุงูู ุฑุฌุนูุฉ ุงูุซุงุจุชุฉ (ูุงุณุชูุณุงุฎ XTTS) ู ู ู ุฌูุฏ audio ุงูู ุญูู | |
| def get_local_speaker_waves(): | |
| if not os.path.exists(AUDIO_FOLDER): | |
| return [] | |
| files = [f for f in os.listdir(AUDIO_FOLDER) if f.endswith(('.wav', '.mp3'))] | |
| return [(f.split('.')[0].replace('-', ' '), os.path.join(AUDIO_FOLDER, f)) for f in files] | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุญููุฉ Edge-TTS ุงูุฏุงุฆู ุฉ (ุฎูุท ูุงุญุฏ ุทููู ุงูุนู ุฑุ ุจุฏูู ุฅูุดุงุก/ุฅุบูุงู ู ุชูุฑุฑ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| _edge_loop = None | |
| _edge_thread = None | |
| def _start_edge_loop(): | |
| global _edge_loop, _edge_thread | |
| if _edge_loop is not None: | |
| return | |
| _edge_loop = asyncio.new_event_loop() | |
| def run_loop(): | |
| asyncio.set_event_loop(_edge_loop) | |
| _edge_loop.run_forever() | |
| _edge_thread = threading.Thread(target=run_loop, daemon=True) | |
| _edge_thread.start() | |
| def _stop_edge_loop(): | |
| global _edge_loop, _edge_thread | |
| if _edge_loop is not None: | |
| _edge_loop.call_soon_threadsafe(_edge_loop.stop) | |
| _edge_thread.join(timeout=5) | |
| _edge_loop.close() | |
| _edge_loop = None | |
| _edge_thread = None | |
| def _shutdown_handler(signum, frame): | |
| _stop_edge_loop() | |
| exit(0) | |
| signal.signal(signal.SIGINT, _shutdown_handler) | |
| signal.signal(signal.SIGTERM, _shutdown_handler) | |
| def get_available_voices(): | |
| _start_edge_loop() | |
| async def _fetch(): | |
| voices = await edge_tts.list_voices() | |
| arabic, english = [], [] | |
| for v in voices: | |
| short_name = v["ShortName"] | |
| if "Neural" not in short_name: | |
| continue | |
| label = f"{v['Locale']} - {short_name} ({v['Gender']})" | |
| if v["Locale"].startswith("ar"): | |
| arabic.append((label, short_name)) | |
| elif v["Locale"].startswith("en"): | |
| english.append((label, short_name)) | |
| arabic.sort(key=lambda x: x[0]) | |
| english.sort(key=lambda x: x[0]) | |
| return arabic, english | |
| future = asyncio.run_coroutine_threadsafe(_fetch(), _edge_loop) | |
| return future.result(timeout=30) | |
| print("โณ ุฌุงุฑู ุชุญู ูู ูุงุฆู ุฉ ุฃุตูุงุช EdgeโTTS...") | |
| try: | |
| ARABIC_EDGE_VOICES, ENGLISH_EDGE_VOICES = get_available_voices() | |
| except Exception: | |
| ARABIC_EDGE_VOICES = [("ุงูุนุฑุจูุฉ - ุตูุช ุงูุชุฑุงุถู", "ar-JO-TaimNeural")] | |
| ENGLISH_EDGE_VOICES = [("English - Default", "en-US-JennyNeural")] | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ู ุญุฑูุงุช ุงูุชูููุฏ ุงูุตูุชู (ูู ุฏุงูุฉ ุชุฃุฎุฐ ุฌู ูุฉ ูุงุญุฏุฉ ูุตูุฑุฉ ูุชูุฑุฌุน ู ุณุงุฑ ู ูู) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def generate_edge_audio(text: str, voice: str) -> str: | |
| filepath = os.path.join(VOICES_DIR, f"edge_{uuid.uuid4().hex}.mp3") | |
| async def _task(): | |
| communicate = edge_tts.Communicate(text.strip(), voice) | |
| await communicate.save(filepath) | |
| _start_edge_loop() | |
| future = asyncio.run_coroutine_threadsafe(_task(), _edge_loop) | |
| future.result(timeout=120) | |
| return filepath | |
| _piper_voice = None | |
| _piper_lock = threading.Lock() | |
| def load_piper_lazy(): | |
| global _piper_voice | |
| if _piper_voice is not None: | |
| return _piper_voice | |
| with _piper_lock: | |
| if _piper_voice is not None: | |
| return _piper_voice | |
| from piper import PiperVoice | |
| model_path = hf_hub_download(repo_id=DATASET_REPO, filename="models/ar_JO-kareem-low.onnx", repo_type="dataset", token=HF_TOKEN or None) | |
| config_path = hf_hub_download(repo_id=DATASET_REPO, filename="models/ar_JO-kareem-low.onnx.json", repo_type="dataset", token=HF_TOKEN or None) | |
| _piper_voice = PiperVoice.load(model_path, config_path) | |
| return _piper_voice | |
| def generate_piper_audio(text: str) -> str: | |
| voice = load_piper_lazy() | |
| filepath = os.path.join(VOICES_DIR, f"piper_{uuid.uuid4().hex}.wav") | |
| try: | |
| gc.collect() | |
| pcm_chunks = [chunk.audio_int16_bytes for chunk in voice.synthesize(text.strip())] | |
| wavfile.write(filepath, 22050, np.frombuffer(b"".join(pcm_chunks), dtype=np.int16)) | |
| return filepath | |
| except Exception as e: | |
| if os.path.exists(filepath): os.remove(filepath) | |
| raise RuntimeError(f"Piper Error: {e}") | |
| _xtts_model = None | |
| _xtts_lock = threading.Lock() | |
| def load_xtts_lazy(): | |
| global _xtts_model | |
| if _xtts_model is not None: | |
| return _xtts_model | |
| with _xtts_lock: | |
| if _xtts_model is not None: | |
| return _xtts_model | |
| print("โณ ุฌุงุฑู ุชุญู ูู ูู ูุฐุฌ ุงูุงุณุชูุณุงุฎ XTTS-v2...") | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| _xtts_model = TTS("tts_models/multilingual/multi-dataset/xtts_v2", gpu=torch.cuda.is_available()).to(device) | |
| print("โ ุชู ุชุญู ูู ู ุญุฑู XTTS-v2 ุจูุฌุงุญ.") | |
| return _xtts_model | |
| def generate_xtts_audio(text: str, speaker_audio_path: str) -> str: | |
| if not speaker_audio_path or not os.path.exists(speaker_audio_path): | |
| raise ValueError("ูุฌุจ ุชุญุฏูุฏ ุฃู ุฑูุน ู ูู ุตูุชู ู ุฑุฌุนู ููุงุณุชูุณุงุฎ.") | |
| tts = load_xtts_lazy() | |
| filepath = os.path.join(VOICES_DIR, f"xtts_{uuid.uuid4().hex}.wav") | |
| try: | |
| gc.collect() | |
| tts.tts_to_file(text=text.strip(), speaker_wav=speaker_audio_path, language="ar", file_path=filepath) | |
| return filepath | |
| except Exception as e: | |
| if os.path.exists(filepath): os.remove(filepath) | |
| raise RuntimeError(f"XTTS Error: {e}") | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 4) ุชูุทูุน ุงููุตูุต ุงูุทูููุฉ ูุฏู ุฌ ุงูู ูุงุทุน ุงูุตูุชูุฉ | |
| # | |
| # ุงูุชูุทูุน ุงูุฃุณุงุณู ุนูู ุนูุงู ุงุช ุฅููุงุก ุงูุฌู ูุฉ (. ! ุ). ุฅุฐุง ุจููุช ุฌู ูุฉ ูุงุญุฏุฉ | |
| # ุทูููุฉ ุฌุฏุงู ุจุนุฏ ุฐูู (ุฃุทูู ู ู MAX_CHUNK_CHARS)ุ ููุณูู ูุง ุฅุถุงููุงู ุนูู | |
| # ุงููุงุตูุฉ (ุ ,) ููุท ูุญู ุงูุฉ ุฅุถุงููุฉ โ ูุชุฌููุจ ุชูุชูุช ูู ุฌู ูุฉ ูุตูุฑุฉ ุนุงุฏูุฉ ุฅูู | |
| # ุฃุฌุฒุงุก ุตุบูุฑุฉ ุฌุฏุงู ุจูุง ุฏุงุนู. | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| MAX_CHUNK_CHARS = 300 | |
| _SENTENCE_END_RE = re.compile(r'(?<=[.!ุ])\s+') | |
| _SOFT_SPLIT_RE = re.compile(r'(?<=[ุ,])\s+') | |
| def split_text_into_chunks(text: str): | |
| text = (text or "").strip() | |
| if not text: | |
| return [] | |
| sentences = [s.strip() for s in _SENTENCE_END_RE.split(text) if s.strip()] | |
| if not sentences: | |
| sentences = [text] | |
| chunks = [] | |
| for s in sentences: | |
| if len(s) <= MAX_CHUNK_CHARS: | |
| chunks.append(s) | |
| continue | |
| # ุฌู ูุฉ ุทูููุฉ ุฌุฏุงู: ุชูุทูุน ุฅุถุงูู ุนูู ุงูููุงุตู ูุญู ุงูุฉ ุงูู ุญุฑู ุงูุตูุชู | |
| buf = "" | |
| for part in [p.strip() for p in _SOFT_SPLIT_RE.split(s) if p.strip()]: | |
| if len(buf) + len(part) + 1 <= MAX_CHUNK_CHARS: | |
| buf = f"{buf} {part}".strip() | |
| else: | |
| if buf: | |
| chunks.append(buf) | |
| buf = part | |
| if buf: | |
| chunks.append(buf) | |
| return chunks | |
| def synthesize_long_text(text: str, synth_fn, *synth_args) -> str: | |
| """ููุทูุน ุงููุตุ ููููุฏ ูู ุฌุฒุก ุนุจุฑ synth_fnุ ุซู ูุฏู ุฌ ูู ุงูู ูุงุทุน ูู ู ูู WAV | |
| ูุงุญุฏ ููุงุฆู ุนุจุฑ pydub. ุฅุฐุง ูุงู ุงููุต ูุตูุฑุงู (ุฌู ูุฉ ูุงุญุฏุฉ) ูุนู ู ู ุจุงุดุฑุฉ | |
| ุจุฏูู ุฃู ุชูุทูุน ูุชูููุฑ ุงูููุช.""" | |
| chunks = split_text_into_chunks(text) | |
| if not chunks: | |
| raise ValueError("ุงููุต ูุงุฑุบ ุจุนุฏ ุงูู ุนุงูุฌุฉ.") | |
| if len(chunks) == 1: | |
| return synth_fn(chunks[0], *synth_args) | |
| combined = AudioSegment.empty() | |
| temp_files = [] | |
| try: | |
| for chunk in chunks: | |
| part_path = synth_fn(chunk, *synth_args) | |
| temp_files.append(part_path) | |
| combined += AudioSegment.from_file(part_path) | |
| combined += AudioSegment.silent(duration=180) # ูุงุตู ุทุจูุนู ูุตูุฑ ุจูู ุงูุฌู ู | |
| final_path = os.path.join(VOICES_DIR, f"merged_{uuid.uuid4().hex}.wav") | |
| combined.export(final_path, format="wav") | |
| return final_path | |
| finally: | |
| for p in temp_files: | |
| if os.path.exists(p): | |
| try: | |
| os.remove(p) | |
| except Exception: | |
| pass | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุฏุงูุฉ ุงูุชูููุฏ ุงูุฑุฆูุณูุฉ (ูุงุฌูุฉ ุงูู ุณุชุฎุฏู ุงูุฏุงุฎููุฉุ ุจุนุฏ ุชุณุฌูู ุงูุฏุฎูู) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def synth_arabic(text, engine, edge_voice, xtts_mode, local_speaker, uploaded_speaker, user_name): | |
| if not text or not text.strip(): | |
| return None, "โ ๏ธ ุงูุฑุฌุงุก ูุชุงุจุฉ ูุต ุฃููุงู" | |
| try: | |
| if engine == "ู ุญุฑู Coqui XTTS-v2 (ุงุณุชูุณุงุฎ ุงูุฃุตูุงุช ุงูุนุฑุจูุฉ)": | |
| chosen_speaker = local_speaker if xtts_mode == "ุงุฎุชุฑ ู ู ุงูุฃุตูุงุช ุงูุนุฑุจูุฉ ุงูู ุฑููุนุฉ ูู ุงูู ุณุชูุฏุน" else uploaded_speaker | |
| path = synthesize_long_text(text, generate_xtts_audio, chosen_speaker) | |
| elif engine == "ู ุญุฑู Piper ุงูู ุญูู (ุจุตูุช ูุฑูู )": | |
| path = synthesize_long_text(text, generate_piper_audio) | |
| else: | |
| path = synthesize_long_text(text, generate_edge_audio, edge_voice) | |
| msg = upload_audio_to_vault(path, user_name) | |
| return path, f"โ ุชู ุงูุชูููุฏ ุจูุฌุงุญ!{msg}" | |
| except Exception as e: | |
| return None, f"โ {e}" | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # 3) ููุทุฉ API ุงูุฎุงุฑุฌูุฉ (Webhook) ูู ุณุงุญุฉ ุงูู ุญุงุฏุซุฉ ุงูุฃุฎุฑู | |
| # | |
| # ๐ ู ุญู ูุฉ ุงูุขู ุจู ูุชุงุญ ุณุฑู (api_key) ูููุงุฑูู ุจู ุชุบููุฑ ุงูุจูุฆุฉ | |
| # INTERNAL_API_KEY. ุงููุงุฌูุฉ ุงูุฑุณูู ูุฉ (ุชุณุฌูู ุฏุฎูู VIP) ุชุจูู ุนุงู ุฉ ุจุงููุงู ู | |
| # ููุง ุชุชุฃุซุฑ ุจูุฐุง โ ุงูุญู ุงูุฉ ููุง ุฎุงุตุฉ ุจููุทุฉ ุงูู API ููุท. | |
| # ูุดู ุขู ู (Fail-Closed): ุฅุฐุง ูู ููุถุจุท INTERNAL_API_KEY ูู ุฅุนุฏุงุฏุงุช ูุฐู | |
| # ุงูู ุณุงุญุฉุ ุชูุฑูุถ ูู ุงูุงุณุชุฏุนุงุกุงุช ุชููุงุฆูุงู ุจุฏู ุฃู ุชุจูู ู ูุชูุญุฉ ุจุงูุฎุทุฃ. | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def synth_arabic_api(text: str, speaker_name: str, api_key: str): | |
| if not INTERNAL_API_KEY or (api_key or "").strip() != INTERNAL_API_KEY: | |
| raise gr.Error("๐ซ ูุตูู ู ุฑููุถ: ู ูุชุงุญ API ุบูุฑ ุตุญูุญ ุฃู ุบูุฑ ู ููุนููู ูู ูุฐู ุงูู ุณุงุญุฉ.") | |
| if not text or not text.strip(): | |
| raise gr.Error("ุงููุต ุงูู ูุฑุณู ูุงุฑุบ.") | |
| speaker_name = (speaker_name or "").strip() | |
| if speaker_name.startswith("ar-"): | |
| return synthesize_long_text(text, generate_edge_audio, speaker_name) | |
| speaker_path = os.path.join(AUDIO_FOLDER, speaker_name) | |
| if not os.path.exists(speaker_path): | |
| raise gr.Error(f"ูู ูุชู ุงูุนุซูุฑ ุนูู ุงูุตูุช ุงูู ุฑุฌุนู: {speaker_name}") | |
| return synthesize_long_text(text, generate_xtts_audio, speaker_path) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ูุงุฌูุฉ Gradio | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| LOCAL_SPEAKERS = get_local_speaker_waves() | |
| with gr.Blocks(title="ู ูุตุฉ ุงูุฃุณุชุงุฐ ุนุจูุฏ") as demo: | |
| gr.Markdown("# ๐๏ธ ู ูุตุฉ ุงูุฃุณุชุงุฐ ุนุจูุฏ - ูุธุงู ุงูุชูููุฏ ุงูุตูุชู ุงูุงุญุชุฑุงูู") | |
| user_name_state = gr.State("") | |
| # ---------------- ุดุงุดุฉ ุงูุญุฑุงุณุฉ (VIP Login Gate) ---------------- | |
| with gr.Column(visible=True) as login_screen: | |
| gr.Markdown("### ๐ ุงูุฏุฎูู ู ูุตูุฑ ุนูู ุฃุนุถุงุก VIP ููุท") | |
| email_input = gr.Textbox(label="ุงูุจุฑูุฏ ุงูุฅููุชุฑููู") | |
| device_input = gr.Textbox(label="ุจุตู ุฉ ุงูุฌูุงุฒ (Device Token)") | |
| login_btn = gr.Button("๐ช ุฏุฎูู", variant="primary") | |
| # ---------------- ุงููุงุฌูุฉ ุงูุฑุฆูุณูุฉ (ู ุฎููุฉ ุญุชู ุชุณุฌูู ุงูุฏุฎูู) ---------------- | |
| with gr.Column(visible=False) as main_app: | |
| welcome_md = gr.Markdown("") | |
| with gr.Tabs(): | |
| with gr.Tab("ุงูุนุฑุจูุฉ"): | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| text_ar = gr.Textbox(label="โ๏ธ ุงููุต ุงูุนุฑุจู ูุชูููุฏู ุตูุชุงู", lines=6, rtl=True) | |
| with gr.Column(scale=1): | |
| engine_ar = gr.Radio( | |
| choices=["ู ุญุฑู ู ุงููุฑูุณููุช ุงูุณุญุงุจู (Edge-TTS)", "ู ุญุฑู Piper ุงูู ุญูู (ุจุตูุช ูุฑูู )", "ู ุญุฑู Coqui XTTS-v2 (ุงุณุชูุณุงุฎ ุงูุฃุตูุงุช ุงูุนุฑุจูุฉ)"], | |
| label="โ๏ธ ุงุฎุชุฑ ุงูู ุญุฑู ุงูุตูุชู", value="ู ุญุฑู ู ุงููุฑูุณููุช ุงูุณุญุงุจู (Edge-TTS)" | |
| ) | |
| voice_ar = gr.Dropdown(choices=ARABIC_EDGE_VOICES, label="๐๏ธ ุงูุตูุช ุงูุนุฑุจู (Edge)", value=ARABIC_EDGE_VOICES[0][1] if ARABIC_EDGE_VOICES else None) | |
| xtts_mode_ar = gr.Radio(choices=["ุงุฎุชุฑ ู ู ุงูุฃุตูุงุช ุงูุนุฑุจูุฉ ุงูู ุฑููุนุฉ ูู ุงูู ุณุชูุฏุน", "ุฑูุน ู ูู ุตูุชู ุฌุฏูุฏ (ู ุงููุฑูููู)"], label="๐๏ธ ู ุตุฏุฑ ุตูุช ุงูุงุณุชูุณุงุฎ", value="ุฑูุน ู ูู ุตูุชู ุฌุฏูุฏ (ู ุงููุฑูููู)", visible=False) | |
| local_speaker_ar = gr.Dropdown(choices=LOCAL_SPEAKERS, label="๐ค ุงูุฃุตูุงุช ุงูุนุฑุจูุฉ ุงูู ุชุงุญุฉ ูู ุงูุณูุฑูุฑ", visible=False) | |
| uploaded_speaker_ar = gr.Audio(label="๐๏ธ ุงุฑูุน ู ูู ุตูุชู ู ุฑุฌุนู (6 ุซูุงูู)", type="filepath", visible=False) | |
| btn_ar = gr.Button("๐ ุชูููุฏ ุงูุตูุช ุงูุนุฑุจู", variant="primary") | |
| audio_ar = gr.Audio(label="ุงูู ูู ุงูุตูุชู ุงููุงุชุฌ", type="filepath") | |
| status_ar = gr.Textbox(label="ุญุงูุฉ ุงูุชูููุฏ ูุงูุฑูุน ุณุญุงุจูุงู", interactive=False) | |
| def update_engine_fields(engine): | |
| is_edge = engine == "ู ุญุฑู ู ุงููุฑูุณููุช ุงูุณุญุงุจู (Edge-TTS)" | |
| is_xtts = engine == "ู ุญุฑู Coqui XTTS-v2 (ุงุณุชูุณุงุฎ ุงูุฃุตูุงุช ุงูุนุฑุจูุฉ)" | |
| return gr.update(visible=is_edge), gr.update(visible=is_xtts), gr.update(visible=False), gr.update(visible=False) | |
| def update_xtts_source(mode): | |
| is_local = mode == "ุงุฎุชุฑ ู ู ุงูุฃุตูุงุช ุงูุนุฑุจูุฉ ุงูู ุฑููุนุฉ ูู ุงูู ุณุชูุฏุน" | |
| return gr.update(visible=is_local), gr.update(visible=not is_local) | |
| engine_ar.change(fn=update_engine_fields, inputs=engine_ar, outputs=[voice_ar, xtts_mode_ar, local_speaker_ar, uploaded_speaker_ar], api_name=False) | |
| xtts_mode_ar.change(fn=update_xtts_source, inputs=xtts_mode_ar, outputs=[local_speaker_ar, uploaded_speaker_ar], api_name=False) | |
| btn_ar.click( | |
| fn=synth_arabic, | |
| inputs=[text_ar, engine_ar, voice_ar, xtts_mode_ar, local_speaker_ar, uploaded_speaker_ar, user_name_state], | |
| outputs=[audio_ar, status_ar], | |
| api_name=False, # โ ๏ธ ูุงู : ุจุฏูู ูุฐุงุ Gradio ููุตุฏูุฑูุง ุชููุงุฆูุงู ุจุงุณู ุฏุงูุชูุง "/synth_arabic" ููุชุนุงุฑุถ ู ุน ููุทุฉ ุงูู API ุงูู ูุตูุฏุฉ ุฃุฏูุงู | |
| ) | |
| with gr.Tab("English"): | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| text_en = gr.Textbox(label="โ๏ธ English Text", lines=6) | |
| with gr.Column(scale=1): | |
| voice_en = gr.Dropdown(choices=ENGLISH_EDGE_VOICES, label="๐๏ธ English Voice", value=ENGLISH_EDGE_VOICES[0][1] if ENGLISH_EDGE_VOICES else None) | |
| btn_en = gr.Button("๐ Generate English Audio", variant="primary") | |
| audio_en = gr.Audio(label="Output Audio", type="filepath") | |
| status_en = gr.Textbox(label="Status", interactive=False) | |
| btn_en.click( | |
| fn=lambda t, v: (synthesize_long_text(t, generate_edge_audio, v), "โ Done"), | |
| inputs=[text_en, voice_en], outputs=[audio_en, status_en], | |
| api_name=False, | |
| ) | |
| with gr.Tab("๐ ู ููุงุชู"): | |
| gr.Markdown("ุงูู ููุงุช ุงููุตูุฉ ุงูู ุฎุตุตุฉ ูู ููุท ู ู ุงูุฎุฒูุฉ (Final_Arabic_Files/ุงุณู ู/).") | |
| refresh_files_btn = gr.Button("๐ ุชุญุฏูุซ ูุงุฆู ุฉ ู ููุงุชู") | |
| my_files_dropdown = gr.Dropdown(label="ุงุฎุชุฑ ู ููุงู ูุตูุงู", choices=[]) | |
| load_file_btn = gr.Button("๐ฅ ุชุญู ูู ู ุญุชูู ุงูู ูู ุงูู ุฎุชุงุฑ") | |
| my_file_text = gr.Textbox(label="ู ุญุชูู ุงูู ูู", lines=8, rtl=True) | |
| use_in_generate_btn = gr.Button("โก๏ธ ุงุณุชุฎุฏู ูุฐุง ุงููุต ูู ุชุจููุจ ุงูุนุฑุจูุฉ") | |
| def refresh_my_files(user_name): | |
| return gr.update(choices=list_user_text_files(user_name)) | |
| refresh_files_btn.click(fn=refresh_my_files, inputs=[user_name_state], outputs=[my_files_dropdown], api_name=False) | |
| load_file_btn.click(fn=load_user_text_file, inputs=[my_files_dropdown], outputs=[my_file_text], api_name=False) | |
| use_in_generate_btn.click(fn=lambda txt: txt, inputs=[my_file_text], outputs=[text_ar], api_name=False) | |
| # ---------------- ุฑุจุท ุฒุฑ ุชุณุฌูู ุงูุฏุฎูู ---------------- | |
| def handle_login(email, device_token): | |
| user = authenticate_user(email, device_token) | |
| if user is None: | |
| raise gr.Error("๐ซ ุงููุตูู ู ุฑููุถ: ุงูุจุฑูุฏ ุงูุฅููุชุฑููู ุฃู ุจุตู ุฉ ุงูุฌูุงุฒ ุบูุฑ ุตุญูุญุฉุ ุฃู ุงูุญุณุงุจ ููุณ VIP.") | |
| resolved_name = user.get("user_name") or user.get("email", "guest") | |
| files = list_user_text_files(resolved_name) | |
| return ( | |
| gr.update(visible=False), # login_screen | |
| gr.update(visible=True), # main_app | |
| resolved_name, # user_name_state | |
| f"### ู ุฑุญุจุงู ุจูุ {resolved_name} ๐", # welcome_md | |
| gr.update(choices=files), # my_files_dropdown | |
| ) | |
| login_btn.click( | |
| fn=handle_login, | |
| inputs=[email_input, device_input], | |
| outputs=[login_screen, main_app, user_name_state, welcome_md, my_files_dropdown], | |
| api_name=False, | |
| ) | |
| # ---------------- ููุทุฉ API ุฎููุฉ ูู ุณุงุญุฉ ุงูู ุญุงุฏุซุฉ ุงูุฎุงุฑุฌูุฉ ---------------- | |
| # ู ููููุงุช ุบูุฑ ุธุงูุฑุฉ ูู ุงููุงุฌูุฉ ุงูุฑุณูู ูุฉุ ุชูุณุชุฎุฏูู ููุท ุนุจุฑ ุงุณุชุฏุนุงุก API. | |
| with gr.Row(visible=False): | |
| api_text_in = gr.Textbox() | |
| api_speaker_in = gr.Textbox() | |
| api_key_in = gr.Textbox() | |
| api_audio_out = gr.Audio(type="filepath") | |
| api_trigger_btn = gr.Button() | |
| api_trigger_btn.click( | |
| fn=synth_arabic_api, | |
| inputs=[api_text_in, api_speaker_in, api_key_in], | |
| outputs=api_audio_out, | |
| api_name="synth_arabic", | |
| ) | |
| if __name__ == "__main__": | |
| _start_edge_loop() | |
| demo.queue(max_size=1).launch(server_name="0.0.0.0") | |