File size: 5,754 Bytes
88c1a2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4a89a4
88c1a2c
 
 
 
 
 
 
 
 
 
 
 
 
 
24d9666
 
94fe5f0
 
88c1a2c
 
 
 
 
 
 
aa8a59a
 
 
88c1a2c
 
 
 
 
 
 
 
 
 
 
 
5967d49
9f6525b
94fe5f0
88c1a2c
 
 
 
 
 
 
 
aa8a59a
 
 
88c1a2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""
Kokoro TTS Engine - Simple ONNX implementation
Works with Python 3.9+ without complex dependencies
"""
import os
import logging
import numpy as np
from huggingface_hub import hf_hub_download

log = logging.getLogger("kokoro_engine")


class KokoroEngine:
    """
    محرك Kokoro TTS بسيط باستخدام kokoro-onnx من PyPI
    """
    
    def __init__(self, voice: str = "af_alloy", sample_rate: int = 24000):
        """
        تهيئة محرك Kokoro
        
        Args:
            voice: اسم الصوت الافتراضي
            sample_rate: معدل العينة (دائماً 24000 لـ Kokoro)
        """
        self.sample_rate = sample_rate
        self.voice = voice
        self.kokoro = None
        
        try:
            # استخدام kokoro-onnx من PyPI مباشرة
            from kokoro_onnx import Kokoro
            
            # تحميل ملفات النموذج
            model_path = self._download_model()
            voices_path = self._download_voices()
            
            # تهيئة Kokoro
            self.kokoro = Kokoro(model_path, voices_path)
            
            log.info(f"✅ Kokoro-ONNX initialized successfully")
            
        except Exception as e:
            log.error(f"❌ Failed to initialize Kokoro-ONNX: {e}")
            import traceback
            traceback.print_exc()
            raise
    
    def _download_model(self) -> str:
        """تحميل ملف النموذج"""
        try:
            # جرب أولاً من onnx-community (الأكثر موثوقية)
            model_path = hf_hub_download(
                repo_id="fastrtc/kokoro-onnx",
                filename="kokoro-v1.0.onnx",
                cache_dir="./models"
                )
            log.info(f"Model downloaded from onnx-community: {model_path}")
            return model_path
        except Exception as e1:
            log.warning(f"Failed to download from onnx-community: {e1}")
            try:
                # جرب من NeuML كبديل
                model_path = hf_hub_download(
                repo_id="onnx-community/Kokoro-82M-ONNX",
                filename="onnx/model.onnx",
                cache_dir="./models"
                )
                log.info(f"Model downloaded from NeuML: {model_path}")
                return model_path
            except Exception as e2:
                log.error(f"Failed to download model from all sources")
                raise RuntimeError(f"Could not download model: {e2}")
    
    def _download_voices(self) -> str:
        """تحميل ملف الأصوات"""
        try:
            # جرب أولاً voices.bin من onnx-community
            voices_path = hf_hub_download(
                repo_id="fastrtc/kokoro-onnx",
                filename="voices-v1.0.bin",
                cache_dir="./models"
            )
            log.info(f"Voices downloaded from onnx-community: {voices_path}")
            return voices_path
        except Exception as e1:
            log.warning(f"Failed to download voices.bin: {e1}")
            try:
                # جرب voices.json من NeuML كبديل
                voices_path = hf_hub_download(
                repo_id="onnx-community/Kokoro-82M-ONNX",
                filename="voices/voices.bin",
                cache_dir="./models"
                )
                log.info(f"Voices downloaded from NeuML: {voices_path}")
                return voices_path
            except Exception as e2:
                log.error(f"Failed to download voices from all sources")
                raise RuntimeError(f"Could not download voices: {e2}")
    
    def set_voice(self, voice: str):
        """
        تغيير الصوت
        
        Args:
            voice: اسم الصوت الجديد
        """
        self.voice = voice
        log.debug(f"Voice changed to: {voice}")
    
    def synthesize(self, text: str, speed: float = 1.0) -> np.ndarray:
        """
        تحويل النص إلى صوت
        
        Args:
            text: النص المراد تحويله
            speed: سرعة التحدث (1.0 = عادي)
            
        Returns:
            np.ndarray: البيانات الصوتية كـ numpy array (float32)
        """
        if self.kokoro is None:
            raise RuntimeError("Kokoro engine not initialized")
        
        try:
            # استخدام kokoro-onnx للتوليد
            samples, sample_rate = self.kokoro.create(
                text,
                voice=self.voice,
                speed=speed,
                lang='en-us'
            )
            
            log.info(f"✅ Audio generated: {len(samples)} samples, duration: {len(samples)/sample_rate:.2f}s")
            
            # التأكد من أن النوع float32
            return samples.astype(np.float32)
            
        except Exception as e:
            log.error(f"❌ Synthesis failed: {e}")
            import traceback
            traceback.print_exc()
            raise
    
    def get_available_voices(self):
        """
        الحصول على قائمة الأصوات المتاحة
        """
        return [
            # British Female
            "bf_alice", "bf_emma", "bf_isabella", "bf_lily",
            # American Female
            "af_alloy", "af_aoede", "af_bella", "af_heart",
            "af_jessica", "af_kore", "af_nicole", "af_nova",
            "af_river", "af_sarah", "af_sky",
            # British Male
            "bm_daniel", "bm_fable", "bm_george", "bm_lewis",
            # American Male
            "am_adam", "am_echo", "am_eric", "am_fenrir",
            "am_liam", "am_michael", "am_onyx", "am_puck"
        ]