Spaces:
Paused
Paused
| import os | |
| import uuid | |
| import subprocess | |
| import shutil | |
| import time | |
| from flask import Flask, request, jsonify, send_from_directory | |
| from threading import Thread | |
| # --- CONFIGURACIÓN --- | |
| OUTPUT_FOLDER = "processed_videos" | |
| TEMP_FOLDER = "temp_processing" | |
| MAX_FILE_AGE_SECONDS = 3600 # 1 hora en segundos | |
| # Crea las carpetas necesarias al iniciar la app | |
| os.makedirs(OUTPUT_FOLDER, exist_ok=True) | |
| os.makedirs(TEMP_FOLDER, exist_ok=True) | |
| app = Flask(__name__, static_folder='static', static_url_path='') | |
| TASKS_STATUS = {} | |
| # --- FUNCIÓN DE AUTOLIMPIEZA --- | |
| def cleanup_old_files(): | |
| """Borra archivos en OUTPUT_FOLDER más viejos que MAX_FILE_AGE_SECONDS.""" | |
| while True: | |
| try: | |
| for filename in os.listdir(OUTPUT_FOLDER): | |
| file_path = os.path.join(OUTPUT_FOLDER, filename) | |
| if os.path.isfile(file_path): | |
| file_age = time.time() - os.path.getmtime(file_path) | |
| if file_age > MAX_FILE_AGE_SECONDS: | |
| os.remove(file_path) | |
| print(f"Limpiado archivo antiguo: {filename}") | |
| except Exception as e: | |
| print(f"Error durante la limpieza: {e}") | |
| time.sleep(600) # Revisa cada 10 minutos | |
| # --- LÓGICA DE PROCESAMIENTO DE VIDEO --- | |
| def video_processing_worker(task_id, urls): | |
| task_dir = os.path.join(TEMP_FOLDER, task_id) | |
| final_video_path = os.path.join(OUTPUT_FOLDER, f"video_{task_id}.webm") | |
| log = [] | |
| def update_status(status, progress, new_log=None): | |
| if new_log: | |
| log.append(new_log) | |
| TASKS_STATUS[task_id] = {'status': status, 'progress': progress, 'log': "\n".join(log)} | |
| try: | |
| os.makedirs(task_dir, exist_ok=True) | |
| update_status('processing', 5, "Entorno de trabajo creado.") | |
| downloaded_files = [] | |
| for i, url in enumerate(urls): | |
| progress = 5 + int(((i + 1) / len(urls)) * 70) | |
| update_status('processing', progress, f"({i+1}/{len(urls)}) Descargando: {url}") | |
| output_template = os.path.join(task_dir, f"{i:03d}.%(ext)s") | |
| command = ['yt-dlp','-f','bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best','--merge-output-format','mp4','-o',output_template,url] | |
| subprocess.run(command, check=True, capture_output=True, text=True, encoding='utf-8') | |
| created_file = next(f for f in os.listdir(task_dir) if f.startswith(f"{i:03d}")) | |
| downloaded_files.append(os.path.join(task_dir, created_file)) | |
| update_status('processing', 85, "\nUniendo videos...") | |
| file_list_path = os.path.join(task_dir, "filelist.txt") | |
| with open(file_list_path, 'w', encoding='utf-8') as f: | |
| for file_path in downloaded_files: | |
| f.write(f"file '{os.path.abspath(file_path)}'\n") | |
| ffmpeg_command = ['ffmpeg','-f','concat','-safe','0','-i',file_list_path,'-c:v','libvpx-vp9','-crf','30','-b:v','0','-c:a','libopus','-b:a','128k','-y',final_video_path] | |
| subprocess.run(ffmpeg_command, check=True, capture_output=True, text=True, encoding='utf-8') | |
| update_status('completed', 100, "\n¡Proceso completado con éxito!") | |
| TASKS_STATUS[task_id]['fileUrl'] = f"/download/{os.path.basename(final_video_path)}" | |
| except subprocess.CalledProcessError as e: | |
| error_details = f"Error en comando:\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}" | |
| update_status('failed', 100, f"\n¡ERROR! El proceso falló.\n{error_details}") | |
| except Exception as e: | |
| update_status('failed', 100, f"\n¡ERROR INESPERADO! {str(e)}") | |
| finally: | |
| if os.path.exists(task_dir): | |
| shutil.rmtree(task_dir) | |
| # --- RUTAS DE LA API (ENDPOINTS) --- | |
| def serve_index(): | |
| return app.send_static_file('index.html') | |
| def health_check(): | |
| return "OK", 200 | |
| def start_processing(): | |
| urls = request.json.get('urls') | |
| if not urls or not isinstance(urls, list): | |
| return jsonify({'error': 'La lista de URLs es inválida.'}), 400 | |
| task_id = str(uuid.uuid4()) | |
| TASKS_STATUS[task_id] = {'status': 'queued', 'progress': 0, 'log': 'Tarea en cola...'} | |
| thread = Thread(target=video_processing_worker, args=(task_id, urls)) | |
| thread.start() | |
| return jsonify({'taskId': task_id}) | |
| def get_status(task_id): | |
| return jsonify(TASKS_STATUS.get(task_id, {'error': 'Tarea no encontrada.'})) | |
| def download_file(filename): | |
| if ".." in filename or filename.startswith("/"): | |
| return "Nombre de archivo no válido", 400 | |
| return send_from_directory(OUTPUT_FOLDER, filename, as_attachment=True) | |
| # --- INICIA EL HILO DE LIMPIEZA --- | |
| cleanup_thread = Thread(target=cleanup_old_files, daemon=True) | |
| cleanup_thread.start() |