import gradio as gr from pydub import AudioSegment import requests import os import uuid import re # مسیر ذخیره فایل‌های موقت TEMP_DIR = "temp_audio" if not os.path.exists(TEMP_DIR): os.makedirs(TEMP_DIR) def download_file(url, output_path): """فایل را از یک URL دانلود می‌کند.""" try: response = requests.get(url, stream=True) response.raise_for_status() with open(output_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) return True except requests.exceptions.RequestException as e: print(f"Error downloading {url}: {e}") return False except Exception as e: print(f"An unexpected error occurred during download of {url}: {e}") return False def get_audio_from_input(input_source): """منبع ورودی را پردازش کرده و یک شی AudioSegment برمی‌گرداند.""" unique_filename = os.path.join(TEMP_DIR, str(uuid.uuid4())) if input_source.startswith("http://") or input_source.startswith("https://"): file_extension = os.path.splitext(input_source.split('?')[0])[1] if not file_extension: file_extension = ".mp3" temp_filepath = unique_filename + "_downloaded" + file_extension if not download_file(input_source, temp_filepath): return None, f"خطا در دانلود فایل از لینک: {input_source}" audio_path = temp_filepath else: audio_path = input_source try: audio = AudioSegment.from_file(audio_path) return audio, None except Exception as e: return None, f"خطا در بارگذاری فایل صوتی ({audio_path}): {e}. مطمئن شوید فایل MP3 یا WAV معتبر است." finally: if 'temp_filepath' in locals() and os.path.exists(temp_filepath): try: os.remove(temp_filepath) except OSError as e: print(f"Error removing temporary file {temp_filepath}: {e}") def merge_audio_files(input_sources): """چندین فایل صوتی را ادغام می‌کند و یک فایل MP3 خروجی می‌دهد.""" if not input_sources: return None, "لیست ورودی‌های صوتی خالی است." combined_audio = AudioSegment.empty() errors = [] for source in input_sources: audio_segment, error = get_audio_from_input(source) if audio_segment: combined_audio += audio_segment else: errors.append(error) print(f"Skipping {source} due to error: {error}") if not combined_audio.duration_seconds > 0: return None, "هیچ فایل صوتی معتبری برای ادغام پیدا نشد. " + "\n".join(errors) if errors else "" output_filename = os.path.join(TEMP_DIR, f"merged_audio_{uuid.uuid4()}.mp3") try: combined_audio.export(output_filename, format="mp3") return output_filename, "عملیات موفقیت آمیز بود!" except Exception as e: return None, f"خطا در ذخیره فایل خروجی: {e}" def add_intro_outro_and_background(podcast_audio, intro_audio_url, background_audio_url, outro_audio_url): """افکت‌های صوتی را به پادکست اضافه می‌کند.""" # دانلود صدای ابتدایی (intro) intro_audio, _ = get_audio_from_input(intro_audio_url) if not intro_audio: return None, "خطا در دانلود یا بارگذاری صدای ابتدایی" # دانلود موزیک پس‌زمینه background_audio, _ = get_audio_from_input(background_audio_url) if not background_audio: return None, "خطا در دانلود یا بارگذاری موزیک پس‌زمینه" # دانلود صدای انتهایی (outro) outro_audio, _ = get_audio_from_input(outro_audio_url) if not outro_audio: return None, "خطا در دانلود یا بارگذاری صدای انتهایی" # طول پادکست اصلی podcast_duration = len(podcast_audio) # اضافه کردن صدای ابتدایی final_audio = intro_audio # اضافه کردن موزیک پس‌زمینه background_audio = background_audio[:podcast_duration + 2000] # موزیک به اندازه پادکست + 2 ثانیه background_audio = background_audio.fade_in(2000).fade_out(2000) # fade-in و fade-out final_audio = final_audio.overlay(background_audio, position=0) # موزیک پس‌زمینه از ابتدا شروع می‌شود # اضافه کردن پادکست اصلی final_audio = final_audio + podcast_audio # اضافه کردن صدای انتهایی final_audio = final_audio + outro_audio return final_audio, None def tts_and_merge_with_effects(text_input): """متن ورودی را پردازش کرده و فایل صوتی با افکت‌های صوتی تولید می‌کند.""" if not text_input.strip(): return None, "لطفاً متنی برای پردازش وارد کنید." # تجزیه متن و تولید پادکست اصلی lines = text_input.strip().split('\n') audio_urls_to_merge = [] errors = [] for line in lines: match = re.match(r'^\s*\((\d+)\)(.*)$', line) if match: speaker_number = match.group(1) text_for_tts = match.group(2).strip() if not text_for_tts: errors.append(f"خطا: متن خالی برای گوینده {speaker_number} در خط '{line}'") continue # ساخت URL برای Talkbot API tts_url = f"https://talkbot.ir/api/TTS-S{speaker_number}?text={text_for_tts}" print(f"درخواست TTS برای گوینده {speaker_number}: {tts_url}") try: response = requests.get(tts_url) response.raise_for_status() audio_link = response.text.strip() if audio_link.startswith("http"): audio_urls_to_merge.append(audio_link) else: errors.append(f"API برای گوینده {speaker_number} لینک معتبری برنگرداند: '{audio_link}'") except requests.exceptions.RequestException as e: errors.append(f"خطا در ارتباط با Talkbot API برای گوینده {speaker_number}: {e}") except Exception as e: errors.append(f"خطای غیرمنتظره در پردازش Talkbot API برای گوینده {speaker_number}: {e}") else: if line.strip(): errors.append(f"فرمت نامعتبر در خط: '{line}'. انتظار می‌رود (شماره)متن.") if not audio_urls_to_merge: return None, "هیچ فایل صوتی برای ادغام تولید نشد." + "\n".join(errors) if errors else "" # ادغام فایل‌های صوتی تولید شده podcast_audio_path, merge_message = merge_audio_files(audio_urls_to_merge) if not podcast_audio_path: return None, merge_message # بارگذاری پادکست اصلی podcast_audio = AudioSegment.from_file(podcast_audio_path) # افزودن افکت‌های صوتی final_audio, error = add_intro_outro_and_background( podcast_audio, intro_audio_url="https://talkbot.ir/example/effect-podcast/wk.mp3", background_audio_url="https://talkbot.ir/example/effect-podcast/bk.mp3", # مسیر موزیک پس‌زمینه outro_audio_url="https://talkbot.ir/example/effect-podcast/1.mp3" # مسیر صدای انتهایی ) if not final_audio: return None, error # ذخیره فایل نهایی output_filename = os.path.join(TEMP_DIR, f"final_podcast_{uuid.uuid4()}.mp3") try: final_audio.export(output_filename, format="mp3") return output_filename, "پادکست با افکت‌های صوتی با موفقیت تولید شد!" except Exception as e: return None, f"خطا در ذخیره پادکست نهایی: {e}" # ایجاد رابط کاربری Gradio with gr.Blocks() as demo: gr.Markdown( """ # ابزار ادغام فایل‌های صوتی (MP3/WAV) و تولید صدا از متن در اینجا دو بخش اصلی وجود دارد: 1. **ادغام فایل‌های صوتی موجود:** می‌توانید لینک‌های فایل‌های صوتی (MP3/WAV) را وارد کنید. 2. **تولید صدا از متن با Talkbot API و ادغام:** متنی را با فرمت `(شماره)متن` وارد کنید. (مثال: `(1)سلام\n(2)بله`) این بخش با استفاده از Talkbot API صدا تولید کرده و آن‌ها را ادغام می‌کند. """ ) with gr.Tab("ادغام فایل‌های صوتی موجود"): gr.Markdown("## ادغام فایل‌های صوتی موجود (از لینک یا فایل محلی)") audio_links_input = gr.Textbox( label="لینک یا مسیر فایل‌های صوتی (هر کدام در یک خط جدید)", placeholder="مثال:\nhttps://example.com/audio1.mp3\n./local_audio.wav\nhttps://example.com/audio2.wav", lines=10 ) audio_merge_output_message = gr.Textbox(label="پیام", interactive=False) audio_merge_output_audio = gr.Audio(label="فایل صوتی ادغام شده", type="filepath") merge_button = gr.Button("ادغام فایل‌های صوتی") merge_button.click( fn=lambda x: merge_audio_files([s.strip() for s in x.split('\n') if s.strip()]), inputs=[audio_links_input], outputs=[audio_merge_output_audio, audio_merge_output_message] ) gr.Examples( examples=[ ["https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3\nhttps://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3"], ], inputs=audio_links_input, label="نمونه‌ها" ) with gr.Tab("تولید صدا از متن (Talkbot API) و ادغام"): gr.Markdown("## تولید صدا با Talkbot API و ادغام فایل‌ها") tts_text_input = gr.Textbox( label="متن برای تولید صدا (فرمت: (شماره)متن - هر پرسوناژ در یک خط جدید)", placeholder="(1)سلام این تست صحبت اولین نفر است.\n(2)سلام، بله این هم یک تست است و من کاراکتر دوم هستم.\n(1)خب از کجا شروع کنیم\n(2)بهتره از اول شروع کنیم", lines=10 ) tts_output_message = gr.Textbox(label="پیام", interactive=False) tts_output_audio = gr.Audio(label="فایل صوتی تولید و ادغام شده", type="filepath") tts_merge_button = gr.Button("تولید و ادغام صدا") tts_merge_button.click( fn=tts_and_merge_with_effects, inputs=[tts_text_input], outputs=[tts_output_audio, tts_output_message] ) gr.Examples( examples=[ ["(1)سلام این تست صحبت اولین نفر است.\n(2)سلام، بله این هم یک تست است و من کاراکتر دوم هستم."], ["(1)امروز هوا چطوره؟\n(2)فکر کنم آفتابیه."] ], inputs=tts_text_input, label="نمونه‌ها" ) if __name__ == "__main__": demo.launch() # برای اجرا در لوکال # demo.launch(share=True) # برای اشتراک‌گذاری موقت در یک لینک عمومی