suprimedev commited on
Commit
9ddae4b
·
verified ·
1 Parent(s): 79fd030

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -87
app.py CHANGED
@@ -3,164 +3,226 @@ from pydub import AudioSegment
3
  import requests
4
  import os
5
  import uuid
6
- import re # برای پردازش لینک‌های ورودی
7
- import mimetypes # برای استفاده از Content-Type برای تخمین پسوند فایل
 
8
 
9
- # پوشه برای ذخیره فایل‌های موقت
10
- TEMP_DIR = "temp_audio_files" # تغییر نام پوشه برای انعطاف‌پذیری بیشتر
11
- os.makedirs(TEMP_DIR, exist_ok=True) # اطمینان از وجود پوشه
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- def download_audio(url: str, output_dir: str) -> tuple[str | None, str | None]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
15
  فایل صوتی را از URL دانلود می‌کند.
16
  :param url: URL فایل صوتی.
17
  :param output_dir: دایرکتوری برای ذخیره فایل دانلود شده.
18
- :return: (مسیر کامل فایل دانلود شده, پسوند تخمین زده شده) یا (None, None) در صورت خطا.
19
  """
20
  try:
21
  response = requests.get(url, stream=True, timeout=30)
22
  response.raise_for_status()
23
 
24
- content_type = response.headers.get('Content-Type', '')
25
- # تخمین پسوند فایل بر اساس Content-Type
26
- # اگر نتوانستیم پسوند را از Content-Type حدس بزنیم، از 'mp3' به عنوان پیش‌فرض استفاده می‌کنیم
27
- # این کار ریسک ایجاد می‌کند ولی pydub ممکن است بتواند آن را تشخیص دهد.
28
- estimated_extension = mimetypes.guess_extension(content_type)
29
- if not estimated_extension:
30
- # سعی می‌کنیم پسوند را از URL بگیریم
31
- path_parts = os.path.splitext(url.split('?')[0])
32
- if len(path_parts) > 1 and path_parts[1]:
33
- estimated_extension = path_parts[1].lower()
 
 
34
  else:
35
- # اگر هیچ کدام جواب نداد، mp3 را به عنوان پیش‌فرض قرار می‌دهیم
36
- # pydub در زمان بارگذاری فایل واقعی، فرمت را بررسی می‌کند.
37
- estimated_extension = ".mp3"
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- # اطمینان از پاک بودن پسوند (بدون نقطه اضافی)
40
- if estimated_extension.startswith('.'):
41
- estimated_extension = estimated_extension[1:]
42
 
43
- # ساخت نام فایل موقت
44
- temp_file_name = f"temp_download_{uuid.uuid4().hex}.{estimated_extension}"
45
- output_path = os.path.join(output_dir, temp_file_name)
46
 
47
  with open(output_path, 'wb') as f:
48
  for chunk in response.iter_content(chunk_size=8192):
49
  if chunk:
50
  f.write(chunk)
51
- print(f"Successfully downloaded {url} to {output_path} (estimated type: {estimated_extension})")
52
- return output_path, estimated_extension
53
  except requests.exceptions.Timeout:
54
- print(f"Error downloading {url}: Request timed out.")
55
- return None, None
56
  except requests.exceptions.RequestException as e:
57
- print(f"Error downloading {url}: {e}")
58
- return None, None
59
  except Exception as e:
60
  print(f"An unexpected error occurred during download of {url}: {e}")
61
- return None, None
62
 
63
  def merge_audio_files_sequential(audio_file_paths: list[str], output_file_path: str) -> bool:
64
  """
65
  چندین فایل صوتی را به ترتیب ادغام می‌کند. همه فایل‌ها به MP3 خروجی گرفته می‌شوند.
66
- :param audio_file_paths: لیستی از مسیرهای فایل‌های صوتی (MP3, WAV, و غیره).
67
  :param output_file_path: مسیر کامل برای ذخیره فایل ادغام شده (خروجی همیشه MP3 خواهد بود).
