Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -2,19 +2,24 @@ import gradio as gr
|
|
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,17 +39,14 @@ def get_audio_from_input(input_source):
|
|
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:
|
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,10 +55,7 @@ def get_audio_from_input(input_source):
|
|
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,14 +98,15 @@ def tts_and_merge(text_input):
|
|
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,47 +116,87 @@ 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 |
-
#
|
131 |
-
|
132 |
-
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
134 |
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
151 |
|
152 |
-
|
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 |
-
|
160 |
|
161 |
|
162 |
# ایجاد رابط کاربری Gradio
|
@@ -184,7 +224,7 @@ with gr.Blocks() as demo:
|
|
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,8 +232,6 @@ with gr.Blocks() as demo:
|
|
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,6 +265,6 @@ with gr.Blocks() as demo:
|
|
227 |
|
228 |
|
229 |
if __name__ == "__main__":
|
230 |
-
demo.launch(
|
231 |
-
#
|
232 |
-
|
|
|
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 |
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 |
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 |
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 line_idx, line in enumerate(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 |
+
# فرض: این Endpoint برای درخواست TTS و دریافت event_id است
|
120 |
+
# (ممکن است نیاز به تغییر به POST و body JSON داشته باشد)
|
121 |
+
tts_request_url = f"https://talkbot.ir/api/TTS-S{speaker_number}/request?text={requests.utils.quote(text_for_tts)}" # URL Encode text
|
122 |
|
123 |
+
yield None, f"در حال تولید صدا برای خط {line_idx+1} (گوینده {speaker_number})..."
|
124 |
|
125 |
try:
|
126 |
+
# مرحله 1: ارسال درخواست TTS و دریافت event_id
|
127 |
+
response = requests.get(tts_request_url) # یا requests.post(...)
|
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 |
+
# مرحله 2: Polling برای وضعیت
|
141 |
+
audio_link = None
|
142 |
+
polling_attempts = 0
|
143 |
+
while polling_attempts < MAX_POLLING_ATTEMPTS:
|
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 not audio_link: # اگر Polling به پایان رسید و لینکی دریافت نشد
|
175 |
+
errors.append(f"تولید صدا برای گوینده {speaker_number} (خط {line_idx+1}) در زمان تعیین شده به اتمام نرسید یا لینکی دریافت نشد.")
|
176 |
|
177 |
except requests.exceptions.RequestException as e:
|
178 |
+
errors.append(f"خطا در ارتباط با Talkbot API برای گوینده {speaker_number} (خط {line_idx+1}): {e}")
|
179 |
except Exception as e:
|
180 |
+
errors.append(f"خطای غیرمنتظره در پردازش Talkbot API برای گوینده {speaker_number} (خط {line_idx+1}): {e}")
|
181 |
else:
|
182 |
+
if line.strip():
|
183 |
errors.append(f"فرمت نامعتبر در خط: '{line}'. انتظار میرود (شماره)متن.")
|
184 |
|
|
|
185 |
if not audio_urls_to_merge:
|
186 |
+
final_message = "هیچ فایل صوتی برای ادغام تولید نشد."
|
187 |
+
if errors:
|
188 |
+
final_message += "\n\nخطاهای رخ داده:\n" + "\n".join(errors)
|
189 |
+
yield None, final_message
|
190 |
+
return
|
191 |
|
192 |
+
yield None, "در حال ادغام فایلهای صوتی..."
|
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 |
+
yield merged_output_path, final_message
|
200 |
|
201 |
|
202 |
# ایجاد رابط کاربری Gradio
|
|
|
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 |
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 |
|
266 |
|
267 |
if __name__ == "__main__":
|
268 |
+
demo.launch(debug=True, show_api=False, inline=False, share=True)
|
269 |
+
# برای اینکه Gradio بتواند خروجیهای میانی (yield) را مدیریت کند، نیاز به queue دارید.
|
270 |
+
# demo.launch(share=True, enable_queue=True)
|