Politrees commited on
Commit
0b79a93
·
verified ·
1 Parent(s): aa724a9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +396 -353
app.py CHANGED
@@ -6,13 +6,9 @@ import subprocess
6
  import gradio as gr
7
  from tqdm import tqdm
8
  from datetime import datetime
9
- from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
10
- from threading import Thread
11
  import multiprocessing
12
  from PIL import Image
13
- import shutil
14
- import platform
15
- import json
16
 
17
  # ----------------------- Internationalization -----------------------
18
  from i18n_local import en, ru, es, fr, de, it, ja, ko, ar, hi, tr
@@ -21,166 +17,365 @@ i18n = gr.I18n(
21
  ja=ja, ko=ko, ar=ar, hi=hi, tr=tr
22
  )
23
 
24
- # ----------------------- Performance Settings -----------------------
25
- MAX_WORKERS = multiprocessing.cpu_count()
26
- CHUNK_SIZE = 1024 * 1024 * 10 # 10MB chunks for streaming
27
-
28
- # ----------------------- Hardware Acceleration Detection -----------------------
29
- def detect_hardware_acceleration():
30
- """Determines the available methods for hardware acceleration."""
31
- accelerations = {
32
- 'nvidia': False,
33
- 'intel': False,
34
- 'amd': False,
35
- 'videotoolbox': False # macOS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  }
 
37
 
 
 
 
38
  try:
39
- result = subprocess.run(['ffmpeg', '-hide_banner', '-hwaccels'],
40
- capture_output=True, text=True)
41
- output = result.stdout.lower()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- if 'cuda' in output or 'nvenc' in output:
44
- accelerations['nvidia'] = True
45
- if 'qsv' in output or 'vaapi' in output:
46
- accelerations['intel'] = True
47
- if 'amf' in output:
48
- accelerations['amd'] = True
49
- if 'videotoolbox' in output:
50
- accelerations['videotoolbox'] = True
51
- except:
52
- pass
53
-
54
- return accelerations
55
-
56
- HARDWARE_ACCEL = detect_hardware_acceleration()
57
-
58
- # ----------------------- FFmpeg Optimized Utils -----------------------
59
- def get_optimal_ffmpeg_params(input_file, output_file, conversion_type="audio", use_hw=True):
60
- """Returns the optimal FFmpeg parameters for conversion."""
61
  params = []
62
 
63
- # Basic parameters for acceleration
64
- params.extend(['-hide_banner', '-y', '-loglevel', 'error', '-stats'])
65
-
66
- # Hardware acceleration (if available and enabled)
67
- if use_hw and conversion_type == "video":
68
- if HARDWARE_ACCEL['nvidia']:
69
- params.extend(['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'])
70
- elif HARDWARE_ACCEL['intel']:
71
- params.extend(['-hwaccel', 'qsv'])
72
- elif HARDWARE_ACCEL['videotoolbox'] and platform.system() == 'Darwin':
73
- params.extend(['-hwaccel', 'videotoolbox'])
74
-
75
  params.extend(['-i', input_file])
76
 
77
- # Optimization by Conversion Type
 
78
  if conversion_type == "audio":
79
- ext = os.path.splitext(output_file)[1].lower()[1:]
 
80
  if ext == 'mp3':
81
- params.extend(['-codec:a', 'libmp3lame', '-q:a', '2', '-threads', str(MAX_WORKERS)])
82
  elif ext == 'aac' or ext == 'm4a':
83
- params.extend(['-codec:a', 'aac', '-b:a', '192k', '-threads', str(MAX_WORKERS)])
84
  elif ext == 'opus':
85
- params.extend(['-codec:a', 'libopus', '-b:a', '128k', '-threads', str(MAX_WORKERS)])
 
 
 
 
 
 
 
86
  elif ext == 'flac':
87
- params.extend(['-codec:a', 'flac', '-compression_level', '5', '-threads', str(MAX_WORKERS)])
88
- else:
89
- params.extend(['-threads', str(MAX_WORKERS)])
 
 
 
 
 
 
90
 
91
  elif conversion_type == "video":
92
- ext = os.path.splitext(output_file)[1].lower()[1:]
93
-
94
- # Use of hardware codecs
95
- if use_hw and HARDWARE_ACCEL['nvidia']:
96
- params.extend(['-c:v', 'h264_nvenc', '-preset', 'p4', '-tune', 'hq', '-rc', 'vbr', '-cq', '23'])
97
- elif use_hw and HARDWARE_ACCEL['intel']:
98
- params.extend(['-c:v', 'h264_qsv', '-preset', 'faster', '-global_quality', '23'])
99
- elif use_hw and HARDWARE_ACCEL['videotoolbox'] and platform.system() == 'Darwin':
100
- params.extend(['-c:v', 'h264_videotoolbox', '-b:v', '5000k'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  else:
102
- # CPU optimization
103
- params.extend(['-c:v', 'libx264', '-preset', 'faster', '-crf', '23', '-threads', str(MAX_WORKERS)])
 
 
104
 
105
- # Copy audio without transcoding (faster)
106
- params.extend(['-c:a', 'copy'])
107
 
108
  elif conversion_type == "video_to_audio":
109
- # Extracting only the audio track
110
- params.extend(['-vn']) # Turn off video
111
- ext = os.path.splitext(output_file)[1].lower()[1:]
 
112
  if ext == 'mp3':
113
  params.extend(['-codec:a', 'libmp3lame', '-q:a', '2'])
114
  elif ext == 'aac' or ext == 'm4a':
115
  params.extend(['-codec:a', 'aac', '-b:a', '192k'])
 
 
 
 
 
 
 
 
 
 
116
  else:
117
- params.extend(['-codec:a', 'copy']) # Try copying without transcoding
 
 
 
 
118
 
119
  params.extend(['-threads', str(MAX_WORKERS)])
120
 
121
- # Оптимизация для больших файлов
122
- params.extend(['-max_muxing_queue_size', '9999'])
123
-
124
  params.append(output_file)
125
  return params
126
 
127
- def run_ffmpeg_with_progress(params, duration=None):
128
- """Launches FFmpeg with a progress indicator."""
129
- try:
130
- process = subprocess.Popen(
131
- ['ffmpeg'] + params,
132
- stdout=subprocess.PIPE,
133
- stderr=subprocess.PIPE,
134
- universal_newlines=True
135
- )
136
-
137
- for line in process.stderr:
138
- if 'time=' in line:
139
- print('.', end='', flush=True)
140
-
141
- process.wait()
142
- if process.returncode != 0:
143
- raise Exception(f"FFmpeg failed with return code {process.returncode}")
144
-
145
- return True
146
- except Exception as e:
147
- print(f"FFmpeg error: {e}")
148
- return False
149
-
150
- # ----------------------- Optimized Conversion Functions -----------------------
151
  def convert_audio_ffmpeg(input_file, output_file, output_ext):
152
- """Fast audio conversion using a direct FFmpeg call."""
 
 
 
 
 
 
 
 
153
  params = get_optimal_ffmpeg_params(input_file, output_file, "audio")
154
  return run_ffmpeg_with_progress(params)
155
 
156
  def convert_video_ffmpeg(input_file, output_file, conversion_type):
157
- """Fast video conversion using a direct FFmpeg call."""
158
  conv_type = "video_to_audio" if conversion_type == "Video to Audio" else "video"
 
 
 
 
 
 
 
 
 
159
  params = get_optimal_ffmpeg_params(input_file, output_file, conv_type)
 
 
160
  return run_ffmpeg_with_progress(params)
161
 
162
  def merge_audio_files_ffmpeg(input_files, output_file, gap_duration):
163
- """Efficiently merge audio files using FFmpeg."""
164
- list_file = f"concat_list_{uuid.uuid4().hex}.txt"
165
 
166
  try:
167
  with open(list_file, 'w') as f:
168
- for i, file in enumerate(input_files):
169
  f.write(f"file '{os.path.abspath(file)}'\n")
170
- if i < len(input_files) - 1 and gap_duration > 0:
171
- f.write(f"file 'silence.wav'\n")
172
-
173
- # Create a silent file if needed
174
- if gap_duration > 0:
175
- silence_params = [
176
- '-f', 'lavfi',
177
- '-i', f'anullsrc=duration={gap_duration/1000}:sample_rate=44100',
178
- '-t', str(gap_duration/1000),
179
- 'silence.wav'
180
- ]
181
- subprocess.run(['ffmpeg', '-y'] + silence_params, check=True, capture_output=True)
182
 
183
- # We are merging files
184
  params = [
185
  '-f', 'concat',
186
  '-safe', '0',
@@ -191,80 +386,81 @@ def merge_audio_files_ffmpeg(input_files, output_file, gap_duration):
191
  ]
192
 
193
  return run_ffmpeg_with_progress(params)
194
-
195
  finally:
196
  if os.path.exists(list_file):
197
  os.remove(list_file)
198
- if os.path.exists('silence.wav'):
199
- os.remove('silence.wav')
200
 
201
- # ----------------------- Parallel Processing -----------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  def process_file_parallel(args):
203
- """Function for parallel file processing."""
204
  input_file, output_path, file_type, output_ext = args
205
 
206
  try:
207
  if file_type == "audio":
208
  success = convert_audio_ffmpeg(input_file, output_path, output_ext)
209
  elif file_type == "image":
210
- success = convert_image_optimized(input_file, output_path, output_ext)
211
  else:
212
  success = False
213
-
214
  return output_path if success else None
215
  except Exception as e:
216
  print(f"Error processing {input_file}: {e}")
217
  return None
218
 
219
- def convert_image_optimized(input_file, output_path, output_ext):
220
- """Optimized image conversion"""
221
- try:
222
- pil_fmt = pil_format_for_ext(output_ext)
223
- if not pil_fmt:
224
- return False
225
-
226
- with Image.open(input_file) as img:
227
- if pil_fmt.upper() == "JPEG":
228
- img = img.convert("RGB")
229
- img.save(output_path, format=pil_fmt, quality=90, optimize=True)
230
- elif pil_fmt.upper() == "PNG":
231
- img.save(output_path, format=pil_fmt, optimize=True, compress_level=6)
232
- else:
233
- img.save(output_path, format=pil_fmt)
234
-
235
- return True
236
- except Exception as e:
237
- print(f"Image conversion error: {e}")
238
- return False
239
 
240
  # ----------------------- Main Processing Functions -----------------------
241
- def process_audio_files_optimized(files, output_ext, merge_files, gap_duration, progress=gr.Progress(track_tqdm=True)):
242
- """Optimized audio file processing"""
243
  if not files:
244
  raise gr.Error("Please upload at least one audio file!")
245
 
246
- session_id = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + "_" + str(uuid.uuid4())[:8]
247
  os.makedirs(session_id, exist_ok=True)
248
 
249
- print(f"\nStarting optimized audio session: {session_id}")
250
  print(f"Files to convert: {len(files)} to .{output_ext}")
 
251
 
252
  file_paths = [f if isinstance(f, str) else f.name for f in files]
253
 
254
  if merge_files:
255
- merged_output_path = os.path.join(session_id, f"merged_output.{output_ext}")
256
 
257
  temp_files = []
258
- with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
259
- tasks = []
260
- for i, file_path in enumerate(file_paths):
261
- temp_output = os.path.join(session_id, f"temp_{i}.{output_ext}")
262
- tasks.append(executor.submit(convert_audio_ffmpeg, file_path, temp_output, output_ext))
263
-
264
- for future in tqdm(tasks, desc="Converting files"):
265
- result = future.result()
266
- if result:
267
- temp_files.append(result)
268
 
269
  if merge_audio_files_ffmpeg(temp_files, merged_output_path, gap_duration):
270
  for temp_file in temp_files:
@@ -283,14 +479,13 @@ def process_audio_files_optimized(files, output_ext, merge_files, gap_duration,
283
  tasks = []
284
  for file_path in file_paths:
285
  base_name = os.path.splitext(os.path.basename(file_path))[0]
286
- output_filename = f"{base_name}.{output_ext}"
287
- output_path = os.path.join(session_id, output_filename)
288
  tasks.append((file_path, output_path, "audio", output_ext))
289
 
290
  results = list(tqdm(
291
  executor.map(process_file_parallel, tasks),
292
  total=len(tasks),
293
- desc="Converting audio files"
294
  ))
295
 
296
  output_files = [r for r in results if r is not None]
@@ -299,38 +494,36 @@ def process_audio_files_optimized(files, output_ext, merge_files, gap_duration,
299
  raise gr.Error("No files were successfully converted")
300
 
301
  if len(output_files) > 1:
302
- print("Creating ZIP archive...")
303
- zip_filename = create_zip_optimized(output_files, session_id)
304
- return zip_filename
305
 
306
  return output_files[0]
307
 
308
- def process_image_files_optimized(files, output_ext, progress=gr.Progress(track_tqdm=True)):
309
- """Optimized image processing"""
310
  if not files:
311
  raise gr.Error("Please upload at least one image!")
312
 
313
- session_id = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + "_" + str(uuid.uuid4())[:8]
314
  os.makedirs(session_id, exist_ok=True)
315
 
316
- print(f"\nStarting optimized image session: {session_id}")
317
  print(f"Files to convert: {len(files)} to .{output_ext}")
 
318
 
319
  file_paths = [f if isinstance(f, str) else f.name for f in files]
320
  output_files = []
321
-
322
  with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
323
  tasks = []
324
  for file_path in file_paths:
325
  base_name = os.path.splitext(os.path.basename(file_path))[0]
326
- output_filename = f"{base_name}.{output_ext}"
327
- output_path = os.path.join(session_id, output_filename)
328
  tasks.append((file_path, output_path, "image", output_ext))
329
 
330
  results = list(tqdm(
331
  executor.map(process_file_parallel, tasks),
332
  total=len(tasks),
333
- desc="Converting images"
334
  ))
335
 
336
  output_files = [r for r in results if r is not None]
@@ -339,26 +532,23 @@ def process_image_files_optimized(files, output_ext, progress=gr.Progress(track_
339
  raise gr.Error("No images were successfully converted")
340
 
341
  if len(output_files) > 1:
342
- print("Creating ZIP archive...")
343
- zip_filename = create_zip_optimized(output_files, session_id)
344
- return zip_filename
345
 
346
  return output_files[0]
347
 
348
- def process_video_optimized(input_video, conversion_type, output_ext, progress=gr.Progress(track_tqdm=True)):
349
- """Optimized video processing"""
350
  if not input_video:
351
  raise gr.Error("Please upload a video file!")
352
 
353
- session_id = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + "_" + str(uuid.uuid4())[:8]
354
  os.makedirs(session_id, exist_ok=True)
355
 
356
  input_path = input_video if isinstance(input_video, str) else input_video.name
357
  base_name = os.path.splitext(os.path.basename(input_path))[0]
358
- output_filename = f"{base_name}_converted.{output_ext}"
359
- output_path = os.path.join(session_id, output_filename)
360
 
361
- print(f"\nStarting optimized video session: {session_id}")
362
  print(f"Conversion type: {conversion_type}, Output: .{output_ext}")
363
 
364
  success = convert_video_ffmpeg(input_path, output_path, conversion_type)
@@ -367,171 +557,10 @@ def process_video_optimized(input_video, conversion_type, output_ext, progress=g
367
  print("Video processing complete!")
368
  return output_path
369
  else:
370
- raise gr.Error("Video processing failed")
371
-
372
- def create_zip_optimized(files_to_zip, session_id):
373
- """Optimized ZIP archive creation"""
374
- zip_filename = f"{session_id}.zip"
375
-
376
- with zipfile.ZipFile(zip_filename, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=1) as zipf:
377
- for file in tqdm(files_to_zip, desc="Creating ZIP archive"):
378
- zipf.write(file, os.path.basename(file))
379
-
380
- return zip_filename
381
-
382
- # ----------------------- Keep original helper functions -----------------------
383
- def _run_ffmpeg(args):
384
- try:
385
- res = subprocess.run(["ffmpeg", *args], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
386
- return res.stdout
387
- except Exception:
388
- return None
389
-
390
- def ffmpeg_writable_formats():
391
- out = _run_ffmpeg(["-hide_banner", "-v", "error", "-formats"])
392
- if not out:
393
- return set()
394
- fmts = set()
395
- for line in out.splitlines():
396
- if re.match(r"^\s*[D\s]*E\s+", line):
397
- m = re.search(r"^\s*[D\s]*E\s+([^\s]+)", line)
398
- if not m:
399
- continue
400
- names = m.group(1)
401
- for name in names.split(","):
402
- fmts.add(name.strip())
403
- return fmts
404
-
405
- def ffmpeg_audio_encoders():
406
- out = _run_ffmpeg(["-hide_banner", "-v", "error", "-encoders"])
407
- if not out:
408
- return set()
409
- enc = set()
410
- for line in out.splitlines():
411
- m = re.match(r"^\s*A\S*\s+([^\s]+)", line)
412
- if m:
413
- enc.add(m.group(1).strip())
414
- return enc
415
-
416
- # Extension mappings
417
- AUDIO_EXT_TO_FFMPEG_FORMAT = {
418
- "mp3": "mp3",
419
- "wav": "wav",
420
- "w64": "w64",
421
- "flac": "flac",
422
- "ogg": "ogg",
423
- "oga": "ogg",
424
- "opus": "ogg",
425
- "spx": "ogg",
426
- "aac": "adts",
427
- "m4a": "mp4",
428
- "m4b": "mp4",
429
- "m4r": "mp4",
430
- "ac3": "ac3",
431
- "aiff": "aiff",
432
- "aif": "aiff",
433
- "aifc": "aiff",
434
- "caf": "caf",
435
- "au": "au",
436
- "amr": "amr",
437
- "dts": "dts",
438
- "mp2": "mp2",
439
- "wma": "asf",
440
- "wv": "wv",
441
- "mka": "matroska",
442
- }
443
-
444
- AUDIO_REQUIRED_CODECS = {
445
- "mp3": ["libmp3lame"],
446
- "opus": ["libopus"],
447
- "spx": ["libspeex"],
448
- }
449
-
450
- VIDEO_EXT_TO_FFMPEG_FORMAT = {
451
- "mp4": "mp4",
452
- "m4v": "mp4",
453
- "mov": "mov",
454
- "avi": "avi",
455
- "mkv": "matroska",
456
- "webm": "webm",
457
- "flv": "flv",
458
- "ogv": "ogg",
459
- "mpeg": "mpeg",
460
- "mpg": "mpeg",
461
- "ts": "mpegts",
462
- "m2ts": "mpegts",
463
- "mxf": "mxf",
464
- "3gp": "3gp",
465
- "3g2": "3g2",
466
- "asf": "asf",
467
- "wmv": "asf",
468
- "vob": "vob",
469
- }
470
-
471
- def available_audio_extensions():
472
- writable = ffmpeg_writable_formats()
473
- encoders = ffmpeg_audio_encoders()
474
- exts = []
475
- for ext, ffmt in AUDIO_EXT_TO_FFMPEG_FORMAT.items():
476
- if ffmt not in writable:
477
- continue
478
- req = AUDIO_REQUIRED_CODECS.get(ext)
479
- if req and not any(r in encoders for r in req):
480
- continue
481
- exts.append(ext)
482
- if not exts:
483
- exts = ["mp3", "wav", "flac", "ogg", "aac", "m4a", "aiff", "wma", "opus"]
484
- return sorted(set(exts))
485
-
486
- def available_video_extensions():
487
- writable = ffmpeg_writable_formats()
488
- exts = [ext for ext, ffmt in VIDEO_EXT_TO_FFMPEG_FORMAT.items() if ffmt in writable]
489
- if not exts:
490
- exts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "mpeg", "mpg", "ts"]
491
- return sorted(set(exts))
492
-
493
- def available_image_extensions():
494
- ext2fmt = Image.registered_extensions()
495
- save_ok = set(getattr(Image, "SAVE", {}).keys()) or set()
496
- if not save_ok:
497
- save_ok = set(ext2fmt.values())
498
- exts = []
499
- for ext, fmt in ext2fmt.items():
500
- if fmt in save_ok:
501
- e = ext.lstrip(".").lower()
502
- exts.append(e)
503
- if not exts:
504
- exts = ["png", "jpg", "jpeg", "webp", "bmp", "tiff", "gif", "ico", "ppm", "pgm", "pbm", "pnm", "tga", "xbm", "xpm", "pdf", "eps"]
505
- return sorted(set(exts))
506
-
507
- def pil_format_for_ext(ext):
508
- ext = ext.lower().strip(".")
509
- for k, v in Image.registered_extensions().items():
510
- if k.lstrip(".").lower() == ext:
511
- return v
512
- fallback = {
513
- "jpg": "JPEG",
514
- "jpeg": "JPEG",
515
- "png": "PNG",
516
- "webp": "WEBP",
517
- "bmp": "BMP",
518
- "tiff": "TIFF",
519
- "tif": "TIFF",
520
- "gif": "GIF",
521
- "ico": "ICO",
522
- "ppm": "PPM",
523
- "pgm": "PPM",
524
- "pbm": "PPM",
525
- "pnm": "PPM",
526
- "tga": "TGA",
527
- "xbm": "XBM",
528
- "xpm": "XPM",
529
- "pdf": "PDF",
530
- "eps": "EPS",
531
- }
532
- return fallback.get(ext, None)
533
 
534
  def update_format_choices(conversion_type):
 
535
  if conversion_type == "Video to Video":
536
  vf = available_video_extensions()
537
  value = "mp4" if "mp4" in vf else (vf[0] if vf else None)
@@ -541,13 +570,20 @@ def update_format_choices(conversion_type):
541
  value = "mp3" if "mp3" in af else (af[0] if af else None)
542
  return gr.Dropdown(choices=af, value=value, label="Output Audio Format")
543
 
544
- # ----------------------- UI with optimized handlers -----------------------
545
  AUDIO_FORMATS = available_audio_extensions()
546
  VIDEO_FORMATS = available_video_extensions()
547
  IMAGE_FORMATS = available_image_extensions()
548
 
549
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
550
  gr.HTML(i18n("title_app"))
 
 
 
 
 
 
 
551
 
552
  with gr.Tabs():
553
  # AUDIO TAB
@@ -561,13 +597,19 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
561
  default_audio = "mp3" if "mp3" in AUDIO_FORMATS else (AUDIO_FORMATS[0] if AUDIO_FORMATS else None)
562
  audio_format_choice = gr.Dropdown(choices=AUDIO_FORMATS, label=i18n("label_audio_format_choice"), value=default_audio)
563
  merge_files_checkbox = gr.Checkbox(label=i18n("label_merge_files_checkbox"))
564
- gap_slider = gr.Slider(minimum=0, maximum=5000, step=100, value=500, label=i18n("label_gap_slider"))
565
 
566
  audio_submit_button = gr.Button(i18n("сonvert"), variant="primary")
567
  audio_output_file = gr.File(label=i18n("download_result"))
 
 
 
 
 
 
568
 
569
  audio_submit_button.click(
570
- fn=process_audio_files_optimized,
571
  inputs=[audio_file_input, audio_format_choice, merge_files_checkbox, gap_slider],
572
  outputs=audio_output_file
573
  )
@@ -587,7 +629,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
587
  image_output_file = gr.File(label=i18n("download_result"))
588
 
589
  image_submit_button.click(
590
- fn=process_image_files_optimized,
591
  inputs=[image_file_input, image_format_choice],
592
  outputs=image_output_file
593
  )
@@ -622,10 +664,11 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
622
  )
623
 
624
  video_submit_button.click(
625
- fn=process_video_optimized,
626
  inputs=[video_input, conversion_type_radio, video_format_dropdown],
627
  outputs=video_output_file
628
  )
629
 
630
  if __name__ == "__main__":
 
631
  demo.queue().launch(i18n=i18n, debug=True, show_error=True)
 
6
  import gradio as gr
7
  from tqdm import tqdm
8
  from datetime import datetime
9
+ from concurrent.futures import ThreadPoolExecutor
 
10
  import multiprocessing
11
  from PIL import Image
 
 
 
12
 
13
  # ----------------------- Internationalization -----------------------
14
  from i18n_local import en, ru, es, fr, de, it, ja, ko, ar, hi, tr
 
17
  ja=ja, ko=ko, ar=ar, hi=hi, tr=tr
18
  )
19
 
20
+ # ----------------------- HuggingFace Spaces Settings -----------------------
21
+ MAX_WORKERS = min(2, multiprocessing.cpu_count()) # Limited CPU on HF Spaces
22
+ CHUNK_SIZE = 1024 * 1024 * 5 # 5MB chunks
23
+
24
+ def check_ffmpeg_installation():
25
+ """Check FFmpeg installation and available codecs"""
26
+ try:
27
+ result = subprocess.run(
28
+ ['ffmpeg', '-version'],
29
+ capture_output=True,
30
+ text=True,
31
+ timeout=5
32
+ )
33
+ print("FFmpeg version info:")
34
+ print(result.stdout.split('\n')[0])
35
+
36
+ result = subprocess.run(
37
+ ['ffmpeg', '-hide_banner', '-codecs'],
38
+ capture_output=True,
39
+ text=True,
40
+ timeout=5
41
+ )
42
+
43
+ important_codecs = ['libx264', 'libmp3lame', 'aac', 'libvpx-vp9', 'libopus']
44
+ available = []
45
+ for codec in important_codecs:
46
+ if codec in result.stdout:
47
+ available.append(codec)
48
+
49
+ print(f"Available important codecs: {', '.join(available)}")
50
+ return True
51
+
52
+ except Exception as e:
53
+ print(f"FFmpeg check failed: {e}")
54
+ return False
55
+
56
+ # ----------------------- FFmpeg utils -----------------------
57
+ def _run_ffmpeg(args):
58
+ try:
59
+ res = subprocess.run(["ffmpeg", *args], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
60
+ return res.stdout
61
+ except Exception:
62
+ return None
63
+
64
+ def ffmpeg_writable_formats():
65
+ """Returns a set of FFmpeg format names (including aliases) available for writing (E flag)."""
66
+ out = _run_ffmpeg(["-hide_banner", "-v", "error", "-formats"])
67
+ if not out:
68
+ return set()
69
+ fmts = set()
70
+ for line in out.splitlines():
71
+ if re.match(r"^\s*[D\s]*E\s+", line):
72
+ m = re.search(r"^\s*[D\s]*E\s+([^\s]+)", line)
73
+ if not m:
74
+ continue
75
+ names = m.group(1)
76
+ for name in names.split(","):
77
+ fmts.add(name.strip())
78
+ return fmts
79
+
80
+ def ffmpeg_audio_encoders():
81
+ """Returns a set of available audio encoders."""
82
+ out = _run_ffmpeg(["-hide_banner", "-v", "error", "-encoders"])
83
+ if not out:
84
+ return set()
85
+ enc = set()
86
+ for line in out.splitlines():
87
+ m = re.match(r"^\s*A\S*\s+([^\s]+)", line)
88
+ if m:
89
+ enc.add(m.group(1).strip())
90
+ return enc
91
+
92
+ # Extension -> FFmpeg container mapping (full list)
93
+ AUDIO_EXT_TO_FFMPEG_FORMAT = {
94
+ "mp3": "mp3",
95
+ "wav": "wav",
96
+ "w64": "w64",
97
+ "flac": "flac",
98
+ "ogg": "ogg",
99
+ "oga": "ogg",
100
+ "opus": "ogg",
101
+ "spx": "ogg",
102
+ "aac": "adts",
103
+ "m4a": "mp4",
104
+ "m4b": "mp4",
105
+ "m4r": "mp4",
106
+ "ac3": "ac3",
107
+ "aiff": "aiff",
108
+ "aif": "aiff",
109
+ "aifc": "aiff",
110
+ "caf": "caf",
111
+ "au": "au",
112
+ "amr": "amr",
113
+ "dts": "dts",
114
+ "mp2": "mp2",
115
+ "wma": "asf",
116
+ "wv": "wv",
117
+ "mka": "matroska",
118
+ }
119
+
120
+ AUDIO_REQUIRED_CODECS = {
121
+ "mp3": ["libmp3lame"],
122
+ "opus": ["libopus"],
123
+ "spx": ["libspeex"],
124
+ }
125
+
126
+ VIDEO_EXT_TO_FFMPEG_FORMAT = {
127
+ "mp4": "mp4",
128
+ "m4v": "mp4",
129
+ "mov": "mov",
130
+ "avi": "avi",
131
+ "mkv": "matroska",
132
+ "webm": "webm",
133
+ "flv": "flv",
134
+ "ogv": "ogg",
135
+ "mpeg": "mpeg",
136
+ "mpg": "mpeg",
137
+ "ts": "mpegts",
138
+ "m2ts": "mpegts",
139
+ "mxf": "mxf",
140
+ "3gp": "3gp",
141
+ "3g2": "3g2",
142
+ "asf": "asf",
143
+ "wmv": "asf",
144
+ "vob": "vob",
145
+ }
146
+
147
+ def available_audio_extensions():
148
+ writable = ffmpeg_writable_formats()
149
+ encoders = ffmpeg_audio_encoders()
150
+ exts = []
151
+ for ext, ffmt in AUDIO_EXT_TO_FFMPEG_FORMAT.items():
152
+ if ffmt not in writable:
153
+ continue
154
+ req = AUDIO_REQUIRED_CODECS.get(ext)
155
+ if req and not any(r in encoders for r in req):
156
+ continue
157
+ exts.append(ext)
158
+ if not exts:
159
+ exts = ["mp3", "wav", "flac", "ogg", "aac", "m4a", "aiff", "wma", "opus"]
160
+ return sorted(set(exts))
161
+
162
+ def available_video_extensions():
163
+ writable = ffmpeg_writable_formats()
164
+ exts = [ext for ext, ffmt in VIDEO_EXT_TO_FFMPEG_FORMAT.items() if ffmt in writable]
165
+ if not exts:
166
+ exts = ["mp4", "mkv", "avi", "mov", "webm", "flv", "mpeg", "mpg", "ts"]
167
+ return sorted(set(exts))
168
+
169
+ def available_image_extensions():
170
+ ext2fmt = Image.registered_extensions()
171
+ save_ok = set(getattr(Image, "SAVE", {}).keys()) or set()
172
+ if not save_ok:
173
+ save_ok = set(ext2fmt.values())
174
+ exts = []
175
+ for ext, fmt in ext2fmt.items():
176
+ if fmt in save_ok:
177
+ e = ext.lstrip(".").lower()
178
+ exts.append(e)
179
+ if not exts:
180
+ exts = ["png", "jpg", "jpeg", "webp", "bmp", "tiff", "gif", "ico", "ppm", "pgm", "pbm", "pnm", "tga", "xbm", "xpm", "pdf", "eps"]
181
+ return sorted(set(exts))
182
+
183
+ def pil_format_for_ext(ext):
184
+ ext = ext.lower().strip(".")
185
+ for k, v in Image.registered_extensions().items():
186
+ if k.lstrip(".").lower() == ext:
187
+ return v
188
+ fallback = {
189
+ "jpg": "JPEG",
190
+ "jpeg": "JPEG",
191
+ "png": "PNG",
192
+ "webp": "WEBP",
193
+ "bmp": "BMP",
194
+ "tiff": "TIFF",
195
+ "tif": "TIFF",
196
+ "gif": "GIF",
197
+ "ico": "ICO",
198
+ "ppm": "PPM",
199
+ "pgm": "PPM",
200
+ "pbm": "PPM",
201
+ "pnm": "PPM",
202
+ "tga": "TGA",
203
+ "xbm": "XBM",
204
+ "xpm": "XPM",
205
+ "pdf": "PDF",
206
+ "eps": "EPS",
207
  }
208
+ return fallback.get(ext, None)
209
 
210
+ # ----------------------- Optimized FFmpeg Processing -----------------------
211
+ def run_ffmpeg_with_progress(params):
212
+ """Run FFmpeg with progress display"""
213
  try:
214
+ process = subprocess.Popen(
215
+ ['ffmpeg'] + params,
216
+ stdout=subprocess.PIPE,
217
+ stderr=subprocess.STDOUT,
218
+ universal_newlines=True,
219
+ bufsize=1
220
+ )
221
+
222
+ output = []
223
+ for line in process.stdout:
224
+ output.append(line)
225
+ if 'time=' in line:
226
+ print('.', end='', flush=True)
227
+
228
+ process.wait()
229
+
230
+ if process.returncode != 0:
231
+ error_output = ''.join(output[-50:])
232
+ print(f"FFmpeg error output:\n{error_output}")
233
+ return False
234
 
235
+ return True
236
+
237
+ except Exception as e:
238
+ print(f"FFmpeg exception: {e}")
239
+ return False
240
+
241
+ def get_optimal_ffmpeg_params(input_file, output_file, conversion_type="audio"):
242
+ """Get optimal FFmpeg parameters for conversion"""
 
 
 
 
 
 
 
 
 
 
243
  params = []
244
 
245
+ params.extend(['-hide_banner', '-y'])
 
 
 
 
 
 
 
 
 
 
 
246
  params.extend(['-i', input_file])
247
 
248
+ ext = os.path.splitext(output_file)[1].lower()[1:]
249
+
250
  if conversion_type == "audio":
251
+ ff_format = AUDIO_EXT_TO_FFMPEG_FORMAT.get(ext, ext)
252
+
253
  if ext == 'mp3':
254
+ params.extend(['-codec:a', 'libmp3lame', '-q:a', '2'])
255
  elif ext == 'aac' or ext == 'm4a':
256
+ params.extend(['-codec:a', 'aac', '-b:a', '192k'])
257
  elif ext == 'opus':
258
+ encoders = ffmpeg_audio_encoders()
259
+ if 'libopus' in encoders:
260
+ params.extend(['-codec:a', 'libopus', '-b:a', '128k'])
261
+ else:
262
+ params.extend(['-codec:a', 'libvorbis', '-b:a', '128k'])
263
+ elif ext == 'spx':
264
+ if 'libspeex' in ffmpeg_audio_encoders():
265
+ params.extend(['-codec:a', 'libspeex'])
266
  elif ext == 'flac':
267
+ params.extend(['-codec:a', 'flac', '-compression_level', '5'])
268
+ elif ext == 'wav':
269
+ params.extend(['-codec:a', 'pcm_s16le'])
270
+
271
+ # Format specification if needed
272
+ if ff_format != ext:
273
+ params.extend(['-f', ff_format])
274
+
275
+ params.extend(['-threads', str(MAX_WORKERS)])
276
 
277
  elif conversion_type == "video":
278
+ if ext in ['mp4', 'm4v', 'mov']:
279
+ params.extend(['-codec:v', 'libx264'])
280
+ params.extend(['-preset', 'veryfast'])
281
+ params.extend(['-crf', '23'])
282
+ params.extend(['-codec:a', 'aac', '-b:a', '192k'])
283
+ params.extend(['-movflags', '+faststart'])
284
+ elif ext == 'webm':
285
+ params.extend(['-codec:v', 'libvpx-vp9'])
286
+ params.extend(['-crf', '30', '-b:v', '0'])
287
+ params.extend(['-codec:a', 'libopus', '-b:a', '128k'])
288
+ elif ext == 'avi':
289
+ params.extend(['-codec:v', 'libx264'])
290
+ params.extend(['-preset', 'veryfast'])
291
+ params.extend(['-crf', '23'])
292
+ params.extend(['-codec:a', 'mp3', '-b:a', '192k'])
293
+ elif ext == 'mkv':
294
+ params.extend(['-codec:v', 'libx264'])
295
+ params.extend(['-preset', 'veryfast'])
296
+ params.extend(['-crf', '23'])
297
+ params.extend(['-codec:a', 'aac', '-b:a', '192k'])
298
+ elif ext == 'flv':
299
+ params.extend(['-codec:v', 'flv1'])
300
+ params.extend(['-codec:a', 'mp3', '-ar', '44100'])
301
  else:
302
+ params.extend(['-codec:v', 'libx264'])
303
+ params.extend(['-preset', 'veryfast'])
304
+ params.extend(['-crf', '23'])
305
+ params.extend(['-codec:a', 'copy'])
306
 
307
+ params.extend(['-threads', str(MAX_WORKERS)])
 
308
 
309
  elif conversion_type == "video_to_audio":
310
+ params.extend(['-vn'])
311
+
312
+ ff_format = AUDIO_EXT_TO_FFMPEG_FORMAT.get(ext, ext)
313
+
314
  if ext == 'mp3':
315
  params.extend(['-codec:a', 'libmp3lame', '-q:a', '2'])
316
  elif ext == 'aac' or ext == 'm4a':
317
  params.extend(['-codec:a', 'aac', '-b:a', '192k'])
318
+ elif ext == 'flac':
319
+ params.extend(['-codec:a', 'flac', '-compression_level', '5'])
320
+ elif ext == 'wav':
321
+ params.extend(['-codec:a', 'pcm_s16le'])
322
+ elif ext == 'opus':
323
+ encoders = ffmpeg_audio_encoders()
324
+ if 'libopus' in encoders:
325
+ params.extend(['-codec:a', 'libopus', '-b:a', '128k'])
326
+ else:
327
+ params.extend(['-codec:a', 'libvorbis', '-b:a', '128k'])
328
  else:
329
+ params.extend(['-codec:a', 'copy'])
330
+
331
+ # Format specification if needed
332
+ if ff_format != ext:
333
+ params.extend(['-f', ff_format])
334
 
335
  params.extend(['-threads', str(MAX_WORKERS)])
336
 
 
 
 
337
  params.append(output_file)
338
  return params
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  def convert_audio_ffmpeg(input_file, output_file, output_ext):
341
+ """Fast audio conversion via direct FFmpeg call"""
342
+ if not os.path.exists(input_file):
343
+ print(f"Input file does not exist: {input_file}")
344
+ return False
345
+
346
+ output_dir = os.path.dirname(output_file)
347
+ if output_dir and not os.path.exists(output_dir):
348
+ os.makedirs(output_dir, exist_ok=True)
349
+
350
  params = get_optimal_ffmpeg_params(input_file, output_file, "audio")
351
  return run_ffmpeg_with_progress(params)
352
 
353
  def convert_video_ffmpeg(input_file, output_file, conversion_type):
354
+ """Fast video conversion via direct FFmpeg call"""
355
  conv_type = "video_to_audio" if conversion_type == "Video to Audio" else "video"
356
+
357
+ if not os.path.exists(input_file):
358
+ print(f"Input file does not exist: {input_file}")
359
+ return False
360
+
361
+ output_dir = os.path.dirname(output_file)
362
+ if output_dir and not os.path.exists(output_dir):
363
+ os.makedirs(output_dir, exist_ok=True)
364
+
365
  params = get_optimal_ffmpeg_params(input_file, output_file, conv_type)
366
+ print(f"FFmpeg command: ffmpeg {' '.join(params)}")
367
+
368
  return run_ffmpeg_with_progress(params)
369
 
370
  def merge_audio_files_ffmpeg(input_files, output_file, gap_duration):
371
+ """Fast audio merging via FFmpeg"""
372
+ list_file = f"concat_{uuid.uuid4().hex}.txt"
373
 
374
  try:
375
  with open(list_file, 'w') as f:
376
+ for file in input_files:
377
  f.write(f"file '{os.path.abspath(file)}'\n")
 
 
 
 
 
 
 
 
 
 
 
 
378
 
 
379
  params = [
380
  '-f', 'concat',
381
  '-safe', '0',
 
386
  ]
387
 
388
  return run_ffmpeg_with_progress(params)
 
389
  finally:
390
  if os.path.exists(list_file):
391
  os.remove(list_file)
 
 
392
 
393
+ def convert_image_pillow(input_file, output_path, output_ext):
394
+ """Optimized image conversion"""
395
+ try:
396
+ pil_format = pil_format_for_ext(output_ext)
397
+ if not pil_format:
398
+ return False
399
+
400
+ with Image.open(input_file) as img:
401
+ if pil_format == "JPEG":
402
+ img = img.convert("RGB")
403
+ img.save(output_path, format=pil_format, quality=90, optimize=True)
404
+ elif pil_format == "PNG":
405
+ img.save(output_path, format=pil_format, optimize=True, compress_level=6)
406
+ else:
407
+ img.save(output_path, format=pil_format)
408
+
409
+ return True
410
+ except Exception as e:
411
+ print(f"Image conversion error: {e}")
412
+ return False
413
+
414
  def process_file_parallel(args):
415
+ """Function for parallel file processing"""
416
  input_file, output_path, file_type, output_ext = args
417
 
418
  try:
419
  if file_type == "audio":
420
  success = convert_audio_ffmpeg(input_file, output_path, output_ext)
421
  elif file_type == "image":
422
+ success = convert_image_pillow(input_file, output_path, output_ext)
423
  else:
424
  success = False
 
425
  return output_path if success else None
426
  except Exception as e:
427
  print(f"Error processing {input_file}: {e}")
428
  return None
429
 
430
+ def create_zip(files_to_zip, session_id):
431
+ """Create ZIP archive"""
432
+ zip_filename = f"{session_id}.zip"
433
+ with zipfile.ZipFile(zip_filename, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=1) as zipf:
434
+ for file in tqdm(files_to_zip, desc="Creating ZIP"):
435
+ zipf.write(file, os.path.basename(file))
436
+ return zip_filename
 
 
 
 
 
 
 
 
 
 
 
 
 
437
 
438
  # ----------------------- Main Processing Functions -----------------------
439
+ def process_audio_files(files, output_ext, merge_files, gap_duration, progress=gr.Progress(track_tqdm=True)):
440
+ """Process audio files"""
441
  if not files:
442
  raise gr.Error("Please upload at least one audio file!")
443
 
444
+ session_id = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
445
  os.makedirs(session_id, exist_ok=True)
446
 
447
+ print(f"\nStarting audio session: {session_id}")
448
  print(f"Files to convert: {len(files)} to .{output_ext}")
449
+ print(f"Using {MAX_WORKERS} threads")
450
 
451
  file_paths = [f if isinstance(f, str) else f.name for f in files]
452
 
453
  if merge_files:
454
+ merged_output_path = os.path.join(session_id, f"merged.{output_ext}")
455
 
456
  temp_files = []
457
+ for i, file_path in enumerate(tqdm(file_paths, desc="Converting")):
458
+ temp_output = os.path.join(session_id, f"temp_{i}.{output_ext}")
459
+ if convert_audio_ffmpeg(file_path, temp_output, output_ext):
460
+ temp_files.append(temp_output)
461
+
462
+ if not temp_files:
463
+ raise gr.Error("No files were successfully converted")
 
 
 
464
 
465
  if merge_audio_files_ffmpeg(temp_files, merged_output_path, gap_duration):
466
  for temp_file in temp_files:
 
479
  tasks = []
480
  for file_path in file_paths:
481
  base_name = os.path.splitext(os.path.basename(file_path))[0]
482
+ output_path = os.path.join(session_id, f"{base_name}.{output_ext}")
 
483
  tasks.append((file_path, output_path, "audio", output_ext))
484
 
485
  results = list(tqdm(
486
  executor.map(process_file_parallel, tasks),
487
  total=len(tasks),
488
+ desc="Converting"
489
  ))
490
 
491
  output_files = [r for r in results if r is not None]
 
494
  raise gr.Error("No files were successfully converted")
495
 
496
  if len(output_files) > 1:
497
+ return create_zip(output_files, session_id)
 
 
498
 
499
  return output_files[0]
500
 
501
+ def process_image_files(files, output_ext, progress=gr.Progress(track_tqdm=True)):
502
+ """Process image files"""
503
  if not files:
504
  raise gr.Error("Please upload at least one image!")
505
 
506
+ session_id = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
507
  os.makedirs(session_id, exist_ok=True)
508
 
509
+ print(f"\nStarting image session: {session_id}")
510
  print(f"Files to convert: {len(files)} to .{output_ext}")
511
+ print(f"Using {MAX_WORKERS} threads")
512
 
513
  file_paths = [f if isinstance(f, str) else f.name for f in files]
514
  output_files = []
515
+
516
  with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
517
  tasks = []
518
  for file_path in file_paths:
519
  base_name = os.path.splitext(os.path.basename(file_path))[0]
520
+ output_path = os.path.join(session_id, f"{base_name}.{output_ext}")
 
521
  tasks.append((file_path, output_path, "image", output_ext))
522
 
523
  results = list(tqdm(
524
  executor.map(process_file_parallel, tasks),
525
  total=len(tasks),
526
+ desc="Converting"
527
  ))
528
 
529
  output_files = [r for r in results if r is not None]
 
532
  raise gr.Error("No images were successfully converted")
533
 
534
  if len(output_files) > 1:
535
+ return create_zip(output_files, session_id)
 
 
536
 
537
  return output_files[0]
538
 
539
+ def process_video(input_video, conversion_type, output_ext, progress=gr.Progress(track_tqdm=True)):
540
+ """Process video file"""
541
  if not input_video:
542
  raise gr.Error("Please upload a video file!")
543
 
544
+ session_id = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
545
  os.makedirs(session_id, exist_ok=True)
546
 
547
  input_path = input_video if isinstance(input_video, str) else input_video.name
548
  base_name = os.path.splitext(os.path.basename(input_path))[0]
549
+ output_path = os.path.join(session_id, f"{base_name}.{output_ext}")
 
550
 
551
+ print(f"\nStarting video session: {session_id}")
552
  print(f"Conversion type: {conversion_type}, Output: .{output_ext}")
553
 
554
  success = convert_video_ffmpeg(input_path, output_path, conversion_type)
 
557
  print("Video processing complete!")
558
  return output_path
559
  else:
560
+ raise gr.Error("Video processing failed. Please check the file format and try again.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
 
562
  def update_format_choices(conversion_type):
563
+ """Update format dropdown based on conversion type"""
564
  if conversion_type == "Video to Video":
565
  vf = available_video_extensions()
566
  value = "mp4" if "mp4" in vf else (vf[0] if vf else None)
 
570
  value = "mp3" if "mp3" in af else (af[0] if af else None)
571
  return gr.Dropdown(choices=af, value=value, label="Output Audio Format")
572
 
573
+ # ----------------------- UI -----------------------
574
  AUDIO_FORMATS = available_audio_extensions()
575
  VIDEO_FORMATS = available_video_extensions()
576
  IMAGE_FORMATS = available_image_extensions()
577
 
578
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
579
  gr.HTML(i18n("title_app"))
580
+
581
+ # System info
582
+ gr.HTML(f"""
583
+ <div style='text-align: center; color: #666; margin-bottom: 1rem;'>
584
+ <p>CPU cores: {multiprocessing.cpu_count()} | Using {MAX_WORKERS} threads</p>
585
+ </div>
586
+ """)
587
 
588
  with gr.Tabs():
589
  # AUDIO TAB
 
597
  default_audio = "mp3" if "mp3" in AUDIO_FORMATS else (AUDIO_FORMATS[0] if AUDIO_FORMATS else None)
598
  audio_format_choice = gr.Dropdown(choices=AUDIO_FORMATS, label=i18n("label_audio_format_choice"), value=default_audio)
599
  merge_files_checkbox = gr.Checkbox(label=i18n("label_merge_files_checkbox"))
600
+ gap_slider = gr.Slider(minimum=0, maximum=5000, step=100, value=500, label=i18n("label_gap_slider"), visible=False)
601
 
602
  audio_submit_button = gr.Button(i18n("сonvert"), variant="primary")
603
  audio_output_file = gr.File(label=i18n("download_result"))
604
+
605
+ merge_files_checkbox.change(
606
+ lambda x: gr.update(visible=x),
607
+ inputs=merge_files_checkbox,
608
+ outputs=gap_slider
609
+ )
610
 
611
  audio_submit_button.click(
612
+ fn=process_audio_files,
613
  inputs=[audio_file_input, audio_format_choice, merge_files_checkbox, gap_slider],
614
  outputs=audio_output_file
615
  )
 
629
  image_output_file = gr.File(label=i18n("download_result"))
630
 
631
  image_submit_button.click(
632
+ fn=process_image_files,
633
  inputs=[image_file_input, image_format_choice],
634
  outputs=image_output_file
635
  )
 
664
  )
665
 
666
  video_submit_button.click(
667
+ fn=process_video,
668
  inputs=[video_input, conversion_type_radio, video_format_dropdown],
669
  outputs=video_output_file
670
  )
671
 
672
  if __name__ == "__main__":
673
+ check_ffmpeg_installation()
674
  demo.queue().launch(i18n=i18n, debug=True, show_error=True)