import gradio as gr from pydub import AudioSegment import requests import os import uuid import re # برای پردازش لینک‌های ورودی # پوشه برای ذخیره فایل‌های موقت TEMP_DIR = "temp_mp3_files" os.makedirs(TEMP_DIR, exist_ok=True) # اطمینان از وجود پوشه def download_mp3(url: str, output_path: str) -> bool: """ فایل MP3 را از URL دانلود می‌کند. :param url: URL فایل MP3. :param output_path: مسیر کامل برای ذخیره فایل دانلود شده. :return: True اگر دانلود موفق بود، False در غیر این صورت. """ try: response = requests.get(url, stream=True, timeout=30) # افزودن timeout response.raise_for_status() # برای HTTP errors (4xx or 5xx) content_type = response.headers.get('Content-Type', '') if 'audio' not in content_type and 'mpeg' not in content_type: print(f"URL: {url} does not seem to point to an audio file. Content-Type: {content_type}") return False with open(output_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): if chunk: # filter out keep-alive new chunks f.write(chunk) print(f"Successfully downloaded {url} to {output_path}") return True except requests.exceptions.Timeout: print(f"Error downloading {url}: Request timed out.") return False 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 merge_mp3_files_sequential(mp3_file_paths: list[str], output_file_path: str) -> bool: """ چندین فایل MP3 را به ترتیب ادغام می‌کند. :param mp3_file_paths: لیستی از مسیرهای فایل‌های MP3. :param output_file_path: مسیر کامل برای ذخیره فایل ادغام شده. :return: True اگر ادغام موفق بود، False در غیر این صورت. """ if not mp3_file_paths: return False combined_audio = None try: # بارگذاری اولین فایل combined_audio = AudioSegment.from_file(mp3_file_paths[0], format="mp3") # ادغام بقیه فایل‌ها for i, file_path in enumerate(mp3_file_paths[1:]): audio = AudioSegment.from_file(file_path, format="mp3") combined_audio += audio print(f"Merged {file_path}") combined_audio.export(output_file_path, format="mp3") print(f"Successfully merged all files to {output_file_path}") return True except Exception as e: print(f"Error merging MP3 files: {e}") return False def process_mp3_links_gradio(urls_input: str) -> tuple[str, str | None]: """ این تابع اصلی Gradio است که رشته‌ای از لینک‌های MP3 را دریافت کرده، آن‌ها را دانلود و ادغام می‌کند. :param urls_input: رشته‌ای شامل لینک‌های MP3، جدا شده با خط جدید یا کاما. :return: یک تاپل شامل پیام وضعیت و مسیر فایل خروجی (یا None). """ # پاکسازی و تقسیم لینک‌ها # حذف فضای خالی اطراف لینک‌ها و فیلتر کردن لینک‌های خالی urls = [url.strip() for url in re.split(r'[\n,]+', urls_input) if url.strip()] if not urls: return "لطفاً حداقل یک لینک MP3 معتبر وارد کنید.", None if len(urls) < 2: return "لطفاً حداقل دو لینک MP3 برای ادغام وارد کنید.", None status_message = "" output_audio_path = None downloaded_files_paths = [] # برای نگهداری مسیر فایل‌های دانلود شده temp_file_mapping = {} # URL -> temp_path # برای تولید یک unique_id برای هر درخواست request_unique_id = str(uuid.uuid4()) request_temp_dir = os.path.join(TEMP_DIR, request_unique_id) os.makedirs(request_temp_dir, exist_ok=True) try: for i, url in enumerate(urls): temp_file_name = f"temp_{i+1}_{uuid.uuid4().hex}.mp3" temp_file_path = os.path.join(request_temp_dir, temp_file_name) temp_file_mapping[url] = temp_file_path # ذخیره mapping برای حذف آسان status_message += f"در حال دانلود فایل {i+1} از: {url}\n" gr.Info(f"Downloading file {i+1}...") # نمایش پیام در UI Gradio if download_mp3(url, temp_file_path): downloaded_files_paths.append(temp_file_path) else: return f"خطا در دانلود فایل {i+1} از '{url}'. لطفاً لینک را بررسی کنید.", None if not downloaded_files_paths: return "هیچ فایلی با موفقیت دانلود نشد. لطفاً لینک‌ها را بررسی کنید.", None status_message += "فایل‌ها با موفقیت دانلود شدند. در حال ادغام...\n" gr.Info("Files downloaded. Merging...") output_file_name = f"merged_output_{uuid.uuid4().hex}.mp3" output_file_path = os.path.join(request_temp_dir, output_file_name) if merge_mp3_files_sequential(downloaded_files_paths, output_file_path): status_message += f"فایل‌ها با موفقیت ادغام شدند. فایل خروجی: {output_file_path}\n" output_audio_path = output_file_path # مسیر فایل برای Gradio else: status_message += "خطا در ادغام فایل‌ها.\n" except Exception as e: status_message += f"خطای پیش‌بینی نشده: {e}\n" finally: # حذف فایل‌های موقت پس از اتمام کار # حذف تنها فایل‌هایی که در این درخواست ایجاد شده‌اند for f in os.listdir(request_temp_dir): os.remove(os.path.join(request_temp_dir, f)) os.rmdir(request_temp_dir) print(f"Removed temporary directory: {request_temp_dir}") return status_message, output_audio_path # تعریف رابط Gradio # برای بهبود تجربه کاربری در فضای هاگینگ‌فیس، بهتر است از theme=gr.themes.Soft() استفاده کنید. # و همچنین css را با دقت بیشتری برای RTL بنویسید. iface = gr.Interface( fn=process_mp3_links_gradio, inputs=[ gr.Textbox( label="لینک‌های MP3 (هر لینک در یک خط یا با کاما جدا کنید)", placeholder="مثال:\nhttps://example.com/audio1.mp3\nhttps://example.com/audio2.mp3\n...", lines=5 # امکان ورود چند خط ), ], outputs=[ gr.Textbox(label="وضعیت", interactive=False), gr.Audio(label="فایل MP3 ادغام شده", type="filepath") ], title="🎙️ ادغام کننده چندین فایل MP3 از طریق لینک 🎵", description="لینک‌های فایل‌های MP3 خود را (هر لینک در یک خط جدید یا با کاما جدا شده) در کادر زیر وارد کنید تا به صورت یک فایل ادغام شوند.", allow_flagging="never", theme=gr.themes.Soft(), # استفاده از تم جدید Gradio css=""" body { font-family: 'Vazirmatn', sans-serif; direction: rtl; text-align: right; } h1, h2, h3, h4, h5, h6 { text-align: center; } .gr-textbox label, .gr-audio label { text-align: right; width: 100%; display: block; } .gradio-container { max-width: 800px; margin: auto; padding: 20px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border-radius: 8px; } .gr-button { background-color: #4CAF50; /* Example button color */ color: white; } """ ) # تابع راه اندازی: if __name__ == "__main__": iface.launch(share=True) # share=True برای دسترسی عمومی در هاگینگ‌فیس یا از طریق لینک موقت