68
  :return: True اگر ادغام موفق بود، False در غیر این صورت.
69
  """
70
  if not audio_file_paths:
 
71
  return False
72
 
73
  combined_audio = None
 
 
74
  try:
75
  # بارگذاری اولین فایل
76
- # pydub به طور خودکار فرمت را از پسوند فایل تشخیص می‌دهد.
77
- combined_audio = AudioSegment.from_file(audio_file_paths[0])
 
 
 
 
 
78
 
79
  # ادغام بقیه فایل‌ها
80
  for i, file_path in enumerate(audio_file_paths[1:]):
81
- audio = AudioSegment.from_file(file_path)
82
- combined_audio += audio
83
- print(f"Merged {file_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  # خروجی گرفتن نهایی به فرمت MP3
86
  combined_audio.export(output_file_path, format="mp3")
87
- print(f"Successfully merged all files to {output_file_path}")
88
  return True
89
  except Exception as e:
90
- print(f"Error merging audio files: {e}")
91
  return False
92
 
93
  def process_audio_links_gradio(urls_input: str) -> tuple[str, str | None]:
94
  """
95
- این تابع اصلی Gradio است که رشته‌ای از لینک‌های صوتی را دریافت کرده،
96
- آن‌ها را دانلود و ادغام می‌کند.
97
- :param urls_input: رشته‌ای شامل لینک‌های صوتی، جدا شده با خط جدید یا کاما.
98
- :return: یک تاپل شامل پیام وضعیت و مسیر فایل خروجی (یا None).
99
  """
100
- # پاکسازی و تقسیم لینک‌ها
101
  urls = [url.strip() for url in re.split(r'[\n,]+', urls_input) if url.strip()]
102
 
103
  if not urls:
104
  return "لطفاً حداقل یک لینک فایل صوتی معتبر وارد کنید.", None
105
 
106
  if len(urls) < 2:
107
- return "لطفاً حداقل دو لینک فایل صوتی برای ادغام وارد کنید.", None
108
 
109
  status_message = ""
110
  output_audio_path = None
111
  downloaded_files_paths = []
112
 
113
- # برای تولید یک unique_id برای هر درخواست
114
- request_unique_id = str(uuid.uuid4())
115
- request_temp_dir = os.path.join(TEMP_DIR, request_unique_id)
116
- os.makedirs(request_temp_dir, exist_ok=True)
117
-
118
- try:
119
- for i, url in enumerate(urls):
120
- status_message += f"در حال دانلود فایل {i+1} از: {url}\n"
121
- gr.Info(f"Downloading file {i+1}...")
122
- # از تابع download_audio جدید استفاده می‌کنیم
123
- downloaded_path, _ = download_audio(url, request_temp_dir)
124
- if downloaded_path:
125
- downloaded_files_paths.append(downloaded_path)
126
- else:
127
- return f"خطا در دانلود فایل {i+1} از '{url}'. لطفاً لینک را بررسی کنید. ممکن است فایل صوتی نباشد.", None
128
 
129
- if not downloaded_files_paths:
130
- return "هیچ فایلی با موفقیت دانلود نشد. لطفاً لینک‌ها را بررسی کنید.", None
 
 
 
131
 
132
- status_message += "فایل‌ها با موفقیت دانلود شدند. در حال ادغام...\n"
133
- gr.Info("Files downloaded. Merging...")
134
 
135
- output_file_name = f"merged_output_{uuid.uuid4().hex}.mp3" # خروجی همیشه MP3 خواهد بود
136
- output_file_path = os.path.join(request_temp_dir, output_file_name)
137
 
138
- if merge_audio_files_sequential(downloaded_files_paths, output_file_path):
139
- status_message += f"فایل‌ها با موفقیت ادغام شدند. فایل خروجی: {output_file_path}\n"
140
- output_audio_path = output_file_path
141
- else:
142
- status_message += "خطا در ادغام فایل‌ها.\n"
143
 
144
- except Exception as e:
145
- status_message += f"خطای پیش‌بینی نشده: {e}\n"
146
- finally:
147
- # حذف فایل‌های موقت پس از اتمام کار
148
- for f in os.listdir(request_temp_dir):
149
- os.remove(os.path.join(request_temp_dir, f))
150
- os.rmdir(request_temp_dir)
151
- print(f"Removed temporary directory: {request_temp_dir}")
152
 
153
  return status_message, output_audio_path
154
 
155
  # تعریف رابط Gradio
156
- # برای بهبود تجربه کاربری در فضای هاگینگ‌فیس، بهتر است از theme=gr.themes.Soft() استفاده کنید.
157
- # و همچنین css را با دقت بیشتری برای RTL بنویسید.
158
  iface = gr.Interface(
159
  fn=process_audio_links_gradio,
160
  inputs=[
161
  gr.Textbox(
162
  label="لینک‌های فایل صوتی (MP3, WAV و غیره) - هر لینک در یک خط یا با کاما جدا کنید",
163
- placeholder="مثال:\nhttps://example.com/audio1.mp3\nhttps://example.com/audio2.wav\n...",
164
  lines=5
165
  ),
166
  ],
@@ -171,7 +233,7 @@ iface = gr.Interface(
171
  title="🎙️ ادغام کننده چندین فایل صوتی از طریق لینک (MP3, WAV و غیره) 🎵",
172
  description="لینک فایل‌های صوتی خود را (هر لینک در یک خط جدید یا با کاما جدا شده) در کادر زیر وارد کنید تا به صورت یک فایل **MP3** ادغام شوند.",
173
  allow_flagging="never",
174
- theme=gr.themes.Soft(), # استفاده از تم جدید Gradio
175
  css="""
