Abdo96 commited on
Commit
d3963e7
·
verified ·
1 Parent(s): 61b83c3

Upload 55 files

Browse files
Files changed (4) hide show
  1. INTEGRATION_AR.md +75 -0
  2. app.py +147 -66
  3. requirements.txt +8 -0
  4. src/chatterbox/__init__.py +1 -4
INTEGRATION_AR.md ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ربط كل الخدمات على خادم واحد — جسر
2
+
3
+ الآن **خادم واحد** (Hugging Face Space) يقدّم الخدمات الثلاث، والسيرفر الوسيط
4
+ (`api-server.js`) يوجّه إليه فقط. عنوان واحد لكل شيء.
5
+
6
+ ## المعمارية بعد الربط
7
+
8
+ ```
9
+ [تطبيق Flutter]
10
+ │ يرسل: نص للترجمة / صوت للتفريغ / نص للنطق
11
+
12
+ [api-server.js] ← وسيط خفيف (Node، صفر تبعيات)
13
+ │ يحوّل طلبات REST إلى نداءات Gradio
14
+
15
+ [Hugging Face Space] ← خادم واحد على ZeroGPU
16
+ ├── /translate → MADLAD-400-3b-mt (Apache 2.0 تجاري، 400+ لغة)
17
+ ├── /stt → Whisper small
18
+ └── /tts → Chatterbox (استنساخ صوتي، 7 لغات)
19
+ ```
20
+
21
+ ## ما الذي تغيّر
22
+
23
+ **في الـ Space** (`app.py` + `requirements.txt`):
24
+ - أضيف endpoint `translate` يستخدم MADLAD-400 (تجاري، بديل NLLB غير التجاري)
25
+ - أضيف endpoint `stt` يستخدم Whisper
26
+ - الواجهة الآن ثلاثة تبويبات: توليد كلام / ترجمة / تفريغ
27
+
28
+ **في السيرفر الوسيط** (`api-server.js`):
29
+ - حُذفت `TRANSLATE_URL` و `STT_URL` — لم تعد هناك خدمات منفصلة
30
+ - كل الطلبات تذهب لنفس `INFERENCE_BASE_URL` عبر مُنادٍ Gradio عام
31
+ - التفريغ يرفع الملف لـ Gradio ثم ينادي `stt`
32
+
33
+ **في التطبيق** (`api_service.dart`):
34
+ - التفريغ الآن يرسل بايتات الصوت الخام مع ترويسة `x-lang`
35
+
36
+ ## خطوات التشغيل
37
+
38
+ ### 1) الـ Space
39
+ انسخ (Duplicate) الـ Space الرسمي ResembleAI/Chatterbox-Multilingual-TTS-V3،
40
+ ثم استبدل `app.py` و `requirements.txt` بالنسختين المرفقتين، وارفع `default_voice.wav`.
41
+ اختر عتاد **ZeroGPU**.
42
+
43
+ > تحميل أول مرة: MADLAD-3B (~12 جيجا) + Whisper + Chatterbox. أول طلب لكل
44
+ > خدمة يستغرق وقتاً للتحميل، ثم يصبح سريعاً. لو أردت MADLAD أصغر/أسرع اضبط
45
+ > متغير البيئة `MADLAD_MODEL=google/madlad400-3b-mt` (الافتراضي) — أو للجودة
46
+ > الأعلى `google/madlad400-7b-mt`.
47
+
48
+ ### 2) السيرفر الوسيط
49
+ ```bash
50
+ INFERENCE_BASE_URL=https://YOUR-USERNAME-SPACENAME.hf.space node api-server.js
51
+ ```
52
+ متغير واحد فقط! (اختياري: `HF_TOKEN` لو الـ Space خاص، `PORT`).
53
+
54
+ ### 3) التطبيق
55
+ ```bash
56
+ flutter run --dart-define=API_BASE_URL=https://your-bridge-server.com
57
+ ```
58
+
59
+ ## اختبار سريع للخادم الموحّد
60
+
61
+ ```bash
62
+ BASE=https://YOUR-USERNAME-SPACENAME.hf.space
63
+
64
+ # ترجمة
65
+ curl -X POST $BASE/gradio_api/call/translate \
66
+ -H "Content-Type: application/json" \
67
+ -d '{"data":["مرحبا بالعالم","العربية (ar)","English (en)"]}'
68
+ # ثم curl $BASE/gradio_api/call/translate/EVENT_ID
69
+ ```
70
+
71
+ ## ملاحظة الترخيص
72
+ - **MADLAD-400**: Apache 2.0 ✅ تجاري مسموح (بديل NLLB الذي كان CC-BY-NC)
73
+ - **Whisper**: MIT ✅
74
+ - **Chatterbox**: MIT ✅
75
+ كلها آمنة لتطبيق مدفوع.
app.py CHANGED
@@ -1,21 +1,25 @@
1
- # app.py — Chatterbox Multilingual على Hugging Face Spaces (ZeroGPU)
2
- # واجهة Gradio للتجربة + API endpoint باسم /tts لتطبيق الأندرويد
 
 
 
