Spaces:
Running
Running
| 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())) | |
| # Try to determine if it's a URL or a local path. | |
| # For Gradio's file input, it provides a local path. | |
| # For direct text input (like in the tts part), it's a URL. | |
| is_url = input_source.startswith("http://") or input_source.startswith("https://") | |
| audio_path = None # Initialize audio_path to handle finally block | |
| downloaded_temp_filepath = None | |
| if is_url: | |
| file_extension = os.path.splitext(input_source.split('?')[0])[1] | |
| if not file_extension: | |
| file_extension = ".mp3" # Default if no extension found in URL | |
| downloaded_temp_filepath = unique_filename + "_downloaded" + file_extension | |
| if not download_file(input_source, downloaded_temp_filepath): | |
| return None, f"خطا در دانلود فایل از لینک: {input_source}" | |
| audio_path = downloaded_temp_filepath | |
| else: # Assume it's a local file path provided directly or by Gradio | |
| audio_path = input_source | |
| try: | |
| if not os.path.exists(audio_path): | |
| return None, f"فایل پیدا نشد: {audio_path}" | |
| audio = AudioSegment.from_file(audio_path) | |
| return audio, None | |
| except Exception as e: | |
| return None, f"خطا در بارگذاری فایل صوتی ({audio_path}): {e}. مطمئن شوید فایل MP3 یا WAV معتبر است." | |
| finally: | |
| # Clean up downloaded temporary file if it was created | |
| if downloaded_temp_filepath and os.path.exists(downloaded_temp_filepath): | |
| try: | |
| os.remove(downloaded_temp_filepath) | |
| except OSError as e: | |
| print(f"Error removing temporary file {downloaded_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: | |
| # get_audio_from_input can now handle direct file paths from Gradio or URLs | |
| 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_audio, intro_error = get_audio_from_input(intro_audio_url) | |
| if intro_audio is None: | |
| return None, f"خطا در دانلود یا بارگذاری صدای ابتدایی: {intro_error}" | |
| background_audio, background_error = get_audio_from_input(background_audio_url) | |
| if background_audio is None: | |
| return None, f"خطا در دانلود یا بارگذاری موزیک پسزمینه: {background_error}" | |
| outro_audio, outro_error = get_audio_from_input(outro_audio_url) | |
| if outro_audio is None: | |
| return None, f"خطا در دانلود یا بارگذاری صدای انتهایی: {outro_error}" | |
| # طول کل صدای نهایی برای تکرار موزیک پسزمینه | |
| total_duration_estimate = len(intro_audio) + len(podcast_audio) + len(outro_audio) | |
| # مطمئن شوید موزیک پسزمینه به اندازه کافی طولانی است یا تکرار شود | |
| if len(background_audio) < total_duration_estimate: | |
| background_audio = background_audio * (int(total_duration_estimate / len(background_audio)) + 1) | |
| # Trim background audio to total duration | |
| background_audio = background_audio[:total_duration_estimate] | |
| # تنظیم شدت صدای پسزمینه (gain reduction) | |
| # این مقدار را میتوانید تغییر دهید. -18dB معمولاً خوب است، اما بسته به نوع موسیقی متغیر است. | |
| # هرچه عدد منفیتر باشد، صدا کمتر میشود. | |
| background_audio = background_audio - 18 # کاهش مثلاً 18 دسیبل از صدای پسزمینه | |
| # اعمال fade-in و fade-out برای موزیک پسزمینه کلی | |
| fade_duration = 3000 # 3 ثانیه برای fade-in و fade-out کلی | |
| background_audio = background_audio.fade_in(fade_duration).fade_out(fade_duration) | |
| # ایجاد یک قطعه صوتی ساکت به اندازه طول کل پادکست | |
| mixed_audio = AudioSegment.silent(duration=total_duration_estimate) | |
| # 1. افزودن موزیک پسزمینه به mixed_audio (این موزیک قبلاً کاهش گین و fade شده است) | |
| mixed_audio = mixed_audio.overlay(background_audio, position=0) | |
| # 2. افزودن Intro Audio (خوش آمد گویی) در ابتدای پادکست | |
| mixed_audio = mixed_audio.overlay(intro_audio, position=0) | |
| # 3. افزودن Podcast Audio (دیالوگها) به mixed_audio، بلافاصله پس از اینترو | |
| mixed_audio = mixed_audio.overlay(podcast_audio, position=len(intro_audio)) | |
| # 4. افزودن Outro Audio (پایان پادکست) به mixed_audio، بلافاصله پس از پادکست | |
| mixed_audio = mixed_audio.overlay(outro_audio, position=len(intro_audio) + len(podcast_audio)) | |
| return mixed_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 | |
| # Encode the text for URL safety | |
| encoded_text = requests.utils.quote(text_for_tts) | |
| tts_url = f"https://talkbot.ir/api/TTS-S{speaker_number}?text={encoded_text}" | |
| 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(): # Only add error if line is not empty | |
| errors.append(f"فرمت نامعتبر در خط: '{line}'. انتظار میرود (شماره)متن.") | |
| if not audio_urls_to_merge: | |
| # Return specific errors if any occurred, otherwise a generic message | |
| return None, "هیچ فایل صوتی برای ادغام تولید نشد." + ("\n" + "\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 | |
| # بارگذاری پادکست اصلی (دیالوگها) به عنوان AudioSegment | |
| try: | |
| podcast_audio = AudioSegment.from_file(podcast_audio_path) | |
| except Exception as e: | |
| # Clean up the temporary merged file if it exists | |
| if os.path.exists(podcast_audio_path): | |
| os.remove(podcast_audio_path) | |
| return None, f"خطا در بارگذاری پادکست اصلی از مسیر موقت ({podcast_audio_path}): {e}" | |
| finally: | |
| # Always try to remove the temporary podcast_audio_path file | |
| if os.path.exists(podcast_audio_path): | |
| try: | |
| os.remove(podcast_audio_path) | |
| except OSError as e: | |
| print(f"Error removing temporary podcast audio file {podcast_audio_path}: {e}") | |
| # افزودن افکتهای صوتی (اینترو، اوترو، پسزمینه) | |
| final_audio, error = add_intro_outro_and_background( | |
| podcast_audio, | |
| intro_audio_url="https://suprime.ir/example/effect-podcast/wk.mp3", # Intro audio URL | |
| background_audio_url="https://suprime.ir/example/effect-podcast/bk2.mp3", # Background music URL | |
| outro_audio_url="https://suprime.ir/example/effect-podcast/1.mp3" # Outro audio URL | |
| ) | |
| 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( | |
| """ | |
| # ابزار ادغام فایلهای صوتی و تولید پادکست از متن | |
| در اینجا میتوانید فایلهای صوتی را ادغام کنید یا از متن با Talkbot API پادکست بسازید. | |
| **بخشها:** | |
| 1. **ادغام فایلهای صوتی موجود:** چندین لینک یا مسیر فایل صوتی را وارد کرده و آنها را ادغام کنید. | |
| 2. **تولید پادکست از متن با Talkbot API و افکتهای صوتی:** متنی با فرمت گوینده (مثال: `(1)سلام`) را وارد کنید تا به پادکست تبدیل شود. این پادکست شامل افکتهای صوتی (اینترو، پسزمینه، اوترو) با تنظیم بلندی صدای پسزمینه خواهد بود. | |
| """ | |
| ) | |
| 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] | |
| ) | |
| # Examples for audio merging | |
| 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("## تولید پادکست با Talks (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] | |
| ) | |
| # Examples for TTS | |
| gr.Examples( | |
| examples=[ | |
| ["(1)سلام این تست صحبت اولین نفر است.\n(2)سلام، بله این هم یک تست است و من کاراکتر دوم هستم."], | |
| ["(1)امروز هوا چطوره؟\n(2)فکر کنم آفتابیه."] | |
| ], | |
| inputs=tts_text_input, | |
| label="نمونهها" | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() # برای اجرا در لوکال | |
| # demo.launch(share=True) # برای اشتراکگذاری موقت در یک لینک عمومی | |