176
  body { font-family: 'Vazirmatn', sans-serif; direction: rtl; text-align: right; }
177
  h1, h2, h3, h4, h5, h6 { text-align: center; }
@@ -184,12 +246,11 @@ iface = gr.Interface(
184
  border-radius: 8px;
185
  }
186
  .gr-button {
187
- background-color: #4CAF50; /* Example button color */
188
  color: white;
189
  }
190
  """
191
  )
192
 
193
- # تابع راه اندازی:
194
  if __name__ == "__main__":
195
  iface.launch(share=True)
 
3
  import requests
4
  import os
5
  import uuid
6
+ import re
7
+ import mimetypes
8
+ import tempfile # برای مدیریت فایل‌های موقت
9
 
10
+ # --- تنظیمات FFmpeg/ffprobe (برای محیط‌های محلی) ---
11
+ # در محیط‌های Production مانند Hugging Face Spaces، معمولاً FFmpeg از قبل نصب شده است.
12
+ # این خطوط تنها در صورتی نیاز است که با مشکل "FFmpeg or AVConvNotFound" مواجه شوید
13
+ # و FFmpeg را در مسیرهای غیر استاندارد نصب کرده باشید.
14
+ # PARENT_DIR = os.path.dirname(os.path.abspath(__file__))
15
+ # FFMPEG_PATH = os.path.join(PARENT_DIR, "ffmpeg", "bin", "ffmpeg.exe") # مثال برای ویندوز
16
+ # FFPROBE_PATH = os.path.join(PARENT_DIR, "ffmpeg", "bin", "ffprobe.exe") # مثال برای ویندوز
17
+ #
18
+ # if os.path.exists(FFMPEG_PATH):
19
+ # AudioSegment.converter = FFMPEG_PATH
20
+ # print(f"FFmpeg path set to: {FFMPEG_PATH}")
21
+ # if os.path.exists(FFPROBE_PATH):
22
+ # AudioSegment.ffprobe = FFPROBE_PATH
23
+ # print(f"FFprobe path set to: {FFPROBE_PATH}")
24
+ # else:
25
+ # print("FFmpeg/FFprobe paths not explicitly set. Pydub will look in system PATH.")
26
 
27
+ # یک تابع کمکی برای بررسی وجود FFmpeg
28
+ def check_ffmpeg_presence():
29
+ try:
30
+ from pydub.utils import get_prober_name, get_encoder_name
31
+ # تلاش برای فراخوانی ffprobe تا بررسی کنیم آیا در دسترس است یا نه
32
+ output = os.popen(f"{get_prober_name()} -version").read()
33
+ if "ffprobe" in output or "avprobe" in output:
34
+ print("FFprobe is available.")
35
+ return True
36
+ else:
37
+ print("FFprobe not found in system PATH or configured path.")
38
+ return False
39
+ except Exception as e:
40
+ print(f"Error checking FFmpeg/FFprobe: {e}")
41
+ return False
42
+
43
+ # بررسی در ابتدای اجرا
44
+ if not check_ffmpeg_presence():
45
+ print("WARNING: FFmpeg/ffprobe might not be correctly installed or configured. Audio processing may fail.")
46
+ # می‌توانید اینجا یک خطای Gradio به کاربر نشان دهید
47
+ # gr.Warning("FFmpeg/ffprobe not found. Please ensure it's installed and accessible.")
48
+
49
+
50
+ def download_audio(url: str, output_dir: str) -> str | None:
51
  """
