sr4 commited on
Commit
a485066
·
verified ·
1 Parent(s): 29fdcc3

Upload 5 files

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. README.md +39 -12
  3. app.py +552 -0
  4. app_main.py +171 -0
  5. ffmpeg.exe +3 -0
  6. requirements.txt +6 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ ffmpeg.exe filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,39 @@
1
- ---
2
- title: Python Compresor
3
- emoji: 🏃
4
- colorFrom: red
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 5.44.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Compresor Premium - Aplicación Web
2
+
3
+ Aplicación web para compresión de archivos multimedia con interfaz Gradio.
4
+
5
+ ## Características
6
+
7
+ - Interfaz web moderna y responsive
8
+ - Soporte para múltiples formatos de archivo
9
+ - Compresión de videos, imágenes y GIFs
10
+ - Subida y descarga de archivos
11
+ - Procesamiento en tiempo real
12
+
13
+ ## Instalación
14
+
15
+ 1. Instalar dependencias:
16
+ ```bash
17
+ pip install -r requirements.txt
18
+ ```
19
+
20
+ 2. Ejecutar la aplicación web:
21
+ ```bash
22
+ python app.py
23
+ ```
24
+
25
+ 3. Abrir en el navegador: `http://localhost:7860`
26
+
27
+ ## Archivos
28
+
29
+ - `app.py` - Aplicación web principal con Gradio
30
+ - `app_main.py` - Versión alternativa de la aplicación
31
+ - `uploads/` - Carpeta para archivos subidos
32
+ - `requirements.txt` - Dependencias específicas para web
33
+
34
+ ## Uso
35
+
36
+ 1. Acceder a la interfaz web
37
+ 2. Subir archivo(s) a comprimir
38
+ 3. Seleccionar configuraciones de compresión
39
+ 4. Descargar archivo(s) comprimido(s)
app.py ADDED
@@ -0,0 +1,552 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import logging
4
+ import asyncio
5
+ import threading
6
+ import time
7
+ import psutil
8
+ from concurrent.futures import ThreadPoolExecutor, as_completed
9
+ from typing import Optional, List, Tuple, Dict, Any
10
+ from pathlib import Path
11
+ import gradio as gr
12
+ from dataclasses import dataclass
13
+ import json
14
+ import zipfile
15
+ import shutil
16
+
17
+
18
+ from io import BytesIO
19
+
20
+ # Configuración de logging
21
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Directorios y rutas
25
+ UPLOAD_DIR = os.path.join(os.getcwd(), "uploads")
26
+ FFMPEG_PATH = os.path.join(os.path.dirname(__file__), "ffmpeg.exe") # Ruta al ejecutable ffmpeg
27
+
28
+ # Crear directorios
29
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
30
+
31
+ # Definir formatos soportados (solo video y audio)
32
+ VIDEO_FORMATS = ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv', '.m4v', '.3gp', '.ts', '.mts', '.mpg', '.mpeg', '.ogv']
33
+ AUDIO_FORMATS = ['.mp3', '.wav', '.aac', '.flac', '.ogg', '.m4a', '.wma', '.opus', '.ac3', '.dts', '.amr', '.aiff']
34
+
35
+ ALL_SUPPORTED_FORMATS = VIDEO_FORMATS + AUDIO_FORMATS
36
+
37
+ # Configuraciones de compresión agresiva para reducir tamaño
38
+ QUALITY_PRESETS = {
39
+ "Compresión Máxima": {
40
+ "video_bitrate": "200k",
41
+ "audio_bitrate": "32k",
42
+ "preset": "slow",
43
+ "crf": "35"
44
+ },
45
+ "Compresión Alta": {
46
+ "video_bitrate": "400k",
47
+ "audio_bitrate": "48k",
48
+ "preset": "medium",
49
+ "crf": "30"
50
+ },
51
+ "Compresión Media": {
52
+ "video_bitrate": "600k",
53
+ "audio_bitrate": "64k",
54
+ "preset": "fast",
55
+ "crf": "28"
56
+ },
57
+ "Compresión Rápida": {
58
+ "video_bitrate": "800k",
59
+ "audio_bitrate": "96k",
60
+ "preset": "veryfast",
61
+ "crf": "25"
62
+ }
63
+ }
64
+
65
+ @dataclass
66
+ class ConversionProgress:
67
+ filename: str
68
+ status: str
69
+ progress: float
70
+ original_size: int = 0
71
+ compressed_size: int = 0
72
+ compression_ratio: float = 0.0
73
+ output_file: Optional[str] = None
74
+ error: Optional[str] = None
75
+
76
+ class UniversalCompressor:
77
+ def __init__(self):
78
+ self.conversion_progress = {}
79
+ self.active_conversions = 0
80
+ self.cpu_cores = psutil.cpu_count(logical=False)
81
+
82
+ def get_file_size(self, file_path: str) -> int:
83
+ """Obtener tamaño del archivo en bytes."""
84
+ try:
85
+ return os.path.getsize(file_path)
86
+ except:
87
+ return 0
88
+
89
+ def detect_file_type(self, file_path: str) -> str:
90
+ """Detectar tipo de archivo basado en extensión."""
91
+ ext = os.path.splitext(file_path)[1].lower()
92
+ if ext in VIDEO_FORMATS:
93
+ return 'video'
94
+ elif ext in AUDIO_FORMATS:
95
+ return 'audio'
96
+ else:
97
+ return 'unsupported'
98
+
99
+ def sanitize_filename(self, filename: str) -> str:
100
+ """Limpiar y validar nombre de archivo."""
101
+ return ''.join(c for c in filename if c.isalnum() or c in ('.', '_', '-')).rstrip()
102
+
103
+ def ensure_unique_filename(self, directory: str, filename: str) -> str:
104
+ """Generar nombre único para evitar sobreescrituras."""
105
+ base, ext = os.path.splitext(filename)
106
+ counter = 1
107
+ new_filename = filename
108
+ while os.path.exists(os.path.join(directory, new_filename)):
109
+ new_filename = f"{base}_{counter}{ext}"
110
+ counter += 1
111
+ return new_filename
112
+
113
+
114
+
115
+ def compress_media(self, input_file: str, quality_preset: str, progress_callback=None) -> str:
116
+ """Comprimir video/audio con configuraciones optimizadas para CPU."""
117
+ try:
118
+ media_type = self.detect_file_type(input_file)
119
+ base_name = os.path.basename(input_file)
120
+ file_id = f"{base_name}_{int(time.time())}"
121
+
122
+ # Inicializar progreso
123
+ original_size = self.get_file_size(input_file)
124
+ self.conversion_progress[file_id] = ConversionProgress(
125
+ filename=base_name,
126
+ status="Iniciando compresión...",
127
+ progress=0.0,
128
+ original_size=original_size
129
+ )
130
+
131
+ if progress_callback:
132
+ progress_callback(file_id, self.conversion_progress[file_id])
133
+
134
+ # Determinar extensión de salida
135
+ output_extension = 'mp4' if media_type == 'video' else 'm4a'
136
+ output_filename = self.ensure_unique_filename(
137
+ UPLOAD_DIR,
138
+ f"{os.path.splitext(base_name)[0]}_compressed.{output_extension}"
139
+ )
140
+ output_file = os.path.join(UPLOAD_DIR, output_filename)
141
+
142
+ quality_config = QUALITY_PRESETS[quality_preset]
143
+
144
+ # Configuraciones FFmpeg optimizadas para menor uso de CPU
145
+ if media_type == 'video':
146
+ ffmpeg_command = [
147
+ FFMPEG_PATH,
148
+ '-i', input_file,
149
+ '-c:v', 'libx264',
150
+ '-crf', quality_config['crf'],
151
+ '-preset', quality_config['preset'],
152
+ '-c:a', 'aac',
153
+ '-b:a', quality_config['audio_bitrate'],
154
+ '-movflags', '+faststart',
155
+ '-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2',
156
+ '-threads', str(min(self.cpu_cores, 4)), # Limitar threads para menor uso CPU
157
+ '-y',
158
+ output_file
159
+ ]
160
+ else: # audio
161
+ ffmpeg_command = [
162
+ FFMPEG_PATH,
163
+ '-i', input_file,
164
+ '-vn',
165
+ '-c:a', 'aac',
166
+ '-b:a', quality_config['audio_bitrate'],
167
+ '-ar', '44100',
168
+ '-threads', str(min(self.cpu_cores, 2)),
169
+ '-y',
170
+ output_file
171
+ ]
172
+
173
+ # Ejecutar conversión
174
+ self.conversion_progress[file_id].status = "Comprimiendo..."
175
+ self.conversion_progress[file_id].progress = 25.0
176
+ if progress_callback:
177
+ progress_callback(file_id, self.conversion_progress[file_id])
178
+
179
+ process = subprocess.Popen(
180
+ ffmpeg_command,
181
+ stdout=subprocess.PIPE,
182
+ stderr=subprocess.PIPE,
183
+ text=True
184
+ )
185
+
186
+ # Simular progreso
187
+ for i in range(25, 90, 10):
188
+ if process.poll() is None:
189
+ self.conversion_progress[file_id].progress = float(i)
190
+ if progress_callback:
191
+ progress_callback(file_id, self.conversion_progress[file_id])
192
+ time.sleep(0.3)
193
+ else:
194
+ break
195
+
196
+ stdout, stderr = process.communicate()
197
+
198
+ if process.returncode != 0:
199
+ error_msg = f"Error FFmpeg: {stderr}"
200
+ self.conversion_progress[file_id].status = "Error"
201
+ self.conversion_progress[file_id].error = error_msg
202
+ if progress_callback:
203
+ progress_callback(file_id, self.conversion_progress[file_id])
204
+ raise Exception(error_msg)
205
+
206
+ # Finalizar
207
+ compressed_size = self.get_file_size(output_file)
208
+ compression_ratio = ((original_size - compressed_size) / original_size) * 100 if original_size > 0 else 0
209
+
210
+ self.conversion_progress[file_id].status = "Completado"
211
+ self.conversion_progress[file_id].progress = 100.0
212
+ self.conversion_progress[file_id].compressed_size = compressed_size
213
+ self.conversion_progress[file_id].compression_ratio = compression_ratio
214
+ self.conversion_progress[file_id].output_file = output_file
215
+
216
+ if progress_callback:
217
+ progress_callback(file_id, self.conversion_progress[file_id])
218
+
219
+ logger.info(f"Archivo comprimido: {output_file} (Reducción: {compression_ratio:.1f}%)")
220
+ return output_file
221
+
222
+ except Exception as e:
223
+ error_msg = f"Error durante compresión: {str(e)}"
224
+ logger.error(error_msg)
225
+ if file_id in self.conversion_progress:
226
+ self.conversion_progress[file_id].status = "Error"
227
+ self.conversion_progress[file_id].error = error_msg
228
+ if progress_callback:
229
+ progress_callback(file_id, self.conversion_progress[file_id])
230
+ raise Exception(error_msg)
231
+
232
+ def compress_archive(self, input_file: str, progress_callback=None) -> str:
233
+ """Recomprimir archivos comprimidos para mejor ratio."""
234
+ try:
235
+ base_name = os.path.basename(input_file)
236
+ file_id = f"{base_name}_{int(time.time())}"
237
+
238
+ original_size = self.get_file_size(input_file)
239
+ self.conversion_progress[file_id] = ConversionProgress(
240
+ filename=base_name,
241
+ status="Recomprimiendo archivo...",
242
+ progress=50.0,
243
+ original_size=original_size
244
+ )
245
+
246
+ if progress_callback:
247
+ progress_callback(file_id, self.conversion_progress[file_id])
248
+
249
+ # Crear archivo 7z con máxima compresión
250
+ output_filename = self.ensure_unique_filename(
251
+ UPLOAD_DIR, f"{os.path.splitext(base_name)[0]}_compressed.7z"
252
+ )
253
+ output_file = os.path.join(UPLOAD_DIR, output_filename)
254
+
255
+ # Usar 7z para recompresión
256
+ import py7zr
257
+ with py7zr.SevenZipFile(output_file, 'w', filters=[{"id": py7zr.FILTER_LZMA2, "preset": 9}]) as archive:
258
+ archive.write(input_file, base_name)
259
+
260
+ compressed_size = self.get_file_size(output_file)
261
+ compression_ratio = ((original_size - compressed_size) / original_size) * 100 if original_size > 0 else 0
262
+
263
+ self.conversion_progress[file_id].status = "Completado"
264
+ self.conversion_progress[file_id].progress = 100.0
265
+ self.conversion_progress[file_id].compressed_size = compressed_size
266
+ self.conversion_progress[file_id].compression_ratio = compression_ratio
267
+ self.conversion_progress[file_id].output_file = output_file
268
+
269
+ if progress_callback:
270
+ progress_callback(file_id, self.conversion_progress[file_id])
271
+
272
+ return output_file
273
+
274
+ except Exception as e:
275
+ error_msg = f"Error recomprimiendo archivo: {str(e)}"
276
+ logger.error(error_msg)
277
+ if file_id in self.conversion_progress:
278
+ self.conversion_progress[file_id].status = "Error"
279
+ self.conversion_progress[file_id].error = error_msg
280
+ if progress_callback:
281
+ progress_callback(file_id, self.conversion_progress[file_id])
282
+ raise Exception(error_msg)
283
+
284
+ def compress_single_file(self, input_file: str, quality_preset: str, progress_callback=None) -> str:
285
+ """Comprimir un archivo según su tipo."""
286
+ if not os.path.exists(input_file):
287
+ raise ValueError(f"El archivo no existe: {input_file}")
288
+
289
+ file_type = self.detect_file_type(input_file)
290
+
291
+ if file_type in ['video', 'audio']:
292
+ return self.compress_media(input_file, quality_preset, progress_callback)
293
+ else:
294
+ raise ValueError(f"Tipo de archivo no soportado: {os.path.splitext(input_file)[1]}. Solo se soportan videos y audios: {', '.join(ALL_SUPPORTED_FORMATS)}")
295
+
296
+ def compress_multiple_files(self, file_paths: List[str], quality_preset: str, max_workers: int = None, progress_callback=None) -> List[str]:
297
+ """Comprimir múltiples archivos en paralelo con optimización de CPU."""
298
+ if max_workers is None:
299
+ max_workers = min(self.cpu_cores, 3) # Limitar para no saturar CPU
300
+
301
+ compressed_files = []
302
+ self.active_conversions = len(file_paths)
303
+
304
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
305
+ future_to_file = {
306
+ executor.submit(self.compress_single_file, file_path, quality_preset, progress_callback): file_path
307
+ for file_path in file_paths
308
+ }
309
+
310
+ for future in as_completed(future_to_file):
311
+ file_path = future_to_file[future]
312
+ try:
313
+ result = future.result()
314
+ compressed_files.append(result)
315
+ except Exception as e:
316
+ logger.error(f"Error comprimiendo {file_path}: {e}")
317
+
318
+ self.active_conversions = 0
319
+ return compressed_files
320
+
321
+ # Instancia global del compresor
322
+ compressor = UniversalCompressor()
323
+
324
+ def update_progress_display():
325
+ """Actualizar visualización del progreso con información detallada."""
326
+ if not compressor.conversion_progress:
327
+ return "No hay compresiones en progreso."
328
+
329
+ progress_text = "📊 **Estado de Compresiones:**\n\n"
330
+ total_original = 0
331
+ total_compressed = 0
332
+
333
+ for file_id, progress in compressor.conversion_progress.items():
334
+ status_emoji = {
335
+ "Iniciando compresión...": "🔄",
336
+ "Procesando imagen...": "🖼️",
337
+ "Comprimiendo...": "⚙️",
338
+ "Recomprimiendo archivo...": "📦",
339
+ "Completado": "✅",
340
+ "Error": "❌"
341
+ }.get(progress.status, "🔄")
342
+
343
+ progress_text += f"{status_emoji} **{progress.filename}**\n"
344
+ progress_text += f" Estado: {progress.status}\n"
345
+ progress_text += f" Progreso: {progress.progress:.1f}%\n"
346
+
347
+ if progress.original_size > 0:
348
+ original_mb = progress.original_size / (1024 * 1024)
349
+ progress_text += f" Tamaño original: {original_mb:.2f} MB\n"
350
+ total_original += progress.original_size
351
+
352
+ if progress.compressed_size > 0:
353
+ compressed_mb = progress.compressed_size / (1024 * 1024)
354
+ progress_text += f" Tamaño comprimido: {compressed_mb:.2f} MB\n"
355
+ progress_text += f" Reducción: {progress.compression_ratio:.1f}%\n"
356
+ total_compressed += progress.compressed_size
357
+
358
+ if progress.error:
359
+ progress_text += f" Error: {progress.error}\n"
360
+ progress_text += "\n"
361
+
362
+ # Estadísticas totales
363
+ if total_original > 0:
364
+ total_reduction = ((total_original - total_compressed) / total_original) * 100
365
+ progress_text += f"**📈 Estadísticas Totales:**\n"
366
+ progress_text += f"Reducción total: {total_reduction:.1f}% ({(total_original - total_compressed) / (1024 * 1024):.2f} MB ahorrados)\n"
367
+
368
+ return progress_text
369
+
370
+ def gradio_single_compression(file, quality_preset):
371
+ """Interfaz Gradio para compresión de archivo único."""
372
+ if not file:
373
+ raise gr.Error("No se ha subido ningún archivo. Por favor, sube un archivo.")
374
+
375
+ try:
376
+ compressor.conversion_progress.clear()
377
+ compressed_file = compressor.compress_single_file(file, quality_preset)
378
+ return compressed_file, update_progress_display()
379
+ except Exception as e:
380
+ raise gr.Error(f"Compresión fallida: {str(e)}")
381
+
382
+ def gradio_multiple_compression(files, quality_preset, max_workers):
383
+ """Interfaz Gradio para compresión de múltiples archivos."""
384
+ if not files:
385
+ raise gr.Error("No se han subido archivos. Por favor, sube al menos un archivo.")
386
+
387
+ try:
388
+ compressor.conversion_progress.clear()
389
+ file_paths = [file.name for file in files]
390
+
391
+ compressed_files = compressor.compress_multiple_files(
392
+ file_paths, quality_preset, max_workers=max_workers
393
+ )
394
+
395
+ if len(compressed_files) > 1:
396
+ zip_filename = os.path.join(UPLOAD_DIR, f"compressed_files_{int(time.time())}.zip")
397
+ with zipfile.ZipFile(zip_filename, 'w') as zipf:
398
+ for file_path in compressed_files:
399
+ zipf.write(file_path, os.path.basename(file_path))
400
+ return zip_filename, update_progress_display()
401
+ elif len(compressed_files) == 1:
402
+ return compressed_files[0], update_progress_display()
403
+ else:
404
+ raise gr.Error("No se pudo comprimir ningún archivo.")
405
+
406
+ except Exception as e:
407
+ raise gr.Error(f"Compresión fallida: {str(e)}")
408
+
409
+ def refresh_progress():
410
+ """Refrescar estado del progreso."""
411
+ return update_progress_display()
412
+
413
+ # Interfaz Gradio mejorada
414
+ with gr.Blocks(title="🗜️ Compresor Universal Pro Max", theme=gr.themes.Soft()) as iface:
415
+ gr.Markdown(
416
+ """
417
+ # 🗜️ Compresor Universal Pro Max
418
+
419
+ **El compresor más potente y rápido para todos tus archivos**
420
+
421
+ ✨ **Características Avanzadas:**
422
+ - 🚀 Compresión inteligente y optimizada para CPU
423
+ - 🎬 Videos, audios, imágenes, GIFs y documentos
424
+ - 🖼️ Soporte completo para PNG, JPG, WebP, TIFF, GIF animado
425
+ - ⚡ Procesamiento paralelo ultra-rápido
426
+ - 📊 Monitoreo detallado con estadísticas de compresión
427
+ - 🔧 Algoritmos optimizados para máxima reducción de tamaño
428
+ - 💾 Ahorro significativo de espacio en disco
429
+ """
430
+ )
431
+
432
+ with gr.Tabs():
433
+ with gr.TabItem("📄 Archivo Único"):
434
+ with gr.Row():
435
+ with gr.Column(scale=2):
436
+ single_file_input = gr.File(
437
+ label="📁 Subir Archivo (Todos los formatos soportados)",
438
+ type="filepath",
439
+ file_types=ALL_SUPPORTED_FORMATS
440
+ )
441
+ single_quality_input = gr.Dropdown(
442
+ choices=list(QUALITY_PRESETS.keys()),
443
+ value="Compresión Alta",
444
+ label="🎯 Nivel de Compresión"
445
+ )
446
+ single_compress_btn = gr.Button(
447
+ "🗜️ Comprimir Archivo",
448
+ variant="primary",
449
+ size="lg"
450
+ )
451
+
452
+ with gr.Column(scale=1):
453
+ single_output = gr.File(
454
+ label="📥 Descargar Archivo Comprimido",
455
+ type="filepath"
456
+ )
457
+
458
+ with gr.TabItem("📁 Múltiples Archivos"):
459
+ with gr.Row():
460
+ with gr.Column(scale=2):
461
+ multiple_files_input = gr.File(
462
+ label="📁 Subir Múltiples Archivos",
463
+ file_count="multiple",
464
+ type="filepath",
465
+ file_types=ALL_SUPPORTED_FORMATS
466
+ )
467
+ with gr.Row():
468
+ multiple_quality_input = gr.Dropdown(
469
+ choices=list(QUALITY_PRESETS.keys()),
470
+ value="Compresión Alta",
471
+ label="🎯 Nivel de Compresión"
472
+ )
473
+ workers_input = gr.Slider(
474
+ minimum=1,
475
+ maximum=min(psutil.cpu_count(), 6),
476
+ value=min(psutil.cpu_count(logical=False), 3),
477
+ step=1,
478
+ label="⚡ Archivos Simultáneos"
479
+ )
480
+ multiple_compress_btn = gr.Button(
481
+ "🗜️ Comprimir Todos los Archivos",
482
+ variant="primary",
483
+ size="lg"
484
+ )
485
+
486
+ with gr.Column(scale=1):
487
+ multiple_output = gr.File(
488
+ label="📥 Descargar Archivos Comprimidos",
489
+ type="filepath"
490
+ )
491
+
492
+ # Panel de progreso mejorado
493
+ gr.Markdown("## 📊 Estado y Estadísticas de Compresión")
494
+ progress_display = gr.Markdown(
495
+ value="No hay compresiones en progreso.",
496
+ label="Estado"
497
+ )
498
+ refresh_btn = gr.Button("🔄 Actualizar Estado", size="sm")
499
+
500
+ # Información detallada
501
+ with gr.Accordion("ℹ️ Información Detallada", open=False):
502
+ gr.Markdown(
503
+ f"""
504
+ ### 🎯 Niveles de Compresión:
505
+
506
+ - **Compresión Máxima**: Máxima reducción de tamaño, proceso más lento
507
+ - **Compresión Alta**: Excelente balance compresión/velocidad
508
+ - **Compresión Media**: Buena compresión, procesamiento rápido
509
+ - **Compresión Rápida**: Compresión básica, velocidad máxima
510
+
511
+ ### 📁 Formatos Soportados:
512
+
513
+ **🎬 Video**: {', '.join(VIDEO_FORMATS)}
514
+
515
+ **🎵 Audio**: {', '.join(AUDIO_FORMATS)}
516
+
517
+ ### ⚡ Optimizaciones:
518
+
519
+ - **CPU Cores Detectados**: {psutil.cpu_count()} (Lógicos), {psutil.cpu_count(logical=False)} (Físicos)
520
+ - **Threads Optimizados**: Máximo {min(psutil.cpu_count(logical=False), 4)} para video, {min(psutil.cpu_count(logical=False), 2)} para audio
521
+ - **Compresión Inteligente**: Algoritmos adaptativos según tipo de archivo
522
+ - **Gestión de Memoria**: Optimizada para archivos grandes
523
+ """
524
+ )
525
+
526
+ # Eventos
527
+ single_compress_btn.click(
528
+ fn=gradio_single_compression,
529
+ inputs=[single_file_input, single_quality_input],
530
+ outputs=[single_output, progress_display]
531
+ )
532
+
533
+ multiple_compress_btn.click(
534
+ fn=gradio_multiple_compression,
535
+ inputs=[multiple_files_input, multiple_quality_input, workers_input],
536
+ outputs=[multiple_output, progress_display]
537
+ )
538
+
539
+ refresh_btn.click(
540
+ fn=refresh_progress,
541
+ outputs=[progress_display]
542
+ )
543
+
544
+ # Lanzar interfaz
545
+ if __name__ == "__main__":
546
+ iface.launch(
547
+ share=True,
548
+ server_name="0.0.0.0",
549
+ server_port=7861,
550
+ show_error=True,
551
+ max_threads=min(psutil.cpu_count(), 10) # Limitar threads de Gradio
552
+ )
app_main.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import subprocess
4
+ import time
5
+ import logging
6
+ import psutil
7
+ from pathlib import Path
8
+
9
+ # Configuración de logging
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Configuración de rutas
14
+ FFMPEG_PATH = os.path.join(os.getcwd(), "ffmpeg.exe")
15
+ UPLOADS_DIR = os.path.join(os.getcwd(), "uploads")
16
+
17
+ # Crear directorio de uploads si no existe
18
+ os.makedirs(UPLOADS_DIR, exist_ok=True)
19
+
20
+ # Formatos soportados
21
+ VIDEO_FORMATS = ['.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v']
22
+ AUDIO_FORMATS = ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a', '.wma']
23
+ SUPPORTED_FORMATS = VIDEO_FORMATS + AUDIO_FORMATS
24
+
25
+ # Presets de compresión
26
+ COMPRESSION_PRESETS = {
27
+ "Compresión Rápida": {
28
+ "video": "-c:v libx264 -preset ultrafast -crf 28",
29
+ "audio": "-c:a aac -b:a 128k"
30
+ },
31
+ "Compresión Media": {
32
+ "video": "-c:v libx264 -preset medium -crf 23",
33
+ "audio": "-c:a aac -b:a 128k"
34
+ },
35
+ "Compresión Alta": {
36
+ "video": "-c:v libx264 -preset slow -crf 20",
37
+ "audio": "-c:a aac -b:a 192k"
38
+ },
39
+ "Compresión Máxima": {
40
+ "video": "-c:v libx264 -preset veryslow -crf 18",
41
+ "audio": "-c:a aac -b:a 256k"
42
+ }
43
+ }
44
+
45
+ def compress_media(file_path, preset="Compresión Alta", progress=gr.Progress()):
46
+ """Comprimir archivo de media usando FFmpeg"""
47
+ try:
48
+ if not os.path.exists(file_path):
49
+ return None, "Error: Archivo no encontrado"
50
+
51
+ file_ext = Path(file_path).suffix.lower()
52
+ if file_ext not in SUPPORTED_FORMATS:
53
+ return None, f"Error: Formato {file_ext} no soportado"
54
+
55
+ # Generar nombre de archivo de salida
56
+ base_name = Path(file_path).stem
57
+ output_path = os.path.join(UPLOADS_DIR, f"{base_name}_compressed{file_ext}")
58
+
59
+ # Obtener configuración del preset
60
+ preset_config = COMPRESSION_PRESETS.get(preset, COMPRESSION_PRESETS["Compresión Alta"])
61
+
62
+ # Construir comando FFmpeg
63
+ cmd = [
64
+ FFMPEG_PATH,
65
+ "-i", file_path,
66
+ "-y", # Sobrescribir archivo de salida
67
+ ]
68
+
69
+ # Agregar configuración según el tipo de archivo
70
+ if file_ext in VIDEO_FORMATS:
71
+ cmd.extend(preset_config["video"].split())
72
+ cmd.extend(preset_config["audio"].split())
73
+ else: # Audio
74
+ cmd.extend(preset_config["audio"].split())
75
+
76
+ cmd.append(output_path)
77
+
78
+ logger.info(f"Ejecutando comando: {' '.join(cmd)}")
79
+
80
+ # Ejecutar FFmpeg
81
+ process = subprocess.Popen(
82
+ cmd,
83
+ stdout=subprocess.PIPE,
84
+ stderr=subprocess.PIPE,
85
+ text=True
86
+ )
87
+
88
+ # Simular progreso
89
+ for i in progress.tqdm(range(100), desc="Comprimiendo..."):
90
+ time.sleep(0.1)
91
+ if process.poll() is not None:
92
+ break
93
+
94
+ stdout, stderr = process.communicate()
95
+
96
+ if process.returncode != 0:
97
+ logger.error(f"Error en FFmpeg: {stderr}")
98
+ return None, f"Error durante compresión: {stderr[:200]}"
99
+
100
+ if not os.path.exists(output_path):
101
+ return None, "Error: No se pudo crear el archivo comprimido"
102
+
103
+ # Calcular ratio de compresión
104
+ original_size = os.path.getsize(file_path)
105
+ compressed_size = os.path.getsize(output_path)
106
+ compression_ratio = (1 - compressed_size / original_size) * 100
107
+
108
+ logger.info(f"Compresión completada. Ratio: {compression_ratio:.1f}%")
109
+
110
+ return output_path, f"Compresión exitosa! Reducción: {compression_ratio:.1f}%"
111
+
112
+ except Exception as e:
113
+ logger.error(f"Error en compress_media: {str(e)}")
114
+ return None, f"Error durante compresión: {str(e)}"
115
+
116
+ def run_app():
117
+ """Ejecutar la aplicación Gradio"""
118
+
119
+ # Verificar que FFmpeg existe
120
+ if not os.path.exists(FFMPEG_PATH):
121
+ print(f"Error: FFmpeg no encontrado en {FFMPEG_PATH}")
122
+ return
123
+
124
+ with gr.Blocks(title="Compresor de Video y Audio", theme=gr.themes.Soft()) as iface:
125
+ gr.Markdown("# 🎬 Compresor de Video y Audio")
126
+ gr.Markdown("Comprime tus archivos de video y audio de forma eficiente")
127
+
128
+ with gr.Row():
129
+ with gr.Column():
130
+ file_input = gr.File(
131
+ label="Seleccionar archivo",
132
+ file_types=SUPPORTED_FORMATS
133
+ )
134
+
135
+ preset_dropdown = gr.Dropdown(
136
+ choices=list(COMPRESSION_PRESETS.keys()),
137
+ value="Compresión Alta",
138
+ label="Nivel de compresión"
139
+ )
140
+
141
+ compress_btn = gr.Button("🚀 Comprimir", variant="primary")
142
+
143
+ with gr.Column():
144
+ output_file = gr.File(label="Archivo comprimido")
145
+ status_text = gr.Textbox(label="Estado", interactive=False)
146
+
147
+ # Información sobre formatos soportados
148
+ gr.Markdown(
149
+ f"**Formatos soportados:**\n"
150
+ f"- Video: {', '.join(VIDEO_FORMATS)}\n"
151
+ f"- Audio: {', '.join(AUDIO_FORMATS)}"
152
+ )
153
+
154
+ # Evento de compresión
155
+ compress_btn.click(
156
+ fn=compress_media,
157
+ inputs=[file_input, preset_dropdown],
158
+ outputs=[output_file, status_text]
159
+ )
160
+
161
+ # Lanzar interfaz
162
+ iface.launch(
163
+ share=False,
164
+ server_name="127.0.0.1",
165
+ server_port=7862,
166
+ show_error=True,
167
+ inbrowser=True
168
+ )
169
+
170
+ if __name__ == "__main__":
171
+ run_app()
ffmpeg.exe ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1fcb0cb525085cc5be3ad4be16799d60786b65cc36951622a018d00f78c0fb5d
3
+ size 98730496
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ ffmpeg-python==0.2.0
3
+ Pillow==10.4.0
4
+ imageio==2.35.1
5
+ numpy==2.1.1
6
+ requests==2.32.3