3
  #
4
- # ⚠️ هذا الملف مصمم ليعمل داخل نسخة (Duplicate) من الـ Space الرسمي:
5
- # ResembleAI/Chatterbox-Multilingual-TTS-V3
6
- # حيث كود النموذج موجود في مجلد src/ داخل الـ Space (وليس من pip).
7
 
8
  import os
9
  import sys
10
 
11
- # استيراد كود النموذج من مجلد src/ (طريقة الـ Space الرسمي)
12
  sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src"))
13
 
14
  import spaces # مطلوب لـ ZeroGPU
15
  import torch
16
- import soundfile as sf
17
  import gradio as gr
18
 
 
 
 
19
  LANGS = {
20
  "العربية (ar)": "ar",
21
  "Türkçe (tr)": "tr",
@@ -25,84 +29,161 @@ LANGS = {
25
  "Deutsch (de)": "de",
26
  "Français (fr)": "fr",
27
  }
28
-
29
  DEFAULT_VOICE = "default_voice.wav"
30
 
31
- _model = None # يُحمَّل مرة واحدة عند أول طلب
32
-
 
 
33
 
34
- def get_model():
35
- global _model
36
- if _model is None:
37
  from chatterbox.mtl_tts import ChatterboxMultilingualTTS
38
- _model = ChatterboxMultilingualTTS.from_pretrained(device="cuda")
39
- return _model
40
-
41
-
42
- @spaces.GPU(duration=60) # يحجز GPU من ZeroGPU لمدة التوليد فقط
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def tts(text: str, language: str, exaggeration: float = 0.5,
44
  cfg_weight: float = 0.5, voice_ref=None):
45
- """يولّد الكلام ويرجع مسار ملف WAV."""
46
  text = (text or "").strip()
47
  if not text:
48
  raise gr.Error("أدخل نصاً أولاً")
49
  if len(text) > 1000:
50
- raise gr.Error("الحد الأقصى 1000 حرف لكل طلب")
51
-
52
- lang = LANGS.get(language, language) # يقبل "ar" مباشرة أو الاسم المعروض
53
- if lang not in LANGS.values():
54
  raise gr.Error(f"لغة غير مدعومة: {language}")
55
-
56
  ref = voice_ref if voice_ref else DEFAULT_VOICE
57
  if not os.path.exists(ref):
58
  raise gr.Error("ملف الصوت المرجعي default_voice.wav غير موجود — ارفعه للـ Space")
59
-
60
- model = get_model()
61
  wav = model.generate(
62
- text,
63
- language_id=lang,
64
- audio_prompt_path=ref,
65
- exaggeration=float(exaggeration),
66
- cfg_weight=float(cfg_weight),
67
  )
68
  out_path = "/tmp/speech.wav"
69
- sf.write(out_path, wav.squeeze(0).detach().cpu().numpy(), model.sr)
70
  return out_path
71
 
72
 
73
- with gr.Blocks(title="Multilingual TTS") as demo:
74
- gr.Markdown("# 🎙️ توليد كلام بـ 7 لغات — Chatterbox Multilingual")
75
-
76
- with gr.Row():
77
- with gr.Column():
78
- text_in = gr.Textbox(label="النص", lines=4,
79
- placeholder="اكتب النص هنا...")
80
- lang_in = gr.Dropdown(choices=list(LANGS.keys()),
81
- value="العربية (ar)", label="اللغة")
82
- with gr.Accordion("إعدادات متقدمة", open=False):
83
- exag = gr.Slider(0.25, 1.0, value=0.5, step=0.05,
84
- label="قوة التعبير (exaggeration)")
85
- cfg = gr.Slider(0.2, 0.8, value=0.5, step=0.05,
86
- label="التطابق مع الصوت المرجعي (cfg_weight)")
87
- voice_in = gr.Audio(label="صوت مرجعي مخصص (اختياري)",
88
- type="filepath")
89
- btn = gr.Button("🔊 توليد", variant="primary")
90
- with gr.Column():
91
- audio_out = gr.Audio(label="الناتج", type="filepath",
92
- autoplay=True)
93
-
94
- # api_name="tts" ⇒ endpoint ثابت للأندرويد: /gradio_api/call/tts
95
- btn.click(tts, inputs=[text_in, lang_in, exag, cfg, voice_in],
96
- outputs=audio_out, api_name="tts")
97
-
98
- gr.Examples(
99
- examples=[
100
- ["مرحباً بكم في تطبيقنا الجديد", "العربية (ar)"],
101
- ["Uygulamamıza hoş geldiniz", "Türkçe (tr)"],
102
- ["Welcome to our new application", "English (en)"],
103
- ["हमारे नए ऐप में आपका स्वागत है", "हिन्दी (hi)"],
104
- ],
105
- inputs=[text_in, lang_in],
106
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  demo.queue(max_size=20).launch(ssr_mode=False)
 
1
+ # app.py — جسر: خادم موحّد على Hugging Face Space (ZeroGPU)
2
+ # يجمع ثلاث خدمات في مكان واحد:
3
+ # /tts → توليد كلام بصوت مستنسخ (Chatterbox Multilingual)
4
+ # /translate → ترجمة نصية (MADLAD-400، رخصة Apache تجارية)
5
+ # /stt → تفريغ صوتي (Whisper)
6
  #
7
+ # ⚠️ يعمل داخل نسخة (Duplicate) من الـ Space الرسمي:
8
+ # ResembleAI/Chatterbox-Multilingual-TTS-V3 (كود Chatterbox في src/)
 
9
 
10
  import os
11
  import sys
12
 
 
13
  sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src"))
14
 
15
  import spaces # مطلوب لـ ZeroGPU
16
  import torch
17
+ import torchaudio
18
  import gradio as gr
19
 
20
+ # ============================================================
21
+ # اللغات المدعومة
22
+ # ============================================================
23
  LANGS = {
24
  "العربية (ar)": "ar",
25
  "Türkçe (tr)": "tr",
 
29
  "Deutsch (de)": "de",
30
  "Français (fr)": "fr",
31
  }
32
+ LANG_CODES = set(LANGS.values())
33
  DEFAULT_VOICE = "default_voice.wav"
34
 
35
+ # ============================================================
36
+ # 1) نموذج الكلام — Chatterbox (يُحمّل مرة واحدة)
37
+ # ============================================================
38
+ _tts_model = None
39
 
40
+ def get_tts_model():
41
+ global _tts_model
42
+ if _tts_model is None:
43
  from chatterbox.mtl_tts import ChatterboxMultilingualTTS
44
+ _tts_model = ChatterboxMultilingualTTS.from_pretrained(device="cuda")
45
+ return _tts_model
46
+
47
+ # ============================================================
48
+ # 2) نموذج الترجمة — MADLAD-400 (Apache 2.0، تجاري، 400+ لغة)
49
+ # ============================================================
50
+ _mt_model = None
51
+ _mt_tok = None
52
+ MADLAD_ID = os.environ.get("MADLAD_MODEL", "google/madlad400-3b-mt")
53
+
54
+ def get_mt():
55
+ global _mt_model, _mt_tok
56
+ if _mt_model is None:
57
+ from transformers import T5ForConditionalGeneration, T5Tokenizer
58
+ _mt_tok = T5Tokenizer.from_pretrained(MADLAD_ID)
59
+ _mt_model = T5ForConditionalGeneration.from_pretrained(
60
+ MADLAD_ID, torch_dtype=torch.float16, device_map="cuda"
61
+ )
62
+ return _mt_model, _mt_tok
63
+
64
+ # ============================================================
65
+ # 3) نموذج التفريغ — Whisper
66
+ # ============================================================
67
+ _asr = None
68
+
69
+ def get_asr():
70
+ global _asr
71
+ if _asr is None:
72
+ import whisper
73
+ _asr = whisper.load_model("small", device="cuda")
74
+ return _asr
75
+
76
+ # ============================================================
77
+ # الدوال المعروضة كـ API
78
+ # ============================================================
79
+ @spaces.GPU(duration=60)
80
  def tts(text: str, language: str, exaggeration: float = 0.5,
81
  cfg_weight: float = 0.5, voice_ref=None):
82
+ """توليد كلام بصوت مستنسخ. يعيد مسار WAV."""
83
  text = (text or "").strip()
84
  if not text:
85
  raise gr.Error("أدخل نصاً أولاً")
86
  if len(text) > 1000:
87
+ raise gr.Error("الحد الأقصى 1000 حرف")
88
+ lang = LANGS.get(language, language)
89
+ if lang not in LANG_CODES:
 
90
  raise gr.Error(f"لغة غير مدعومة: {language}")
 
91
  ref = voice_ref if voice_ref else DEFAULT_VOICE
92
  if not os.path.exists(ref):
93
  raise gr.Error("ملف الصوت المرجعي default_voice.wav غير موجود — ارفعه للـ Space")
94
+ model = get_tts_model()
 
95
  wav = model.generate(
96
+ text, language_id=lang, audio_prompt_path=ref,
97
+ exaggeration=float(exaggeration), cfg_weight=float(cfg_weight),
 
 
 
98
  )
99
  out_path = "/tmp/speech.wav"
100
+ torchaudio.save(out_path, wav, model.sr)
101
  return out_path
102
 
103
 
104
+ @spaces.GPU(duration=30)
105
+ def translate(text: str, source: str, target: str):
106
+ """ترجمة نص عبر MADLAD-400. يعيد النص المترجم."""
107
+ text = (text or "").strip()
108
+ if not text:
109
+ return ""
110
+ tgt = LANGS.get(target, target)
111
+ src = LANGS.get(source, source)
112
+ if tgt not in LANG_CODES:
113
+ raise gr.Error(f"لغة هدف غير مدعومة: {target}")
114
+ if src == tgt:
115
+ return text
116
+ model, tok = get_mt()
117
+ prompt = f"<2{tgt}> {text}" # بادئة MADLAD تحدد لغة الهدف
118
+ inputs = tok(prompt, return_tensors="pt", truncation=True,
119
+ max_length=512).to("cuda")
120
+ with torch.no_grad():
121
+ out = model.generate(**inputs, max_new_tokens=512, num_beams=1)
122
+ return tok.decode(out[0], skip_special_tokens=True).strip()
123
+
124
+
125
+ @spaces.GPU(duration=30)
126
+ def stt(audio_path: str, language: str = ""):
127
+ """تفريغ صوتي عبر Whisper. يعيد النص."""
128
+ if not audio_path or not os.path.exists(audio_path):
129
+ return ""
130
+ lang = LANGS.get(language, language) if language else None
131
+ asr = get_asr()
132
+ kwargs = {}
133
+ if lang and lang in LANG_CODES:
134
+ kwargs["language"] = lang
135
+ result = asr.transcribe(audio_path, **kwargs)
136
+ return (result.get("text") or "").strip()
137
+
138
+ # ============================================================
139
+ # واجهة Gradio + نقاط API
140
+ # ============================================================
141
+ with gr.Blocks(title="جسر — خادم موحّد") as demo:
142
+ gr.Markdown("# 🌉 جسر — ترجمة + استنساخ صوتي (7 لغات)")
143
+
144
+ with gr.Tab("🔊 توليد كلام"):
145
+ with gr.Row():
146
+ with gr.Column():
147
+ text_in = gr.Textbox(label="النص", lines=4,
148
+ placeholder="اكتب النص هنا...")
149
+ lang_in = gr.Dropdown(choices=list(LANGS.keys()),
150
+ value="العربية (ar)", label="اللغة")
151
+ with gr.Accordion("إعدادات متقدمة", open=False):
152
+ exag = gr.Slider(0.25, 1.0, value=0.5, step=0.05,
153
+ label="قوة التعبير")
154
+ cfg = gr.Slider(0.2, 0.8, value=0.5, step=0.05,
155
+ label="التطابق مع الصوت المرجعي")
156
+ voice_in = gr.Audio(label="صوت مرجعي مخصص (اختياري)",
157
+ type="filepath")
158
+ btn = gr.Button("🔊 توليد", variant="primary")
159
+ with gr.Column():
160
+ audio_out = gr.Audio(label="الناتج", type="filepath",
161
+ autoplay=True)
162
+ btn.click(tts, inputs=[text_in, lang_in, exag, cfg, voice_in],
163
+ outputs=audio_out, api_name="tts")
164
+
165
+ with gr.Tab("🌍 ترجمة"):
166
+ with gr.Row():
167
+ with gr.Column():
168
+ mt_text = gr.Textbox(label="النص", lines=3)
169
+ mt_src = gr.Dropdown(choices=list(LANGS.keys()),
170
+ value="العربية (ar)", label="من")
171
+ mt_tgt = gr.Dropdown(choices=list(LANGS.keys()),
172
+ value="English (en)", label="إلى")
173
+ mt_btn = gr.Button("ترجم", variant="primary")
174
+ mt_out = gr.Textbox(label="الترجمة", lines=3)
175
+ mt_btn.click(translate, inputs=[mt_text, mt_src, mt_tgt],
176
+ outputs=mt_out, api_name="translate")
177
+
178
+ with gr.Tab("🎤 تفريغ صوتي"):
179
+ with gr.Row():
180
+ with gr.Column():
181
+ stt_audio = gr.Audio(label="ملف صوتي", type="filepath")
182
+ stt_lang = gr.Dropdown(choices=[""] + list(LANGS.keys()),
183
+ value="", label="اللغة (اختياري)")
184
+ stt_btn = gr.Button("فرّغ", variant="primary")
185
+ stt_out = gr.Textbox(label="النص", lines=3)
186
+ stt_btn.click(stt, inputs=[stt_audio, stt_lang],
187
+ outputs=stt_out, api_name="stt")
188
 
189
  demo.queue(max_size=20).launch(ssr_mode=False)
requirements.txt CHANGED
@@ -1,3 +1,4 @@
 
1
  numpy==1.26.4
2
  librosa==0.11.0
3
  einops
@@ -9,3 +10,10 @@ transformers==4.46.3
9
  diffusers==0.29.0
10
  resemble-perth==1.0.1
11
  safetensors
 
 
 
 
 
 
 
 
1
+ # --- Chatterbox (توليد الصوت) ---
2
  numpy==1.26.4
3
  librosa==0.11.0
4
  einops
 
10
  diffusers==0.29.0
11
  resemble-perth==1.0.1
12
  safetensors
13
+
14
+ # --- MADLAD-400 (الترجمة) ---
15
+ sentencepiece
16
+ accelerate
17
+
18
+ # --- Whisper (التفريغ الصوتي) ---
19
+ openai-whisper
src/chatterbox/__init__.py CHANGED
@@ -3,10 +3,7 @@ try:
3
  except ImportError:
4
  from importlib_metadata import version # For Python <3.8
5
 
6
- try:
7
- __version__ = version("chatterbox-tts")
8
- except Exception:
9
- __version__ = "0.0.0-vendored" # يعمل من مجلد src/ بدون تثبيت pip
10
 
11
 
12
  from .tts import ChatterboxTTS
 
3
  except ImportError:
4
  from importlib_metadata import version # For Python <3.8
5
 
6
+ __version__ = version("chatterbox-tts")
 
 
 
7
 
8
 
9
  from .tts import ChatterboxTTS