52
  فایل صوتی را از URL دانلود می‌کند.
53
  :param url: URL فایل صوتی.
54
  :param output_dir: دایرکتوری برای ذخیره فایل دانلود شده.
55
+ :return: مسیر کامل فایل دانلود شده یا None در صورت خطا.
56
  """
57
  try:
58
  response = requests.get(url, stream=True, timeout=30)
59
  response.raise_for_status()
60
 
61
+ # استفاده از Content-Disposition برای نام فایل اگر موجود باشد
62
+ content_disposition = response.headers.get('Content-Disposition')
63
+ if content_disposition:
64
+ fname_match = re.search(r'filename\*?=(?:UTF-8\'\')?\"?([^\"]+)\"?', content_disposition)
65
+ if fname_match:
66
+ original_filename = fname_match.group(1).encode('latin-1').decode('utf-8')
67
+ # پاکسازی نام فایل برای جلوگیری از مشکلات مسیر
68
+ original_filename = re.sub(r'[\\/:*?"<>|]', '_', original_filename)
69
+ # اطمینان حاصل شود که پسوند دارد
70
+ if not os.path.splitext(original_filename)[1]:
71
+ original_filename += mimetypes.guess_extension(response.headers.get('Content-Type', ''), strict=False) or '.tmp'
72
+ temp_filename = f"{uuid.uuid4().hex}_{original_filename}"
73
  else:
74
+ temp_filename = f"temp_download_{uuid.uuid4().hex}.tmp"
75
+ else:
76
+ # تخمین پسوند از Content-Type یا URL
77
+ content_type = response.headers.get('Content-Type', '')
78
+ ext = mimetypes.guess_extension(content_type, strict=False)
79
+ if not ext:
80
+ path_parts = os.path.splitext(url.split('?')[0])
81
+ if len(path_parts) > 1 and path_parts[1]:
82
+ ext = path_parts[1].lower()
83
+ else:
84
+ ext = ".tmp" # پسوند موقت برای pydub تا خودش تشخیص دهد
85
+
86
+ # ساخت نام فایل موقت (با پسوند مربوطه)
87
+ temp_filename = f"temp_download_{uuid.uuid4().hex}{ext}"
88
 
89
+ output_path = os.path.join(output_dir, temp_filename)
 
 
90
 
 
 
 
91
 
92
  with open(output_path, 'wb') as f:
93
  for chunk in response.iter_content(chunk_size=8192):
94
  if chunk:
95
  f.write(chunk)
96
+ print(f"Successfully downloaded {url} to {output_path}")
97
+ return output_path
98
  except requests.exceptions.Timeout:
99
+ print(f"Error downloading {url}: Request timed out after 30 seconds.")
100
+ return None
101
  except requests.exceptions.RequestException as e:
102
+ print(f"Network or HTTP error downloading {url}: {e}")
103
+ return None
104
  except Exception as e:
105
  print(f"An unexpected error occurred during download of {url}: {e}")
106
+ return None
107
 
108
  def merge_audio_files_sequential(audio_file_paths: list[str], output_file_path: str) -> bool:
109
  """
