suprimedev commited on
Commit
f969cbd
·
verified ·
1 Parent(s): 6b5cb2f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +302 -0
app.py CHANGED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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)
21
+ return True
22
+ except requests.exceptions.RequestException as e:
23
+ print(f"Error downloading {url}: {e}")
24
+ return False
25
+ except Exception as e:
26
+ print(f"An unexpected error occurred during download of {url}: {e}")
27
+ return False
28
+
29
+ def get_audio_from_input(input_source):
30
+ """منبع ورودی را پردازش کرده و یک شی AudioSegment برمی‌گرداند."""
31
+ unique_filename = os.path.join(TEMP_DIR, str(uuid.uuid4()))
32
+
33
+ # Try to determine if it's a URL or a local path.
34
+ # For Gradio's file input, it provides a local path.
35
+ # For direct text input (like in the tts part), it's a URL.
36
+ is_url = input_source.startswith("http://") or input_source.startswith("https://")
37
+
38
+ audio_path = None # Initialize audio_path to handle finally block
39
+ downloaded_temp_filepath = None
40
+
41
+ if is_url:
42
+ file_extension = os.path.splitext(input_source.split('?')[0])[1]
43
+ if not file_extension:
44
+ file_extension = ".mp3" # Default if no extension found in URL
45
+ downloaded_temp_filepath = unique_filename + "_downloaded" + file_extension
46
+ if not download_file(input_source, downloaded_temp_filepath):
47
+ return None, f"خطا در دانلود فایل از لینک: {input_source}"
48
+ audio_path = downloaded_temp_filepath
49
+ else: # Assume it's a local file path provided directly or by Gradio
50
+ audio_path = input_source
51
+
52
+ try:
53
+ if not os.path.exists(audio_path):
54
+ return None, f"فایل پیدا نشد: {audio_path}"
55
+ audio = AudioSegment.from_file(audio_path)
56
+ return audio, None
57
+ except Exception as e:
58
+ return None, f"خطا در بارگذاری فایل صوتی ({audio_path}): {e}. مطمئن شوید فایل MP3 یا WAV معتبر است."
59
+ finally:
60
+ # Clean up downloaded temporary file if it was created
61
+ if downloaded_temp_filepath and os.path.exists(downloaded_temp_filepath):
62
+ try:
63
+ os.remove(downloaded_temp_filepath)
64
+ except OSError as e:
65
+ print(f"Error removing temporary file {downloaded_temp_filepath}: {e}")
66
+
67
+ def merge_audio_files(input_sources):
68
+ """چندین فایل صوتی را ادغام می‌کند و یک فایل MP3 خروجی می‌دهد."""
69
+ if not input_sources:
70
+ return None, "لیست ورودی‌های صوتی خالی است."
71
+
72
+ combined_audio = AudioSegment.empty()
73
+ errors = []
74
+
75
+ for source in input_sources:
76
+ # get_audio_from_input can now handle direct file paths from Gradio or URLs
77
+ audio_segment, error = get_audio_from_input(source)
78
+ if audio_segment:
79
+ combined_audio += audio_segment
80
+ else:
81
+ errors.append(error)
82
+ print(f"Skipping {source} due to error: {error}")
83
+
84
+ if not combined_audio.duration_seconds > 0:
85
+ return None, "هیچ فایل صوتی معتبری برای ادغام پیدا نشد. " + "\n".join(errors) if errors else ""
86
+
87
+ output_filename = os.path.join(TEMP_DIR, f"merged_audio_{uuid.uuid4()}.mp3")
88
+ try:
89
+ combined_audio.export(output_filename, format="mp3")
90
+ return output_filename, "عملیات موفقیت آمیز بود!"
91
+ except Exception as e:
92
+ return None, f"خطا در ذخیره فایل خروجی: {e}"
93
+
94
+ def add_intro_outro_and_background(podcast_audio, intro_audio_url, background_audio_url, outro_audio_url):
95
+ """افکت‌های صوتی را به پادکست اضافه می‌کند."""
96
+ intro_audio, intro_error = get_audio_from_input(intro_audio_url)
97
+ if intro_audio is None:
98
+ return None, f"خطا در دانلود یا بارگذاری صدای ابتدایی: {intro_error}"
99
+
100
+ background_audio, background_error = get_audio_from_input(background_audio_url)
101
+ if background_audio is None:
102
+ return None, f"خطا در دانلود یا بارگذاری موزیک پس‌زمینه: {background_error}"
103
+
104
+ outro_audio, outro_error = get_audio_from_input(outro_audio_url)
105
+ if outro_audio is None:
106
+ return None, f"خطا در دانلود یا بارگذاری صدای انتهایی: {outro_error}"
107
+
108
+ # طول کل صدای نهایی برای تکرار موزیک پس‌زمینه
109
+ total_duration_estimate = len(intro_audio) + len(podcast_audio) + len(outro_audio)
110
+
111
+ # مطمئن شوید موزیک پس‌زمینه به اندازه کافی طولانی است یا تکرار شود
112
+ if len(background_audio) < total_duration_estimate:
113
+ background_audio = background_audio * (int(total_duration_estimate / len(background_audio)) + 1)
114
+
115
+ # Trim background audio to total duration
116
+ background_audio = background_audio[:total_duration_estimate]
117
+
118
+ # تنظیم شدت صدای پس‌زمینه (gain reduction)
119
+ # این مقدار را می‌توانید تغییر دهید. -18dB معمولاً خوب است، اما بسته به نوع موسیقی متغیر است.
120
+ # هرچه عدد منفی‌تر باشد، صدا کمتر می‌شود.
121
+ background_audio = background_audio - 18 # کاهش مثلاً 18 دسی‌بل از صدای پس‌زمینه
122
+
123
+ # اعمال fade-in و fade-out برای موزیک پس‌زمینه کلی
124
+ fade_duration = 3000 # 3 ثانیه برای fade-in و fade-out کلی
125
+ background_audio = background_audio.fade_in(fade_duration).fade_out(fade_duration)
126
+
127
+ # ایجاد یک قطعه صوتی ساکت به اندازه طول کل پادکست
128
+ mixed_audio = AudioSegment.silent(duration=total_duration_estimate)
129
+
130
+ # 1. افزودن موزیک پس‌زمینه به mixed_audio (این موزیک قبلاً کاهش گین و fade شده است)
131
+ mixed_audio = mixed_audio.overlay(background_audio, position=0)
132
+
133
+ # 2. افزودن Intro Audio (خوش آمد گویی) در ابتدای پادکست
134
+ mixed_audio = mixed_audio.overlay(intro_audio, position=0)
135
+
136
+ # 3. افزودن Podcast Audio (دیالوگ‌ها) به mixed_audio، بلافاصله پس از اینترو
137
+ mixed_audio = mixed_audio.overlay(podcast_audio, position=len(intro_audio))
138
+
139
+ # 4. افزودن Outro Audio (پایان پادکست) به mixed_audio، بلافاصله پس از پادکست
140
+ mixed_audio = mixed_audio.overlay(outro_audio, position=len(intro_audio) + len(podcast_audio))
141
+
142
+ return mixed_audio, None
143
+
144
+ def tts_and_merge_with_effects(text_input):
145
+ """متن ورودی را پردازش کرده و فایل صوتی با افکت‌های صوتی تولید می‌کند."""
146
+ if not text_input.strip():
147
+ return None, "لطفاً متنی برای پردازش وارد کنید."
148
+
149
+ # تجزیه متن و تولید پادکست اصلی
150
+ lines = text_input.strip().split('\n')
151
+
152
+ audio_urls_to_merge = []
153
+ errors = []
154
+
155
+ for line in lines:
156
+ match = re.match(r'^\s*\((\d+)\)(.*)$', line)
157
+ if match:
158
+ speaker_number = match.group(1)
159
+ text_for_tts = match.group(2).strip()
160
+
161
+ if not text_for_tts:
162
+ errors.append(f"خطا: متن خالی برای گوینده {speaker_number} در خط '{line}'")
163
+ continue
164
+
165
+ # ساخت URL برای Talkbot API
166
+ # Encode the text for URL safety
167
+ encoded_text = requests.utils.quote(text_for_tts)
168
+ tts_url = f"https://talkbot.ir/api/TTS-S{speaker_number}?text={encoded_text}"
169
+
170
+ print(f"درخواست TTS برای گوینده {speaker_number}: {tts_url}")
171
+
172
+ try:
173
+ response = requests.get(tts_url)
174
+ response.raise_for_status()
175
+ audio_link = response.text.strip()
176
+
177
+ if audio_link.startswith("http"):
178
+ audio_urls_to_merge.append(audio_link)
179
+ else:
180
+ errors.append(f"API برای گوینده {speaker_number} لینک معتبری برنگرداند: '{audio_link}'")
181
+
182
+ except requests.exceptions.RequestException as e:
183
+ errors.append(f"خطا در ارتباط با Talkbot API برای گوینده {speaker_number}: {e}")
184
+ except Exception as e:
185
+ errors.append(f"خطای غیرمنتظره در پردازش Talkbot API برای گوینده {speaker_number}: {e}")
186
+ else:
187
+ if line.strip(): # Only add error if line is not empty
188
+ errors.append(f"فرمت نامعتبر در خط: '{line}'. انتظار می‌رود (شماره)متن.")
189
+
190
+ if not audio_urls_to_merge:
191
+ # Return specific errors if any occurred, otherwise a generic message
192
+ return None, "هیچ فایل صوتی برای ادغام تولید نشد." + ("\n" + "\n".join(errors) if errors else "")
193
+
194
+ # ادغام فایل‌های صوتی تولید شده برای ساخت پادکست اصلی (دیالوگ‌ها)
195
+ podcast_audio_path, merge_message = merge_audio_files(audio_urls_to_merge)
196
+ if not podcast_audio_path:
197
+ return None, merge_message
198
+
199
+ # بارگذاری پادکست اصلی (دیالوگ‌ها) به عنوان AudioSegment
200
+ try:
201
+ podcast_audio = AudioSegment.from_file(podcast_audio_path)
202
+ except Exception as e:
203
+ # Clean up the temporary merged file if it exists
204
+ if os.path.exists(podcast_audio_path):
205
+ os.remove(podcast_audio_path)
206
+ return None, f"خطا در بارگذاری پادکست اصلی از مسیر موقت ({podcast_audio_path}): {e}"
207
+ finally:
208
+ # Always try to remove the temporary podcast_audio_path file
209
+ if os.path.exists(podcast_audio_path):
210
+ try:
211
+ os.remove(podcast_audio_path)
212
+ except OSError as e:
213
+ print(f"Error removing temporary podcast audio file {podcast_audio_path}: {e}")
214
+
215
+
216
+ # افزودن افکت‌های صوتی (اینترو، اوترو، پس‌زمینه)
217
+ final_audio, error = add_intro_outro_and_background(
218
+ podcast_audio,
219
+ intro_audio_url="https://talkbot.ir/example/effect-podcast/wk.mp3", # Intro audio URL
220
+ background_audio_url="https://talkbot.ir/example/effect-podcast/bk2.mp3", # Background music URL
221
+ outro_audio_url="https://talkbot.ir/example/effect-podcast/1.mp3" # Outro audio URL
222
+ )
223
+ if not final_audio:
224
+ return None, error
225
+
226
+ # ذخیره فایل نهایی
227
+ output_filename = os.path.join(TEMP_DIR, f"final_podcast_{uuid.uuid4()}.mp3")
228
+ try:
229
+ final_audio.export(output_filename, format="mp3")
230
+ return output_filename, "پادکست با افکت‌های صوتی با موفقیت تولید شد!"
231
+ except Exception as e:
232
+ return None, f"خطا در ذخیره پادکست نهایی: {e}"
233
+
234
+ # ایجاد رابط کاربری Gradio
235
+ with gr.Blocks() as demo:
236
+ gr.Markdown(
237
+ """
238
+ # ابزار ادغام فایل‌های صوتی و تولید پادکست از متن
239
+ در اینجا می‌توانید فایل‌های صوتی را ادغام کنید یا از متن با Talkbot API پادکست بسازید.
240
+
241
+ **بخش‌ها:**
242
+ 1. **ادغام فایل‌های صوتی موجود:** چندین لینک یا مسیر فایل صوتی را وارد کرده و آن‌ها را ادغام کنید.
243
+ 2. **تولید پادکست از متن با Talkbot API و افکت‌های صوتی:** متنی با فرمت گوینده (مثال: `(1)سلام`) را وارد کنید تا به پادکست تبدیل شود. این پادکست شامل افکت‌های صوتی (اینترو، پس‌زمینه، اوترو) با تنظیم بلندی صدای پس‌زمینه خواهد بود.
244
+ """
245
+ )
246
+
247
+ with gr.Tab("ادغام فایل‌های صوتی موجود"):
248
+ gr.Markdown("## ادغام فایل‌های صوتی موجود (از لینک یا فایل محلی)")
249
+ audio_links_input = gr.Textbox(
250
+ label="لینک یا مسیر فایل‌های صوتی (هر کدام در یک خط جدید)",
251
+ placeholder="مثال:\nhttps://example.com/audio1.mp3\n./local_audio.wav\nhttps://example.com/audio2.wav",
252
+ lines=10
253
+ )
254
+ audio_merge_output_message = gr.Textbox(label="پیام", interactive=False)
255
+ audio_merge_output_audio = gr.Audio(label="فایل صوتی ادغام شده", type="filepath")
256
+ merge_button = gr.Button("ادغام فایل‌های صوتی")
257
+
258
+ merge_button.click(
259
+ fn=lambda x: merge_audio_files([s.strip() for s in x.split('\n') if s.strip()]),
260
+ inputs=[audio_links_input],
261
+ outputs=[audio_merge_output_audio, audio_merge_output_message]
262
+ )
263
+
264
+ # Examples for audio merging
265
+ gr.Examples(
266
+ examples=[
267
+ ["https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3\nhttps://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3"],
268
+ ],
269
+ inputs=audio_links_input,
270
+ label="نمونه‌ها"
271
+ )
272
+
273
+ with gr.Tab("تولید پادکست از متن (Talkbot API) و افکت‌های صوتی"):
274
+ gr.Markdown("## تولید پادکست با Talks (Talkbot API) و افکت‌های صوتی")
275
+ tts_text_input = gr.Textbox(
276
+ label="متن برای تولید پادکست (فرمت: (شماره)متن - هر پرسوناژ در یک خط جدید)",
277
+ placeholder="(1)سلام این تست صحبت اولین نفر است.\n(2)سلام، بله این هم یک تست است و من کاراکتر دوم هستم.\n(1)خب از کجا شروع کنیم\n(2)بهتره از اول شروع کنیم",
278
+ lines=10
279
+ )
280
+ tts_output_message = gr.Textbox(label="پیام", interactive=False)
281
+ tts_output_audio = gr.Audio(label="فایل پادکست تولید شده", type="filepath")
282
+ tts_merge_button = gr.Button("تولید پادکست")
283
+
284
+ tts_merge_button.click(
285
+ fn=tts_and_merge_with_effects,
286
+ inputs=[tts_text_input],
287
+ outputs=[tts_output_audio, tts_output_message]
288
+ )
289
+
290
+ # Examples for TTS
291
+ gr.Examples(
292
+ examples=[
293
+ ["(1)سلام این تست صحبت اولین نفر است.\n(2)سلام، بله این هم یک تست است و من کاراکتر دوم هستم."],
294
+ ["(1)امروز هوا چطوره؟\n(2)فکر کنم آفتابیه."]
295
+ ],
296
+ inputs=tts_text_input,
297
+ label="نمونه‌ها"
298
+ )
299
+
300
+ if __name__ == "__main__":
301
+ demo.launch() # برای اجرا در لوکال
302
+ # demo.launch(share=True) # برای اشتراک‌گذاری موقت در یک لینک عمومی