Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -2,24 +2,19 @@ import gradio as gr
|
|
2 |
from pydub import AudioSegment
|
3 |
import requests
|
4 |
import os
|
5 |
-
import uuid
|
6 |
-
import re
|
7 |
-
import time # برای sleep در Polling
|
8 |
|
9 |
# مسیر ذخیره فایلهای موقت
|
10 |
TEMP_DIR = "temp_audio"
|
11 |
if not os.path.exists(TEMP_DIR):
|
12 |
os.makedirs(TEMP_DIR)
|
13 |
|
14 |
-
# تنظیمات Polling
|
15 |
-
POLLING_INTERVAL_SECONDS = 2
|
16 |
-
MAX_POLLING_ATTEMPTS = 30 # مثلاً 30 * 2 = 60 ثانیه صبر میکنیم
|
17 |
-
|
18 |
def download_file(url, output_path):
|
19 |
"""فایل را از یک URL دانلود میکند."""
|
20 |
try:
|
21 |
response = requests.get(url, stream=True)
|
22 |
-
response.raise_for_status()
|
23 |
with open(output_path, 'wb') as f:
|
24 |
for chunk in response.iter_content(chunk_size=8192):
|
25 |
f.write(chunk)
|
@@ -39,14 +34,17 @@ def get_audio_from_input(input_source):
|
|
39 |
unique_filename = os.path.join(TEMP_DIR, str(uuid.uuid4()))
|
40 |
|
41 |
if input_source.startswith("http://") or input_source.startswith("https://"):
|
|
|
|
|
42 |
file_extension = os.path.splitext(input_source.split('?')[0])[1]
|
43 |
-
if not file_extension:
|
44 |
file_extension = ".mp3"
|
45 |
temp_filepath = unique_filename + "_downloaded" + file_extension
|
46 |
if not download_file(input_source, temp_filepath):
|
47 |
return None, f"خطا در دانلود فایل از لینک: {input_source}"
|
48 |
audio_path = temp_filepath
|
49 |
else:
|
|
|
50 |
audio_path = input_source
|
51 |
|
52 |
try:
|
@@ -55,7 +53,10 @@ def get_audio_from_input(input_source):
|
|
55 |
except Exception as e:
|
56 |
return None, f"خطا در بارگذاری فایل صوتی ({audio_path}): {e}. مطمئن شوید فایل MP3 یا WAV معتبر است."
|
57 |
finally:
|
|
|
58 |
if 'temp_filepath' in locals() and os.path.exists(temp_filepath):
|
|
|
|
|
59 |
try:
|
60 |
os.remove(temp_filepath)
|
61 |
except OSError as e:
|
@@ -98,15 +99,14 @@ def tts_and_merge(text_input):
|
|
98 |
if not text_input.strip():
|
99 |
return None, "لطفاً متنی برای پردازش وارد کنید."
|
100 |
|
|
|
|
|
101 |
lines = text_input.strip().split('\n')
|
102 |
|
103 |
audio_urls_to_merge = []
|
104 |
errors = []
|
105 |
-
|
106 |
-
# برای Gradio که بتواند در حین Polling وضعیت را نشان دهد
|
107 |
-
yield None, "در حال شروع پردازش TTS..."
|
108 |
|
109 |
-
for
|
110 |
match = re.match(r'^\s*\((\d+)\)(.*)$', line)
|
111 |
if match:
|
112 |
speaker_number = match.group(1)
|
@@ -116,87 +116,47 @@ def tts_and_merge(text_input):
|
|
116 |
errors.append(f"خطا: متن خالی برای گوینده {speaker_number} در خط '{line}'")
|
117 |
continue
|
118 |
|
119 |
-
#
|
120 |
-
#
|
121 |
-
|
122 |
|
123 |
-
|
124 |
|
125 |
try:
|
126 |
-
#
|
127 |
-
response = requests.get(
|
128 |
-
response.raise_for_status()
|
129 |
-
|
130 |
-
# فرض: پاسخ یک JSON با event_id است
|
131 |
-
response_data = response.json()
|
132 |
-
event_id = response_data.get("event_id")
|
133 |
-
|
134 |
-
if not event_id:
|
135 |
-
errors.append(f"API برای گوینده {speaker_number} در خط {line_idx+1} یک event_id معتبر برنگرداند: {response.text}")
|
136 |
-
continue
|
137 |
-
|
138 |
-
print(f"درخواست TTS برای {speaker_number} (خط {line_idx+1}) ارسال شد، Event ID: {event_id}")
|
139 |
|
140 |
-
#
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
polling_attempts += 1
|
145 |
-
status_url = f"https://talkbot.ir/api/TTS-S{speaker_number}/status/{event_id}" # یا یک Endpoint کلی تر مثل /api/tts/status/{event_id}
|
146 |
-
|
147 |
-
yield None, f"خط {line_idx+1} (گوینده {speaker_number}): در حال بررسی وضعیت (تلاش {polling_attempts}/{MAX_POLLING_ATTEMPTS})..."
|
148 |
-
|
149 |
-
status_response = requests.get(status_url)
|
150 |
-
status_response.raise_for_status()
|
151 |
-
status_data = status_response.json()
|
152 |
-
|
153 |
-
status = status_data.get("status")
|
154 |
-
|
155 |
-
if status == "completed":
|
156 |
-
audio_link = status_data.get("audio_url")
|
157 |
-
if audio_link and audio_link.startswith("http"):
|
158 |
-
audio_urls_to_merge.append(audio_link)
|
159 |
-
print(f"صدا برای {speaker_number} (خط {line_idx+1}) آماده شد: {audio_link}")
|
160 |
-
break # از حلقه Polling خارج میشویم
|
161 |
-
else:
|
162 |
-
errors.append(f"لینک صوتی نامعتبر در پاسخ وضعیت برای {speaker_number} (خط {line_idx+1}): {status_data}")
|
163 |
-
break
|
164 |
-
elif status == "failed":
|
165 |
-
error_msg = status_data.get("error", "خطای ناشناخته از API.")
|
166 |
-
errors.append(f"تولید صدا برای گوینده {speaker_number} (خط {line_idx+1}) با خطا مواجه شد: {error_msg}")
|
167 |
-
break
|
168 |
-
elif status == "processing":
|
169 |
-
time.sleep(POLLING_INTERVAL_SECONDS)
|
170 |
-
else:
|
171 |
-
errors.append(f"وضعیت ناشناخته از API برای {speaker_number} (خط {line_idx+1}): {status_data}")
|
172 |
-
break
|
173 |
|
174 |
-
if
|
175 |
-
|
|
|
|
|
176 |
|
177 |
except requests.exceptions.RequestException as e:
|
178 |
-
errors.append(f"خطا در ارتباط با Talkbot API برای گوینده {speaker_number}
|
179 |
except Exception as e:
|
180 |
-
errors.append(f"خطای غیرمنتظره در پردازش Talkbot API برای گوینده {speaker_number}
|
181 |
else:
|
182 |
-
if line.strip():
|
183 |
errors.append(f"فرمت نامعتبر در خط: '{line}'. انتظار میرود (شماره)متن.")
|
184 |
|
|
|
185 |
if not audio_urls_to_merge:
|
186 |
-
|
187 |
-
if errors:
|
188 |
-
final_message += "\n\nخطاهای رخ داده:\n" + "\n".join(errors)
|
189 |
-
yield None, final_message
|
190 |
-
return
|
191 |
|
192 |
-
|
193 |
merged_output_path, merge_message = merge_audio_files(audio_urls_to_merge)
|
194 |
|
195 |
final_message = merge_message
|
196 |
if errors:
|
197 |
final_message += "\n\nخطاهای رخ داده:\n" + "\n".join(errors)
|
198 |
|
199 |
-
|
200 |
|
201 |
|
202 |
# ایجاد رابط کاربری Gradio
|
@@ -224,7 +184,7 @@ with gr.Blocks() as demo:
|
|
224 |
merge_button = gr.Button("ادغام فایلهای صوتی")
|
225 |
|
226 |
merge_button.click(
|
227 |
-
fn=lambda x: merge_audio_files([s.strip() for s in x.split('\n') if s.strip()]),
|
228 |
inputs=[audio_links_input],
|
229 |
outputs=[audio_merge_output_audio, audio_merge_output_message]
|
230 |
)
|
@@ -232,6 +192,8 @@ with gr.Blocks() as demo:
|
|
232 |
gr.Examples(
|
233 |
examples=[
|
234 |
["https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3\nhttps://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3"],
|
|
|
|
|
235 |
],
|
236 |
inputs=audio_links_input,
|
237 |
label="نمونهها"
|
@@ -265,6 +227,5 @@ with gr.Blocks() as demo:
|
|
265 |
|
266 |
|
267 |
if __name__ == "__main__":
|
268 |
-
demo.launch(
|
269 |
-
#
|
270 |
-
# demo.launch(share=True, enable_queue=True)
|
|
|
2 |
from pydub import AudioSegment
|
3 |
import requests
|
4 |
import os
|
5 |
+
import uuid # برای تولید نام فایل منحصر به فرد
|
6 |
+
import re # برای تجزیه متن
|
|
|
7 |
|
8 |
# مسیر ذخیره فایلهای موقت
|
9 |
TEMP_DIR = "temp_audio"
|
10 |
if not os.path.exists(TEMP_DIR):
|
11 |
os.makedirs(TEMP_DIR)
|
12 |
|
|
|
|
|
|
|
|
|
13 |
def download_file(url, output_path):
|
14 |
"""فایل را از یک URL دانلود میکند."""
|
15 |
try:
|
16 |
response = requests.get(url, stream=True)
|
17 |
+
response.raise_for_status() # خطایی رخ داد، آن را بالا ببر
|
18 |
with open(output_path, 'wb') as f:
|
19 |
for chunk in response.iter_content(chunk_size=8192):
|
20 |
f.write(chunk)
|
|
|
34 |
unique_filename = os.path.join(TEMP_DIR, str(uuid.uuid4()))
|
35 |
|
36 |
if input_source.startswith("http://") or input_source.startswith("https://"):
|
37 |
+
# این یک URL است، دانلودش کن
|
38 |
+
# سعی می کنیم پسوند فایل را از URL تشخیص دهیم، در غیر این صورت از .mp3 استفاده می کنیم.
|
39 |
file_extension = os.path.splitext(input_source.split('?')[0])[1]
|
40 |
+
if not file_extension: # اگر پسوندی در URL نباشد (مثلا برای APIها)
|
41 |
file_extension = ".mp3"
|
42 |
temp_filepath = unique_filename + "_downloaded" + file_extension
|
43 |
if not download_file(input_source, temp_filepath):
|
44 |
return None, f"خطا در دانلود فایل از لینک: {input_source}"
|
45 |
audio_path = temp_filepath
|
46 |
else:
|
47 |
+
# فرض میشود یک مسیر فایل محلی است
|
48 |
audio_path = input_source
|
49 |
|
50 |
try:
|
|
|
53 |
except Exception as e:
|
54 |
return None, f"خطا در بارگذاری فایل صوتی ({audio_path}): {e}. مطمئن شوید فایل MP3 یا WAV معتبر است."
|
55 |
finally:
|
56 |
+
# اگر فایل از URL دانلود شده بود، آن را پاک کن
|
57 |
if 'temp_filepath' in locals() and os.path.exists(temp_filepath):
|
58 |
+
# اگر فایل بعداً توسط pydub پردازش و لود شده باشد، میتوانیم آن را حذف کنیم.
|
59 |
+
# در غیر این صورت، ممکن است در حال استفاده باشد.
|
60 |
try:
|
61 |
os.remove(temp_filepath)
|
62 |
except OSError as e:
|
|
|
99 |
if not text_input.strip():
|
100 |
return None, "لطفاً متنی برای پردازش وارد کنید."
|
101 |
|
102 |
+
# الگو برای تشخیص شماره و متن پرانتزی: (شماره)متن
|
103 |
+
# این الگو همچنین newline را برای پردازش خط به خط در نظر میگیرد.
|
104 |
lines = text_input.strip().split('\n')
|
105 |
|
106 |
audio_urls_to_merge = []
|
107 |
errors = []
|
|
|
|
|
|
|
108 |
|
109 |
+
for line in lines:
|
110 |
match = re.match(r'^\s*\((\d+)\)(.*)$', line)
|
111 |
if match:
|
112 |
speaker_number = match.group(1)
|
|
|
116 |
errors.append(f"خطا: متن خالی برای گوینده {speaker_number} در خط '{line}'")
|
117 |
continue
|
118 |
|
119 |
+
# ساخت URL برای Talkbot API
|
120 |
+
# متن باید URL-encoded شود، اما requests.get این کار را به طور خودکار برای پارامترها انجام میدهد.
|
121 |
+
tts_url = f"https://talkbot.ir/api/TTS-S{speaker_number}?text={text_for_tts}"
|
122 |
|
123 |
+
print(f"درخواست TTS برای گوینده {speaker_number}: {tts_url}") # برای debugging
|
124 |
|
125 |
try:
|
126 |
+
# درخواست به Talkbot API
|
127 |
+
response = requests.get(tts_url)
|
128 |
+
response.raise_for_status() # برای خطاهای HTTP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
+
# فرض بر این است که پاسخ مستقیم لینک MP3 است
|
131 |
+
# اگر API لینک را در JSON برمیگرداند، باید آن را parse کنید.
|
132 |
+
# در مثال شما، فرض میشود پاسخ مستقیم لینک MP3 است.
|
133 |
+
audio_link = response.text.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
+
if audio_link.startswith("http"): # مطمئن شوید لینک معتبر است
|
136 |
+
audio_urls_to_merge.append(audio_link)
|
137 |
+
else:
|
138 |
+
errors.append(f"API برای گوینده {speaker_number} لینک معتبری برنگرداند: '{audio_link}'")
|
139 |
|
140 |
except requests.exceptions.RequestException as e:
|
141 |
+
errors.append(f"خطا در ارتباط با Talkbot API برای گوینده {speaker_number}: {e}")
|
142 |
except Exception as e:
|
143 |
+
errors.append(f"خطای غیرمنتظره در پردازش Talkbot API برای گوینده {speaker_number}: {e}")
|
144 |
else:
|
145 |
+
if line.strip(): # اگر خط خالی نباشد، اما مطابق الگو نباشد
|
146 |
errors.append(f"فرمت نامعتبر در خط: '{line}'. انتظار میرود (شماره)متن.")
|
147 |
|
148 |
+
|
149 |
if not audio_urls_to_merge:
|
150 |
+
return None, "هیچ فایل صوتی برای ادغام تولید نشد." + "\n".join(errors) if errors else ""
|
|
|
|
|
|
|
|
|
151 |
|
152 |
+
# حالا لینکهای تولید شده را به تابع merge_audio_files اصلی میفرستیم
|
153 |
merged_output_path, merge_message = merge_audio_files(audio_urls_to_merge)
|
154 |
|
155 |
final_message = merge_message
|
156 |
if errors:
|
157 |
final_message += "\n\nخطاهای رخ داده:\n" + "\n".join(errors)
|
158 |
|
159 |
+
return merged_output_path, final_message
|
160 |
|
161 |
|
162 |
# ایجاد رابط کاربری Gradio
|
|
|
184 |
merge_button = gr.Button("ادغام فایلهای صوتی")
|
185 |
|
186 |
merge_button.click(
|
187 |
+
fn=lambda x: merge_audio_files([s.strip() for s in x.split('\n') if s.strip()]), # تبدیل ورودی رشتهای به لیست برای تابع
|
188 |
inputs=[audio_links_input],
|
189 |
outputs=[audio_merge_output_audio, audio_merge_output_message]
|
190 |
)
|
|
|
192 |
gr.Examples(
|
193 |
examples=[
|
194 |
["https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3\nhttps://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3"],
|
195 |
+
# اگر فایلهای واقعی داشته باشید، میتوانید این خط را فعال کنید:
|
196 |
+
# ["./path/to/your/local_audio.mp3\n./path/to/another/local_audio.wav"]
|
197 |
],
|
198 |
inputs=audio_links_input,
|
199 |
label="نمونهها"
|
|
|
227 |
|
228 |
|
229 |
if __name__ == "__main__":
|
230 |
+
demo.launch() # برای اجرا در لوکال
|
231 |
+
# demo.launch(share=True) # برای اشتراکگذاری موقت در یک لینک عمومی (برای هوش مصنوعی)
|
|