110
  چندین فایل صوتی را به ترتیب ادغام می‌کند. همه فایل‌ها به MP3 خروجی گرفته می‌شوند.
111
+ :param audio_file_paths: لیستی از مسیرهای فایل‌های صوتی.
112
  :param output_file_path: مسیر کامل برای ذخیره فایل ادغام شده (خروجی همیشه MP3 خواهد بود).
113
  :return: True اگر ادغام موفق بود، False در غیر این صورت.
114
  """
115
  if not audio_file_paths:
116
+ print("No audio files provided for merging.")
117
  return False
118
 
119
  combined_audio = None
120
+ successful_merges = 0
121
+
122
  try:
123
  # بارگذاری اولین فایل
124
+ try:
125
+ combined_audio = AudioSegment.from_file(audio_file_paths[0])
126
+ print(f"Successfully loaded initial audio from: {audio_file_paths[0]}")
127
+ successful_merges = 1
128
+ except Exception as e:
129
+ print(f"Error loading first audio file {audio_file_paths[0]}: {e}")
130
+ return False
131
 
132
  # ادغام بقیه فایل‌ها
133
  for i, file_path in enumerate(audio_file_paths[1:]):
134
+ try:
135
+ audio = AudioSegment.from_file(file_path)
136
+ combined_audio += audio
137
+ print(f"Successfully merged {file_path}")
138
+ successful_merges += 1
139
+ except Exception as e:
140
+ print(f"Error merging audio from {file_path}: {e}. Skipping this file.")
141
+ # در اینجا می‌توانیم انتخاب کنیم که آیا کل عملیات را متوقف کنیم یا ادامه دهیم.
142
+ # برای این کاربرد، ادامه دادن معقول‌تر است (ادغام بقیه فایل‌های موفق).
143
+ pass # ادامه به فایل بعدی حتی اگر این یکی موفق نبود
144
+
145
+ if successful_merges < len(audio_file_paths):
146
+ print(f"Warning: Only {successful_merges} out of {len(audio_file_paths)} files were successfully loaded/merged.")
147
+ if successful_merges == 0:
148
+ print("No audio segments were successfully processed for merging.")
149
+ return False
150
 
151
  # خروجی گرفتن نهایی به فرمت MP3
152
  combined_audio.export(output_file_path, format="mp3")
153
+ print(f"Successfully exported final merged file to {output_file_path}")
154
  return True
155
  except Exception as e:
156
+ print(f"An unexpected error occurred during audio merging process: {e}")
157
  return False
158
 
159
  def process_audio_links_gradio(urls_input: str) -> tuple[str, str | None]:
160
  """
161
+ تابع اصلی Gradio برای پردازش لینک‌های صوتی.
 
 
 
