sr4 commited on
Commit
db6a62b
·
verified ·
1 Parent(s): 5bac5ad

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -552
app.py DELETED
@@ -1,552 +0,0 @@
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
- )