File size: 8,406 Bytes
319ea69
c296f8a
cd88e5f
39ac26a
cd88e5f
 
319ea69
cd88e5f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c296f8a
cd88e5f
 
 
 
 
 
 
 
 
 
 
 
319ea69
cd88e5f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a12f33
cd88e5f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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 برای دسترسی عمومی در هاگینگ‌فیس یا از طریق لینک موقت