162
  """
 
163
  urls = [url.strip() for url in re.split(r'[\n,]+', urls_input) if url.strip()]
164
 
165
  if not urls:
166
  return "لطفاً حداقل یک لینک فایل صوتی معتبر وارد کنید.", None
167
 
168
  if len(urls) < 2:
169
+ return "برای ادغام، لطفاً حداقل دو لینک فایل صوتی وارد کنید.", None
170
 
171
  status_message = ""
172
  output_audio_path = None
173
  downloaded_files_paths = []
174
 
175
+ # استفاده از NamedTemporaryFile برای دایرکتوری موقت
176
+ # این دایرکتوری به طور خودکار پس از خروج از context manager حذف می‌شود.
177
+ with tempfile.TemporaryDirectory() as request_temp_dir:
178
+ print(f"Using temporary directory: {request_temp_dir}")
179
+ try:
180
+ for i, url in enumerate(urls):
181
+ status_message += f"در حال دانلود فایل {i+1} از: {url}\n"
182
+ gr.Info(f"Downloading file {i+1} of {len(urls)}...")
183
+ downloaded_path = download_audio(url, request_temp_dir)
184
+ if downloaded_path:
185
+ downloaded_files_paths.append(downloaded_path)
186
+ else:
187
+ return f"خطا در دانلود فایل {i+1} از '{url}'. لطفاً لینک را بررسی کنید. ممکن است فایل صوتی نباشد یا دسترسی به آن ممکن نباشد.", None
 
 
188
 
189
+ if not downloaded_files_paths:
190
+ return "هیچ فایلی با موفقیت دانلود نشد. لطفاً لینک‌ها را بررسی کنید.", None
191
+
192
+ # ترتیب دهی فایل ها بر اساس نام (برای تضمین ترتیب ادغام)
193
+ downloaded_files_paths.sort()
194
 
195
+ status_message += "فایل‌ها با موفقیت دانلود شدند. در حال ادغام...\n"
196
+ gr.Info("Files downloaded. Merging audio segments...")
197
 
198
+ output_file_name_final = f"merged_output_{uuid.uuid4().hex}.mp3"
199
+ output_file_path_final = os.path.join(request_temp_dir, output_file_name_final)
200
 
201
+ if merge_audio_files_sequential(downloaded_files_paths, output_file_path_final):
202
+ status_message += f"فایل‌ها با موفقیت ادغام شدند. فایل خروجی: {output_file_path_final}\n"
203
+ output_audio_path = output_file_path_final
204
+ else:
205
+ status_message += "خطا در ادغام فایل‌ها. (جزئیات خطا در کنسول سرور)\n"
206
 
207
+ except Exception as e:
208
+ status_message += f"خطای پیش‌بینی نشده در طول پردازش: {e}\n"
209
+ print(f"Unhandled exception in process_audio_links_gradio: {e}")
210
+ finally:
211
+ # tempfile.TemporaryDirectory به صورت خودکار دایرکتوری را پاک می‌کند.
212
+ # فقط باید مطمئن شویم که Gradio به فایل `output_audio_path` اشاره می‌کند
213
+ # و gradigo خودش آن فایل را برای دانلود در اختیار قرار می‌دهد.
214
+ print(f"Temporary directory {request_temp_dir} will be removed.")
215
 
216
  return status_message, output_audio_path
217
 
218
  # تعریف رابط Gradio
219
+ # (بخش interface بدون تغییرات عمده)
 
220
  iface = gr.Interface(
221
  fn=process_audio_links_gradio,
222
  inputs=[
223
  gr.Textbox(
224
  label="لینک‌های فایل صوتی (MP3, WAV و غیره) - هر لینک در یک خط یا با کاما جدا کنید",
225
+ placeholder="مثال:\nhttps://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3\nhttps://file-examples.com/storage/fe/2017/11/file_example_WAV_1MG.wav\n...",
226
  lines=5
227
  ),
228
  ],
 
233
  title="🎙️ ادغام کننده چندین فایل صوتی از طریق لینک (MP3, WAV و غیره) 🎵",
234
  description="لینک فایل‌های صوتی خود را (هر لینک در یک خط جدید یا با کاما جدا شده) در کادر زیر وارد کنید تا به صورت یک فایل **MP3** ادغام شوند.",
235
  allow_flagging="never",
236
+ theme=gr.themes.Soft(),
237
  css="""
238
  body { font-family: 'Vazirmatn', sans-serif; direction: rtl; text-align: right; }
239
  h1, h2, h3, h4, h5, h6 { text-align: center; }
 
246
  border-radius: 8px;
247
  }
248
  .gr-button {
249
+ background-color: #4CAF50;
250
  color: white;
251
  }
252
  """
253
  )
254
 
 
255
  if __name__ == "__main__":
256
  iface.launch(share=True)