from flask import Flask, render_template, jsonify, request, send_file import torch import os import time import threading from datetime import datetime import cv2 from werkzeug.utils import secure_filename import uuid import mimetypes import numpy as np from PIL import Image import shutil from threading import Timer # Configuration UPLOAD_FOLDER = '/data/uploads' OUTPUT_FOLDER = '/data/outputs' # Global application state app_state = { "cuda_available": torch.cuda.is_available(), "processing_active": False, "logs": [], "processed_files": [] } def ensure_directories(): """Create necessary directories""" directories = [UPLOAD_FOLDER, OUTPUT_FOLDER] for directory in directories: try: os.makedirs(directory, exist_ok=True) print(f"✅ Directory verified: {directory}") except Exception as e: print(f"⚠️ Error creating directory {directory}: {e}") def cleanup_old_files(): """Limpia archivos antiguos automáticamente - CADA HORA""" try: current_time = time.time() max_age_hours = 1 # Mantener archivos solo 1 HORA max_age_seconds = max_age_hours * 3600 files_removed = 0 space_freed = 0 # Limpiar uploads if os.path.exists(UPLOAD_FOLDER): for filename in os.listdir(UPLOAD_FOLDER): file_path = os.path.join(UPLOAD_FOLDER, filename) if os.path.isfile(file_path): file_age = current_time - os.path.getmtime(file_path) if file_age > max_age_seconds: file_size = os.path.getsize(file_path) os.remove(file_path) files_removed += 1 space_freed += file_size log_message(f"🧹 Removed old upload: {filename}") # Limpiar outputs if os.path.exists(OUTPUT_FOLDER): for filename in os.listdir(OUTPUT_FOLDER): file_path = os.path.join(OUTPUT_FOLDER, filename) if os.path.isfile(file_path): file_age = current_time - os.path.getmtime(file_path) if file_age > max_age_seconds: file_size = os.path.getsize(file_path) os.remove(file_path) files_removed += 1 space_freed += file_size log_message(f"🧹 Removed old output: {filename}") # Limpiar cache de PyTorch if torch.cuda.is_available(): torch.cuda.empty_cache() if files_removed > 0: space_mb = space_freed / (1024 * 1024) log_message(f"🧹 Cleanup: {files_removed} files removed, {space_mb:.1f}MB freed") else: log_message("🧹 Cleanup: No old files to remove") except Exception as e: log_message(f"❌ Error during cleanup: {str(e)}") def cleanup_existing_files_on_startup(): """Limpia TODOS los archivos existentes al iniciar""" try: files_removed = 0 space_freed = 0 # Limpiar todos los uploads existentes if os.path.exists(UPLOAD_FOLDER): for filename in os.listdir(UPLOAD_FOLDER): file_path = os.path.join(UPLOAD_FOLDER, filename) if os.path.isfile(file_path): file_size = os.path.getsize(file_path) os.remove(file_path) files_removed += 1 space_freed += file_size # Limpiar todos los outputs existentes if os.path.exists(OUTPUT_FOLDER): for filename in os.listdir(OUTPUT_FOLDER): file_path = os.path.join(OUTPUT_FOLDER, filename) if os.path.isfile(file_path): file_size = os.path.getsize(file_path) os.remove(file_path) files_removed += 1 space_freed += file_size if files_removed > 0: space_mb = space_freed / (1024 * 1024) log_message(f"🧹 Startup cleanup: {files_removed} existing files removed, {space_mb:.1f}MB freed") else: log_message("🧹 Startup cleanup: No existing files found") except Exception as e: log_message(f"❌ Error during startup cleanup: {str(e)}") def emergency_cleanup(): """Limpieza de emergencia cuando se queda sin espacio""" try: log_message("🚨 Emergency cleanup started!") files_removed = 0 space_freed = 0 # Borrar todos los uploads if os.path.exists(UPLOAD_FOLDER): for filename in os.listdir(UPLOAD_FOLDER): file_path = os.path.join(UPLOAD_FOLDER, filename) if os.path.isfile(file_path): file_size = os.path.getsize(file_path) os.remove(file_path) files_removed += 1 space_freed += file_size # Borrar todos los outputs if os.path.exists(OUTPUT_FOLDER): for filename in os.listdir(OUTPUT_FOLDER): file_path = os.path.join(OUTPUT_FOLDER, filename) if os.path.isfile(file_path): file_size = os.path.getsize(file_path) os.remove(file_path) files_removed += 1 space_freed += file_size # Limpiar cache if torch.cuda.is_available(): torch.cuda.empty_cache() # Limpiar logs antiguos app_state["logs"] = app_state["logs"][-10:] # Mantener solo últimos 10 logs app_state["processed_files"] = [] # Limpiar historial completo space_mb = space_freed / (1024 * 1024) log_message(f"✅ Emergency cleanup: {files_removed} files removed, {space_mb:.1f}MB freed") return True except Exception as e: log_message(f"❌ Emergency cleanup failed: {str(e)}") return False def start_auto_cleanup(): """Inicia limpieza automática cada hora""" def schedule_cleanup(): cleanup_old_files() # Programar próxima limpieza en 1 hora timer = Timer(3600, schedule_cleanup) # 3600 segundos = 1 hora timer.daemon = True timer.start() # Iniciar primera limpieza schedule_cleanup() log_message("🕐 Auto-cleanup scheduled every 1 hour") def check_disk_space(): """Verifica espacio en disco disponible""" try: total, used, free = shutil.disk_usage("/data") free_mb = free / (1024 * 1024) return free_mb except: return 0 def allowed_file(filename): """Check if file has allowed extension""" return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ['png', 'jpg', 'jpeg', 'gif', 'mp4', 'avi', 'mov', 'mkv'] def get_file_mimetype(filename): """Get correct mimetype for file""" mimetype, _ = mimetypes.guess_type(filename) if mimetype is None: ext = filename.lower().rsplit('.', 1)[1] if '.' in filename else '' if ext in ['mp4', 'avi', 'mov', 'mkv']: mimetype = f'video/{ext}' elif ext in ['png', 'jpg', 'jpeg', 'gif']: mimetype = f'image/{ext}' else: mimetype = 'application/octet-stream' return mimetype def log_message(message): """Add message to log with timestamp""" timestamp = datetime.now().strftime("%H:%M:%S") app_state["logs"].append(f"[{timestamp}] {message}") if len(app_state["logs"]) > 50: # Reducido para ahorrar memoria app_state["logs"] = app_state["logs"][-50:] print(f"[{timestamp}] {message}") def optimize_gpu(): """Optimize GPU configuration for 4K upscaling""" try: if torch.cuda.is_available(): torch.backends.cudnn.benchmark = True torch.backends.cudnn.allow_tf32 = True torch.backends.cuda.matmul.allow_tf32 = True torch.cuda.empty_cache() # Test GPU test_tensor = torch.randn(100, 100, device='cuda') _ = torch.mm(test_tensor, test_tensor) log_message("✅ GPU optimized for 4K upscaling") return True else: log_message("⚠️ CUDA not available") return False except Exception as e: log_message(f"❌ Error optimizing GPU: {str(e)}") return False def get_video_codec_info(): """Helper function to get available codecs""" try: # Test which codecs are available available_codecs = [] test_codecs = [ ('mp4v', 'MPEG-4'), ('XVID', 'Xvid'), ('H264', 'H.264'), ('avc1', 'AVC'), ('X264', 'x264'), ('MJPG', 'Motion JPEG') ] for fourcc_str, name in test_codecs: try: fourcc = cv2.VideoWriter_fourcc(*fourcc_str) # Create a small test video to check if codec works test_writer = cv2.VideoWriter('/tmp/test.mp4', fourcc, 30, (640, 480)) if test_writer.isOpened(): available_codecs.append((fourcc_str, name)) test_writer.release() try: os.remove('/tmp/test.mp4') except: pass except: continue return available_codecs except Exception as e: log_message(f"Error checking codecs: {str(e)}") return [('mp4v', 'MPEG-4')] # Fallback def upscale_image_4k(input_path, output_path): """Upscale image to 4K using neural methods""" def process_worker(): try: log_message(f"🎨 Starting 4K upscaling: {os.path.basename(input_path)}") app_state["processing_active"] = True # Read original image image = cv2.imread(input_path) if image is None: log_message("❌ Error: Could not read image") return h, w = image.shape[:2] log_message(f"📏 Original resolution: {w}x{h}") # Check GPU memory availability if torch.cuda.is_available(): device = torch.device('cuda') available_memory = torch.cuda.get_device_properties(0).total_memory - torch.cuda.memory_allocated() required_memory = w * h * 4 * 4 * 3 * 4 # Conservative estimation if required_memory > available_memory * 0.8: log_message(f"⚠️ Image too large for available GPU memory, using CPU") device = torch.device('cpu') else: log_message(f"🚀 Using GPU: {torch.cuda.get_device_name()}") if device.type == 'cuda': # Convert image to normalized tensor image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image_tensor = torch.from_numpy(image_rgb).float().to(device) / 255.0 image_tensor = image_tensor.permute(2, 0, 1).unsqueeze(0) # BCHW format log_message("🧠 Applying neural upscaling...") # Progressive upscaling for better quality target_h, target_w = h * 4, w * 4 with torch.no_grad(): # Step 1: 2x upscaling with bicubic intermediate = torch.nn.functional.interpolate( image_tensor, size=(h * 2, w * 2), mode='bicubic', align_corners=False, antialias=True ) # Step 2: Final 2x upscaling with smoothing upscaled = torch.nn.functional.interpolate( intermediate, size=(target_h, target_w), mode='bicubic', align_corners=False, antialias=True ) # Enhanced sharpening filters kernel_size = 3 sigma = 0.5 kernel = torch.zeros((kernel_size, kernel_size), device=device) center = kernel_size // 2 # Create inverted Gaussian kernel for sharpening for i in range(kernel_size): for j in range(kernel_size): dist = ((i - center) ** 2 + (j - center) ** 2) ** 0.5 kernel[i, j] = torch.exp(-0.5 * (dist / sigma) ** 2) kernel = kernel / kernel.sum() sharpen_kernel = torch.zeros_like(kernel) sharpen_kernel[center, center] = 2.0 sharpen_kernel = sharpen_kernel - kernel sharpen_kernel = sharpen_kernel.unsqueeze(0).unsqueeze(0) # Apply sharpening to each channel enhanced_channels = [] for i in range(3): channel = upscaled[:, i:i+1, :, :] padded = torch.nn.functional.pad(channel, (1, 1, 1, 1), mode='reflect') enhanced = torch.nn.functional.conv2d(padded, sharpen_kernel) enhanced_channels.append(enhanced) enhanced = torch.cat(enhanced_channels, dim=1) # Light smoothing to reduce noise gaussian_kernel = torch.tensor([ [1, 4, 6, 4, 1], [4, 16, 24, 16, 4], [6, 24, 36, 24, 6], [4, 16, 24, 16, 4], [1, 4, 6, 4, 1] ], dtype=torch.float32, device=device).unsqueeze(0).unsqueeze(0) / 256.0 smoothed_channels = [] for i in range(3): channel = enhanced[:, i:i+1, :, :] padded = torch.nn.functional.pad(channel, (2, 2, 2, 2), mode='reflect') smoothed = torch.nn.functional.conv2d(padded, gaussian_kernel) smoothed_channels.append(smoothed) smoothed = torch.cat(smoothed_channels, dim=1) # Blend: 70% enhanced + 30% smoothed for quality/smoothness balance final_result = 0.7 * enhanced + 0.3 * smoothed # Clamp values and optimize contrast final_result = torch.clamp(final_result, 0, 1) # Adaptive contrast optimization for i in range(3): channel = final_result[:, i, :, :] min_val = channel.min() max_val = channel.max() if max_val > min_val: final_result[:, i, :, :] = (channel - min_val) / (max_val - min_val) # Convert back to image result_cpu = final_result.squeeze(0).permute(1, 2, 0).cpu().numpy() result_image = (result_cpu * 255).astype(np.uint8) result_bgr = cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR) # Save result cv2.imwrite(output_path, result_bgr) final_h, final_w = result_bgr.shape[:2] log_message(f"✅ Upscaling completed: {final_w}x{final_h}") log_message(f"📈 Scale factor: {final_w/w:.1f}x") # Memory cleanup del image_tensor, upscaled, enhanced, final_result torch.cuda.empty_cache() else: # CPU fallback log_message("⚠️ Using CPU - optimized processing") target_h, target_w = h * 4, w * 4 # Progressive upscaling on CPU intermediate = cv2.resize(image, (w * 2, h * 2), interpolation=cv2.INTER_CUBIC) upscaled = cv2.resize(intermediate, (target_w, target_h), interpolation=cv2.INTER_CUBIC) # Apply sharpening on CPU kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) sharpened = cv2.filter2D(upscaled, -1, kernel) # Blend for smoothing final_result = cv2.addWeighted(upscaled, 0.7, sharpened, 0.3, 0) cv2.imwrite(output_path, final_result) log_message(f"✅ CPU upscaling completed: {target_w}x{target_h}") else: # No CUDA available, use CPU processing log_message("⚠️ Using CPU - optimized processing") target_h, target_w = h * 4, w * 4 # Progressive upscaling on CPU intermediate = cv2.resize(image, (w * 2, h * 2), interpolation=cv2.INTER_CUBIC) upscaled = cv2.resize(intermediate, (target_w, target_h), interpolation=cv2.INTER_CUBIC) # Apply sharpening on CPU kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) sharpened = cv2.filter2D(upscaled, -1, kernel) # Blend for smoothing final_result = cv2.addWeighted(upscaled, 0.7, sharpened, 0.3, 0) cv2.imwrite(output_path, final_result) log_message(f"✅ CPU upscaling completed: {target_w}x{target_h}") # Add to processed files list app_state["processed_files"].append({ "input_file": os.path.basename(input_path), "output_file": os.path.basename(output_path), "original_size": f"{w}x{h}", "upscaled_size": f"{target_w}x{target_h}", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # LIMPIEZA INMEDIATA del archivo input (ya no se necesita) try: if os.path.exists(input_path): os.remove(input_path) log_message(f"🧹 Input file cleaned: {os.path.basename(input_path)}") except Exception as e: log_message(f"⚠️ Could not clean input file: {str(e)}") except Exception as e: log_message(f"❌ Error in processing: {str(e)}") finally: app_state["processing_active"] = False if torch.cuda.is_available(): torch.cuda.empty_cache() thread = threading.Thread(target=process_worker) thread.daemon = True thread.start() def upscale_video_4k(input_path, output_path): """Upscale video to 4K frame by frame - FIXED VERSION""" def process_worker(): try: log_message(f"🎬 Starting 4K video upscaling: {os.path.basename(input_path)}") app_state["processing_active"] = True # Open video cap = cv2.VideoCapture(input_path) if not cap.isOpened(): log_message("❌ Error: Could not open video") return # Get video properties fps = int(cap.get(cv2.CAP_PROP_FPS)) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) log_message(f"📹 Video: {w}x{h}, {fps}FPS, {frame_count} frames") # Calculate target dimensions with smart scaling # Limit maximum dimensions to avoid codec issues MAX_WIDTH = 7680 # 8K width limit MAX_HEIGHT = 4320 # 4K height limit # Calculate 4x scale but respect limits target_w = min(w * 4, MAX_WIDTH) target_h = min(h * 4, MAX_HEIGHT) # If we hit limits, maintain aspect ratio if target_w == MAX_WIDTH or target_h == MAX_HEIGHT: aspect_ratio = w / h if target_w / target_h > aspect_ratio: target_w = int(target_h * aspect_ratio) else: target_h = int(target_w / aspect_ratio) # Ensure dimensions are even (required for many codecs) target_w = target_w - (target_w % 2) target_h = target_h - (target_h % 2) log_message(f"🎯 Target resolution: {target_w}x{target_h}") # Try multiple codec configurations codecs_to_try = [ ('mp4v', 'MP4V'), ('XVID', 'XVID'), ('H264', 'H264'), ('avc1', 'H264'), ('X264', 'X264') ] out = None successful_codec = None for fourcc_str, codec_name in codecs_to_try: try: log_message(f"🔧 Trying codec: {codec_name}") fourcc = cv2.VideoWriter_fourcc(*fourcc_str) test_out = cv2.VideoWriter(output_path, fourcc, fps, (target_w, target_h)) # Test if the writer was created successfully if test_out.isOpened(): out = test_out successful_codec = codec_name log_message(f"✅ Successfully initialized codec: {codec_name}") break else: test_out.release() log_message(f"❌ Failed to initialize codec: {codec_name}") except Exception as e: log_message(f"❌ Error with codec {codec_name}: {str(e)}") continue if out is None: # Final fallback: try with lower resolution log_message("⚠️ All codecs failed, trying with reduced resolution...") target_w = min(w * 2, 3840) # Try 2x scaling or max 4K width target_h = min(h * 2, 2160) # Try 2x scaling or max 4K height # Ensure even dimensions target_w = target_w - (target_w % 2) target_h = target_h - (target_h % 2) fourcc = cv2.VideoWriter_fourcc(*'mp4v') out = cv2.VideoWriter(output_path, fourcc, fps, (target_w, target_h)) if not out.isOpened(): log_message("❌ All fallback attempts failed") return log_message(f"✅ Fallback successful: {target_w}x{target_h}") if torch.cuda.is_available(): device = torch.device('cuda') log_message(f"🚀 Processing with GPU: {torch.cuda.get_device_name()}") process_frames_gpu(cap, out, device, target_h, target_w, frame_count) else: log_message("💻 Processing with CPU (may be slower)") process_frames_cpu(cap, out, target_h, target_w, frame_count) cap.release() out.release() # Verify the output file was created and has content if os.path.exists(output_path): file_size = os.path.getsize(output_path) if file_size > 0: log_message(f"✅ 4K video completed: {target_w}x{target_h}") log_message(f"📁 Output file size: {file_size / (1024**2):.1f}MB") log_message(f"🎬 Used codec: {successful_codec}") else: log_message(f"❌ Output file is empty: {output_path}") raise Exception("Output video file is empty") else: log_message(f"❌ Output file not created: {output_path}") raise Exception("Output video file was not created") # Add to processed files list app_state["processed_files"].append({ "input_file": os.path.basename(input_path), "output_file": os.path.basename(output_path), "original_size": f"{w}x{h}", "upscaled_size": f"{target_w}x{target_h}", "frame_count": frame_count, "fps": fps, "codec": successful_codec, "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }) # LIMPIEZA INMEDIATA del archivo input (ya no se necesita) try: if os.path.exists(input_path): os.remove(input_path) log_message(f"🧹 Input file cleaned: {os.path.basename(input_path)}") except Exception as e: log_message(f"⚠️ Could not clean input file: {str(e)}") except Exception as e: log_message(f"❌ Error processing video: {str(e)}") finally: app_state["processing_active"] = False if torch.cuda.is_available(): torch.cuda.empty_cache() thread = threading.Thread(target=process_worker) thread.daemon = True thread.start() def process_frames_cpu(cap, out, target_h, target_w, frame_count): """Process video frames using CPU - IMPROVED VERSION""" frame_num = 0 batch_size = 5 # Process in small batches to save memory while True: ret, frame = cap.read() if not ret: break frame_num += 1 try: # Use different interpolation methods based on scale factor scale_factor = target_w / frame.shape[1] if scale_factor >= 4: # For large scaling, use LANCZOS for better quality upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4) elif scale_factor >= 2: # For medium scaling, use CUBIC upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_CUBIC) else: # For small scaling, use LINEAR upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_LINEAR) # Verify frame before writing if upscaled_frame is not None and upscaled_frame.shape[:2] == (target_h, target_w): out.write(upscaled_frame) else: log_message(f"⚠️ Skipping invalid frame {frame_num}") continue except Exception as e: log_message(f"⚠️ Error processing frame {frame_num}: {str(e)}") continue # Progress logging if frame_num % 30 == 0: progress = (frame_num / frame_count) * 100 log_message(f"🎞️ Processing frame {frame_num}/{frame_count} ({progress:.1f}%)") # Memory cleanup every batch if frame_num % batch_size == 0: import gc gc.collect() def process_frames_gpu(cap, out, device, target_h, target_w, frame_count): """Process video frames using GPU with PyTorch""" frame_num = 0 torch.backends.cudnn.benchmark = True while True: ret, frame = cap.read() if not ret: break frame_num += 1 try: # Convert to tensor frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame_tensor = torch.from_numpy(frame_rgb).float().to(device) / 255.0 frame_tensor = frame_tensor.permute(2, 0, 1).unsqueeze(0) with torch.no_grad(): upscaled = torch.nn.functional.interpolate( frame_tensor, size=(target_h, target_w), mode='bicubic', align_corners=False ) # Convert back result_cpu = upscaled.squeeze(0).permute(1, 2, 0).cpu().numpy() result_frame = (result_cpu * 255).astype(np.uint8) result_bgr = cv2.cvtColor(result_frame, cv2.COLOR_RGB2BGR) out.write(result_bgr) except Exception as e: log_message(f"⚠️ GPU processing failed for frame {frame_num}, using CPU fallback") # CPU fallback upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_CUBIC) out.write(upscaled_frame) # Progress logging if frame_num % 30 == 0: progress = (frame_num / frame_count) * 100 log_message(f"🎞️ Processing frame {frame_num}/{frame_count} ({progress:.1f}%)") # Periodic memory cleanup if frame_num % 60 == 0 and torch.cuda.is_available(): torch.cuda.empty_cache() def process_frame_batch(frame_batch, out, device, target_h, target_w): """Process batch of frames on GPU for efficiency""" try: with torch.no_grad(): # Convert batch to tensor batch_tensors = [] for frame in frame_batch: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame_tensor = torch.from_numpy(frame_rgb).float().to(device) / 255.0 frame_tensor = frame_tensor.permute(2, 0, 1) # CHW batch_tensors.append(frame_tensor) # Stack in batch batch_tensor = torch.stack(batch_tensors, dim=0) # BCHW # Upscale entire batch upscaled_batch = torch.nn.functional.interpolate( batch_tensor, size=(target_h, target_w), mode='bicubic', align_corners=False, antialias=True ) # Convert each frame back for i in range(upscaled_batch.shape[0]): result_cpu = upscaled_batch[i].permute(1, 2, 0).cpu().numpy() result_frame = (result_cpu * 255).astype(np.uint8) result_bgr = cv2.cvtColor(result_frame, cv2.COLOR_RGB2BGR) out.write(result_bgr) except Exception as e: log_message(f"❌ Error in batch processing: {str(e)}") # Fallback: process frames individually for frame in frame_batch: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame_tensor = torch.from_numpy(frame_rgb).float().to(device) / 255.0 frame_tensor = frame_tensor.permute(2, 0, 1).unsqueeze(0) upscaled = torch.nn.functional.interpolate( frame_tensor, size=(target_h, target_w), mode='bicubic', align_corners=False ) result_cpu = upscaled.squeeze(0).permute(1, 2, 0).cpu().numpy() result_frame = (result_cpu * 255).astype(np.uint8) result_bgr = cv2.cvtColor(result_frame, cv2.COLOR_RGB2BGR) out.write(result_bgr) # Initialize directories and cleanup existing files ensure_directories() cleanup_existing_files_on_startup() app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/api/system') def api_system(): """Get system information""" try: info = {} # GPU Info if torch.cuda.is_available(): info["gpu_available"] = True info["gpu_name"] = torch.cuda.get_device_name() total_memory = torch.cuda.get_device_properties(0).total_memory allocated_memory = torch.cuda.memory_allocated() info["gpu_memory"] = f"{total_memory / (1024**3):.1f}GB" info["gpu_memory_used"] = f"{allocated_memory / (1024**3):.1f}GB" info["gpu_memory_free"] = f"{(total_memory - allocated_memory) / (1024**3):.1f}GB" info["cuda_version"] = torch.version.cuda info["pytorch_version"] = torch.__version__ else: info["gpu_available"] = False info["gpu_name"] = "CPU Only (No GPU detected)" info["gpu_memory"] = "N/A" info["gpu_memory_used"] = "N/A" info["gpu_memory_free"] = "N/A" info["cuda_version"] = "Not available" info["pytorch_version"] = torch.__version__ # Storage info if os.path.exists("/data"): info["persistent_storage"] = True try: upload_files = os.listdir(UPLOAD_FOLDER) if os.path.exists(UPLOAD_FOLDER) else [] output_files = os.listdir(OUTPUT_FOLDER) if os.path.exists(OUTPUT_FOLDER) else [] upload_size = sum(os.path.getsize(os.path.join(UPLOAD_FOLDER, f)) for f in upload_files if os.path.isfile(os.path.join(UPLOAD_FOLDER, f))) output_size = sum(os.path.getsize(os.path.join(OUTPUT_FOLDER, f)) for f in output_files if os.path.isfile(os.path.join(OUTPUT_FOLDER, f))) info["storage_uploads"] = f"{upload_size / (1024**2):.1f}MB" info["storage_outputs"] = f"{output_size / (1024**2):.1f}MB" info["upload_files_count"] = len(upload_files) info["output_files_count"] = len(output_files) # Agregar info de espacio libre free_space = check_disk_space() info["free_space"] = f"{free_space:.1f}MB" except Exception as e: info["storage_uploads"] = f"Error: {str(e)}" info["storage_outputs"] = "N/A" info["upload_files_count"] = 0 info["output_files_count"] = 0 info["free_space"] = "Unknown" else: info["persistent_storage"] = False return jsonify({"success": True, "data": info}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @app.route('/api/upload', methods=['POST']) def api_upload(): """Upload and process file for 4K upscaling""" try: # Verificar espacio disponible antes de procesar free_space = check_disk_space() if free_space < 50: # Menos de 50MB libres log_message("⚠️ Low disk space detected, running emergency cleanup...") emergency_cleanup() # Verificar de nuevo después de la limpieza free_space = check_disk_space() if free_space < 20: # Aún menos de 20MB return jsonify({ "success": False, "error": "Insufficient disk space. Please try again later." }) if 'file' not in request.files: return jsonify({"success": False, "error": "No file provided"}) file = request.files['file'] if file.filename == '': return jsonify({"success": False, "error": "No file selected"}) if file and allowed_file(file.filename): file_id = str(uuid.uuid4()) filename = secure_filename(file.filename) file_ext = filename.rsplit('.', 1)[1].lower() input_filename = f"{file_id}_input.{file_ext}" input_path = os.path.join(UPLOAD_FOLDER, input_filename) file.save(input_path) output_filename = f"{file_id}_4k.{file_ext}" output_path = os.path.join(OUTPUT_FOLDER, output_filename) if file_ext in ['png', 'jpg', 'jpeg', 'gif']: upscale_image_4k(input_path, output_path) media_type = "image" elif file_ext in ['mp4', 'avi', 'mov', 'mkv']: upscale_video_4k(input_path, output_path) media_type = "video" log_message(f"📤 File uploaded: {filename}") log_message(f"🎯 Starting 4K transformation...") return jsonify({ "success": True, "file_id": file_id, "filename": filename, "output_filename": output_filename, "media_type": media_type, "message": "Upload successful, processing started" }) else: return jsonify({"success": False, "error": "File type not allowed"}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @app.route('/api/processing-status') def api_processing_status(): """Get processing status""" return jsonify({ "success": True, "processing": app_state["processing_active"], "processed_files": app_state["processed_files"] }) @app.route('/api/download/') def api_download(filename): """Download processed file""" try: file_path = os.path.join(OUTPUT_FOLDER, filename) if os.path.exists(file_path): mimetype = get_file_mimetype(filename) file_ext = filename.lower().rsplit('.', 1)[1] if '.' in filename else '' if file_ext in ['mp4', 'avi', 'mov', 'mkv']: return send_file( file_path, as_attachment=True, download_name=f"4k_upscaled_{filename}", mimetype=mimetype ) else: return send_file( file_path, as_attachment=True, download_name=f"4k_upscaled_{filename}", mimetype=mimetype ) else: return jsonify({"error": "File not found"}), 404 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/preview/') def api_preview(filename): """Preview processed file""" try: file_path = os.path.join(OUTPUT_FOLDER, filename) if os.path.exists(file_path): mimetype = get_file_mimetype(filename) return send_file(file_path, mimetype=mimetype) else: return jsonify({"error": "File not found"}), 404 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/logs') def api_logs(): """Get application logs""" return jsonify({ "success": True, "logs": app_state["logs"] }) @app.route('/api/clear-logs', methods=['POST']) def api_clear_logs(): """Clear application logs""" app_state["logs"] = [] log_message("🧹 Logs cleared") return jsonify({"success": True, "message": "Logs cleared"}) @app.route('/api/optimize-gpu', methods=['POST']) def api_optimize_gpu(): """Optimize GPU for processing""" try: success = optimize_gpu() if success: return jsonify({"success": True, "message": "GPU optimized"}) else: return jsonify({"success": False, "message": "GPU optimization failed"}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @app.route('/api/clear-cache', methods=['POST']) def api_clear_cache(): """Clear GPU cache and processed files""" try: if torch.cuda.is_available(): torch.cuda.empty_cache() app_state["processed_files"] = [] log_message("🧹 Cache and history cleared") return jsonify({"success": True, "message": "Cache cleared"}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @app.route('/api/emergency-cleanup', methods=['POST']) def api_emergency_cleanup(): """Endpoint para limpieza de emergencia""" try: success = emergency_cleanup() return jsonify({ "success": success, "message": "Emergency cleanup completed" if success else "Emergency cleanup failed" }) except Exception as e: return jsonify({"success": False, "error": str(e)}) @app.route('/api/cleanup', methods=['POST']) def api_cleanup(): """Endpoint para limpieza manual""" try: cleanup_old_files() return jsonify({"success": True, "message": "Manual cleanup completed"}) except Exception as e: return jsonify({"success": False, "error": str(e)}) if __name__ == '__main__': # Initialize system log_message("🚀 4K Upscaler starting...") try: # Optimize GPU if available if optimize_gpu(): log_message("✅ GPU optimized for 4K upscaling") else: log_message("⚠️ GPU optimization failed, using CPU fallback") # Iniciar limpieza automática cada hora start_auto_cleanup() log_message("✅ 4K Upscaler ready") log_message("📤 Upload images or videos to upscale to 4K resolution") log_message("🧹 Auto-cleanup: Files will be deleted after 1 hour") except Exception as e: log_message(f"❌ Initialization error: {str(e)}") log_message("⚠️ Starting in fallback mode...") # Run application try: app.run(host='0.0.0.0', port=7860, debug=False, threaded=True) except Exception as e: log_message(f"❌ Server startup error: {str(e)}") print(f"Critical error: {str(e)}")