Malaji71 commited on
Commit
9d0f386
Β·
verified Β·
1 Parent(s): fa907bf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +226 -382
app.py CHANGED
@@ -11,7 +11,7 @@ import mimetypes
11
  import numpy as np
12
  from PIL import Image
13
 
14
- # Real-ESRGAN imports with error handling
15
  try:
16
  from realesrgan import RealESRGANer
17
  from basicsr.archs.rrdbnet_arch import RRDBNet
@@ -20,7 +20,6 @@ try:
20
  except ImportError as e:
21
  REALESRGAN_AVAILABLE = False
22
  print(f"⚠️ Real-ESRGAN not available: {e}")
23
- print("πŸ“ Using fallback bicubic upscaling methods")
24
 
25
  # Configuration
26
  UPLOAD_FOLDER = '/data/uploads'
@@ -90,8 +89,12 @@ def download_realesrgan_models():
90
  model_path = os.path.join(MODEL_FOLDER, f"{model_name}.pth")
91
  if not os.path.exists(model_path):
92
  log_message(f"πŸ“₯ Downloading {model_name}...")
93
- urllib.request.urlretrieve(url, model_path)
94
- log_message(f"βœ… Downloaded {model_name}")
 
 
 
 
95
  return True
96
  except Exception as e:
97
  log_message(f"❌ Error downloading models: {str(e)}")
@@ -104,12 +107,15 @@ def initialize_realesrgan(model_name='RealESRGAN_x4plus', scale=4):
104
  return None
105
 
106
  try:
 
 
107
  model_path = os.path.join(MODEL_FOLDER, f"{model_name}.pth")
108
 
109
  # Check if model exists, download if not
110
  if not os.path.exists(model_path):
111
  log_message(f"πŸ“₯ Model {model_name} not found, downloading...")
112
  if not download_realesrgan_models():
 
113
  return None
114
 
115
  # Initialize model architecture
@@ -123,18 +129,19 @@ def initialize_realesrgan(model_name='RealESRGAN_x4plus', scale=4):
123
  log_message(f"❌ Unknown model: {model_name}")
124
  return None
125
 
126
- # Determine device
127
- device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 
128
 
129
  # Initialize upscaler with conservative settings
130
  upscaler = RealESRGANer(
131
  scale=netscale,
132
  model_path=model_path,
133
  model=model,
134
- tile=400 if device.type == 'cuda' else 200, # Conservative tile sizes
135
  tile_pad=10,
136
  pre_pad=0,
137
- half=False, # Disable half precision for stability
138
  device=device
139
  )
140
 
@@ -145,6 +152,8 @@ def initialize_realesrgan(model_name='RealESRGAN_x4plus', scale=4):
145
 
146
  except Exception as e:
147
  log_message(f"❌ Error initializing Real-ESRGAN: {str(e)}")
 
 
148
  return None
149
 
150
  def optimize_gpu():
@@ -163,28 +172,20 @@ def optimize_gpu():
163
  log_message("βœ… GPU optimized")
164
  return True
165
  else:
166
- log_message("⚠️ CUDA not available")
167
  return False
168
  except Exception as e:
169
  log_message(f"❌ Error optimizing GPU: {str(e)}")
170
  return False
171
 
172
- def upscale_image_realesrgan(input_path, output_path):
173
- """Upscale image using Real-ESRGAN"""
174
  def process_worker():
175
  try:
176
- log_message(f"🎨 Starting Real-ESRGAN upscaling: {os.path.basename(input_path)}")
177
  app_state["processing_active"] = True
178
 
179
- # Initialize upscaler if not done
180
- if app_state["upscaler"] is None:
181
- upscaler = initialize_realesrgan()
182
- if upscaler is None:
183
- log_message("❌ Real-ESRGAN initialization failed, using fallback")
184
- upscale_image_fallback(input_path, output_path)
185
- return
186
- else:
187
- upscaler = app_state["upscaler"]
188
 
189
  # Read image
190
  img = cv2.imread(input_path, cv2.IMREAD_COLOR)
@@ -195,51 +196,146 @@ def upscale_image_realesrgan(input_path, output_path):
195
  h, w = img.shape[:2]
196
  log_message(f"πŸ“ Original resolution: {w}x{h}")
197
 
198
- # Check for very large images
199
- if w * h > 4000 * 4000:
200
- log_message("⚠️ Very large image detected, using tiled processing")
201
- upscaler.tile = 200
202
- upscaler.tile_pad = 5
203
 
204
- # Perform upscaling
205
- log_message("🧠 Applying Real-ESRGAN neural upscaling...")
206
- start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
- try:
209
- output, _ = upscaler.enhance(img, outscale=4)
210
-
211
- # Save result
212
- cv2.imwrite(output_path, output)
213
 
214
- processing_time = time.time() - start_time
215
- final_h, final_w = output.shape[:2]
216
 
217
- log_message(f"βœ… Real-ESRGAN upscaling completed: {final_w}x{final_h}")
218
- log_message(f"πŸ“ˆ Scale factor: {final_w/w:.1f}x")
219
- log_message(f"⏱️ Processing time: {processing_time:.1f}s")
220
-
221
- # Add to processed files list
222
- app_state["processed_files"].append({
223
- "input_file": os.path.basename(input_path),
224
- "output_file": os.path.basename(output_path),
225
- "original_size": f"{w}x{h}",
226
- "upscaled_size": f"{final_w}x{final_h}",
227
- "method": f"Real-ESRGAN ({app_state['current_model']})",
228
- "processing_time": f"{processing_time:.1f}s",
229
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
230
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
- except RuntimeError as e:
233
- if "out of memory" in str(e).lower():
234
- log_message("⚠️ GPU memory insufficient, using fallback")
235
- upscale_image_fallback(input_path, output_path)
236
- else:
237
- raise e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  except Exception as e:
240
- log_message(f"❌ Error in Real-ESRGAN processing: {str(e)}")
241
- log_message("πŸ”„ Falling back to traditional upscaling")
242
- upscale_image_fallback(input_path, output_path)
243
  finally:
244
  app_state["processing_active"] = False
245
  if torch.cuda.is_available():
@@ -249,70 +345,6 @@ def upscale_image_realesrgan(input_path, output_path):
249
  thread.daemon = True
250
  thread.start()
251
 
252
- def upscale_image_fallback(input_path, output_path):
253
- """Fallback upscaling method using bicubic interpolation"""
254
- try:
255
- log_message("πŸ”„ Using fallback bicubic upscaling")
256
-
257
- # Read original image
258
- image = cv2.imread(input_path)
259
- if image is None:
260
- log_message("❌ Error: Could not read image")
261
- return
262
-
263
- h, w = image.shape[:2]
264
- target_h, target_w = h * 4, w * 4
265
-
266
- if torch.cuda.is_available():
267
- try:
268
- # GPU implementation
269
- device = torch.device('cuda')
270
- image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
271
- image_tensor = torch.from_numpy(image_rgb).float().to(device) / 255.0
272
- image_tensor = image_tensor.permute(2, 0, 1).unsqueeze(0)
273
-
274
- with torch.no_grad():
275
- upscaled = torch.nn.functional.interpolate(
276
- image_tensor,
277
- size=(target_h, target_w),
278
- mode='bicubic',
279
- align_corners=False,
280
- antialias=True
281
- )
282
- upscaled = torch.clamp(upscaled, 0, 1)
283
-
284
- result_cpu = upscaled.squeeze(0).permute(1, 2, 0).cpu().numpy()
285
- result_image = (result_cpu * 255).astype(np.uint8)
286
- result_bgr = cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR)
287
-
288
- cv2.imwrite(output_path, result_bgr)
289
- log_message(f"βœ… GPU fallback completed: {target_w}x{target_h}")
290
-
291
- except Exception as e:
292
- log_message(f"⚠️ GPU fallback failed: {e}, using CPU")
293
- # CPU fallback
294
- upscaled = cv2.resize(image, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
295
- cv2.imwrite(output_path, upscaled)
296
- log_message(f"βœ… CPU fallback completed: {target_w}x{target_h}")
297
- else:
298
- # CPU fallback
299
- upscaled = cv2.resize(image, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
300
- cv2.imwrite(output_path, upscaled)
301
- log_message(f"βœ… CPU fallback completed: {target_w}x{target_h}")
302
-
303
- # Add to processed files
304
- app_state["processed_files"].append({
305
- "input_file": os.path.basename(input_path),
306
- "output_file": os.path.basename(output_path),
307
- "original_size": f"{w}x{h}",
308
- "upscaled_size": f"{target_w}x{target_h}",
309
- "method": "Fallback (Bicubic)",
310
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
311
- })
312
-
313
- except Exception as e:
314
- log_message(f"❌ Error in fallback processing: {str(e)}")
315
-
316
  def upscale_video_4k(input_path, output_path):
317
  """Upscale video to 4K frame by frame"""
318
  def process_worker():
@@ -340,6 +372,8 @@ def upscale_video_4k(input_path, output_path):
340
 
341
  # Process frames
342
  frame_num = 0
 
 
343
  while True:
344
  ret, frame = cap.read()
345
  if not ret:
@@ -348,14 +382,22 @@ def upscale_video_4k(input_path, output_path):
348
  frame_num += 1
349
 
350
  try:
351
- # Simple bicubic upscaling for video (faster)
352
  upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
353
- out.write(upscaled_frame)
 
 
 
 
 
 
354
 
355
  # Progress logging
356
  if frame_num % 30 == 0:
357
  progress = (frame_num / frame_count) * 100
358
- log_message(f"🎞️ Processing frame {frame_num}/{frame_count} ({progress:.1f}%)")
 
 
359
 
360
  except Exception as e:
361
  log_message(f"⚠️ Error processing frame {frame_num}: {e}")
@@ -367,8 +409,10 @@ def upscale_video_4k(input_path, output_path):
367
  # Verify output
368
  if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
369
  file_size = os.path.getsize(output_path)
 
370
  log_message(f"βœ… Video upscaling completed: {target_w}x{target_h}")
371
- log_message(f"πŸ“ Output file size: {file_size / (1024**2):.1f}MB")
 
372
 
373
  # Add to processed files
374
  app_state["processed_files"].append({
@@ -378,7 +422,8 @@ def upscale_video_4k(input_path, output_path):
378
  "upscaled_size": f"{target_w}x{target_h}",
379
  "frame_count": frame_count,
380
  "fps": fps,
381
- "method": "Bicubic (Video)",
 
382
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
383
  })
384
  else:
@@ -395,194 +440,22 @@ def upscale_video_4k(input_path, output_path):
395
  thread.daemon = True
396
  thread.start()
397
 
398
- # Initialize directories
399
  ensure_directories()
400
 
 
 
 
 
 
 
 
 
401
  app = Flask(__name__)
402
 
403
  @app.route('/')
404
  def index():
405
- return """
406
- <!DOCTYPE html>
407
- <html>
408
- <head>
409
- <title>4K AI Upscaler</title>
410
- <meta charset="utf-8">
411
- <meta name="viewport" content="width=device-width, initial-scale=1">
412
- <style>
413
- body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
414
- .container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
415
- h1 { color: #333; text-align: center; }
416
- .upload-area { border: 2px dashed #ccc; padding: 40px; text-align: center; margin: 20px 0; border-radius: 10px; }
417
- .upload-area.dragover { border-color: #007bff; background-color: #f0f8ff; }
418
- .btn { background-color: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; }
419
- .btn:hover { background-color: #0056b3; }
420
- .progress { width: 100%; height: 20px; background-color: #f0f0f0; border-radius: 10px; overflow: hidden; margin: 20px 0; }
421
- .progress-bar { height: 100%; background-color: #007bff; width: 0%; transition: width 0.3s; }
422
- .logs { background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 5px; padding: 15px; height: 200px; overflow-y: auto; font-family: monospace; font-size: 12px; }
423
- .system-info { background-color: #e9ecef; padding: 15px; border-radius: 5px; margin: 20px 0; }
424
- .hidden { display: none; }
425
- </style>
426
- </head>
427
- <body>
428
- <div class="container">
429
- <h1>πŸš€ 4K AI Upscaler</h1>
430
-
431
- <div class="system-info" id="system-info">
432
- Loading system information...
433
- </div>
434
-
435
- <div class="upload-area" id="upload-area">
436
- <p>πŸ“€ Drop your image or video here, or click to select</p>
437
- <input type="file" id="file-input" accept=".png,.jpg,.jpeg,.gif,.mp4,.avi,.mov,.mkv" style="display: none;">
438
- <button class="btn" onclick="document.getElementById('file-input').click()">Select File</button>
439
- </div>
440
-
441
- <div class="progress hidden" id="progress">
442
- <div class="progress-bar" id="progress-bar"></div>
443
- </div>
444
-
445
- <div id="result" class="hidden">
446
- <h3>βœ… Processing Complete!</h3>
447
- <button class="btn" id="download-btn">Download Result</button>
448
- </div>
449
-
450
- <h3>πŸ“Š System Logs</h3>
451
- <div class="logs" id="logs"></div>
452
-
453
- <button class="btn" onclick="clearLogs()" style="margin-top: 10px;">Clear Logs</button>
454
- </div>
455
-
456
- <script>
457
- let currentFileId = null;
458
-
459
- // Load system info
460
- function loadSystemInfo() {
461
- fetch('/api/system')
462
- .then(response => response.json())
463
- .then(data => {
464
- if (data.success) {
465
- const info = data.data;
466
- document.getElementById('system-info').innerHTML = `
467
- <strong>πŸ–₯️ System Status:</strong><br>
468
- GPU: ${info.gpu_name}<br>
469
- PyTorch: ${info.pytorch_version}<br>
470
- Real-ESRGAN: ${info.realesrgan_available ? 'βœ… Available' : '❌ Not Available'}<br>
471
- Current Model: ${info.current_model || 'None'}
472
- `;
473
- }
474
- })
475
- .catch(error => {
476
- document.getElementById('system-info').innerHTML = '❌ Error loading system info';
477
- });
478
- }
479
-
480
- // Load logs
481
- function loadLogs() {
482
- fetch('/api/logs')
483
- .then(response => response.json())
484
- .then(data => {
485
- if (data.success) {
486
- const logsDiv = document.getElementById('logs');
487
- logsDiv.innerHTML = data.logs.join('<br>');
488
- logsDiv.scrollTop = logsDiv.scrollHeight;
489
- }
490
- });
491
- }
492
-
493
- // Clear logs
494
- function clearLogs() {
495
- fetch('/api/clear-logs', { method: 'POST' })
496
- .then(() => loadLogs());
497
- }
498
-
499
- // File upload handling
500
- const uploadArea = document.getElementById('upload-area');
501
- const fileInput = document.getElementById('file-input');
502
-
503
- uploadArea.addEventListener('dragover', (e) => {
504
- e.preventDefault();
505
- uploadArea.classList.add('dragover');
506
- });
507
-
508
- uploadArea.addEventListener('dragleave', () => {
509
- uploadArea.classList.remove('dragover');
510
- });
511
-
512
- uploadArea.addEventListener('drop', (e) => {
513
- e.preventDefault();
514
- uploadArea.classList.remove('dragover');
515
- const files = e.dataTransfer.files;
516
- if (files.length > 0) {
517
- uploadFile(files[0]);
518
- }
519
- });
520
-
521
- fileInput.addEventListener('change', (e) => {
522
- if (e.target.files.length > 0) {
523
- uploadFile(e.target.files[0]);
524
- }
525
- });
526
-
527
- function uploadFile(file) {
528
- const formData = new FormData();
529
- formData.append('file', file);
530
-
531
- document.getElementById('progress').classList.remove('hidden');
532
- document.getElementById('result').classList.add('hidden');
533
-
534
- fetch('/api/upload', {
535
- method: 'POST',
536
- body: formData
537
- })
538
- .then(response => response.json())
539
- .then(data => {
540
- if (data.success) {
541
- currentFileId = data.file_id;
542
- checkProcessingStatus();
543
- } else {
544
- alert('Upload failed: ' + data.error);
545
- }
546
- })
547
- .catch(error => {
548
- alert('Upload error: ' + error);
549
- });
550
- }
551
-
552
- function checkProcessingStatus() {
553
- fetch('/api/processing-status')
554
- .then(response => response.json())
555
- .then(data => {
556
- if (data.success) {
557
- if (data.processing) {
558
- // Still processing
559
- setTimeout(checkProcessingStatus, 2000);
560
- } else {
561
- // Processing complete
562
- document.getElementById('progress').classList.add('hidden');
563
- document.getElementById('result').classList.remove('hidden');
564
-
565
- // Find the latest processed file
566
- if (data.processed_files.length > 0) {
567
- const latest = data.processed_files[data.processed_files.length - 1];
568
- document.getElementById('download-btn').onclick = () => {
569
- window.open(`/api/download/${latest.output_file}`, '_blank');
570
- };
571
- }
572
- }
573
- }
574
- loadLogs(); // Update logs
575
- });
576
- }
577
-
578
- // Initialize
579
- loadSystemInfo();
580
- loadLogs();
581
- setInterval(loadLogs, 5000); // Update logs every 5 seconds
582
- </script>
583
- </body>
584
- </html>
585
- """
586
 
587
  @app.route('/api/system')
588
  def api_system():
@@ -600,7 +473,6 @@ def api_system():
600
  info["gpu_memory_used"] = f"{allocated_memory / (1024**3):.1f}GB"
601
  info["gpu_memory_free"] = f"{(total_memory - allocated_memory) / (1024**3):.1f}GB"
602
  info["cuda_version"] = torch.version.cuda
603
- info["pytorch_version"] = torch.__version__
604
  else:
605
  info["gpu_available"] = False
606
  info["gpu_name"] = "CPU Only"
@@ -608,35 +480,32 @@ def api_system():
608
  info["gpu_memory_used"] = "N/A"
609
  info["gpu_memory_free"] = "N/A"
610
  info["cuda_version"] = "Not available"
611
- info["pytorch_version"] = torch.__version__
 
612
 
613
  # Real-ESRGAN info
614
- info["realesrgan_available"] = REALESRGAN_AVAILABLE
615
  info["current_model"] = app_state.get("current_model", "None")
616
 
617
  # Storage info
618
- if os.path.exists("/data"):
619
- info["persistent_storage"] = True
620
- try:
621
- upload_files = os.listdir(UPLOAD_FOLDER) if os.path.exists(UPLOAD_FOLDER) else []
622
- output_files = os.listdir(OUTPUT_FOLDER) if os.path.exists(OUTPUT_FOLDER) else []
623
-
624
- upload_size = sum(os.path.getsize(os.path.join(UPLOAD_FOLDER, f))
625
- for f in upload_files if os.path.isfile(os.path.join(UPLOAD_FOLDER, f)))
626
- output_size = sum(os.path.getsize(os.path.join(OUTPUT_FOLDER, f))
627
- for f in output_files if os.path.isfile(os.path.join(OUTPUT_FOLDER, f)))
628
-
629
- info["storage_uploads"] = f"{upload_size / (1024**2):.1f}MB"
630
- info["storage_outputs"] = f"{output_size / (1024**2):.1f}MB"
631
- info["upload_files_count"] = len(upload_files)
632
- info["output_files_count"] = len(output_files)
633
- except Exception as e:
634
- info["storage_uploads"] = f"Error: {str(e)}"
635
- info["storage_outputs"] = "N/A"
636
- info["upload_files_count"] = 0
637
- info["output_files_count"] = 0
638
- else:
639
- info["persistent_storage"] = False
640
 
641
  return jsonify({"success": True, "data": info})
642
  except Exception as e:
@@ -666,21 +535,14 @@ def api_upload():
666
  output_path = os.path.join(OUTPUT_FOLDER, output_filename)
667
 
668
  if file_ext in ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp']:
669
- # Use Real-ESRGAN for images if available
670
- if REALESRGAN_AVAILABLE:
671
- upscale_image_realesrgan(input_path, output_path)
672
- method = "Real-ESRGAN"
673
- else:
674
- upscale_image_fallback(input_path, output_path)
675
- method = "Bicubic Fallback"
676
  media_type = "image"
677
  elif file_ext in ['mp4', 'avi', 'mov', 'mkv']:
678
  upscale_video_4k(input_path, output_path)
679
- method = "Video Upscaling"
680
  media_type = "video"
681
 
682
  log_message(f"πŸ“€ File uploaded: {filename}")
683
- log_message(f"🎯 Starting upscaling with {method}...")
684
 
685
  return jsonify({
686
  "success": True,
@@ -688,7 +550,6 @@ def api_upload():
688
  "filename": filename,
689
  "output_filename": output_filename,
690
  "media_type": media_type,
691
- "method": method,
692
  "message": "Upload successful, processing started"
693
  })
694
  else:
@@ -756,16 +617,28 @@ def api_optimize_gpu():
756
  """Optimize GPU for processing"""
757
  try:
758
  success = optimize_gpu()
759
- if success:
760
- return jsonify({"success": True, "message": "GPU optimized"})
 
 
 
 
 
 
 
 
 
 
 
 
761
  else:
762
- return jsonify({"success": False, "message": "GPU optimization failed"})
763
  except Exception as e:
764
  return jsonify({"success": False, "error": str(e)})
765
 
766
  @app.route('/api/clear-cache', methods=['POST'])
767
  def api_clear_cache():
768
- """Clear GPU cache and processed files"""
769
  try:
770
  if torch.cuda.is_available():
771
  torch.cuda.empty_cache()
@@ -777,47 +650,18 @@ def api_clear_cache():
777
  except Exception as e:
778
  return jsonify({"success": False, "error": str(e)})
779
 
780
- @app.route('/api/select-model', methods=['POST'])
781
- def api_select_model():
782
- """Select Real-ESRGAN model"""
783
- try:
784
- data = request.get_json()
785
- model_name = data.get('model_name', 'RealESRGAN_x4plus')
786
-
787
- if not REALESRGAN_AVAILABLE:
788
- return jsonify({"success": False, "error": "Real-ESRGAN not available"})
789
-
790
- # Initialize new model
791
- upscaler = initialize_realesrgan(model_name)
792
- if upscaler:
793
- return jsonify({"success": True, "message": f"Model {model_name} selected"})
794
- else:
795
- return jsonify({"success": False, "error": "Failed to initialize model"})
796
- except Exception as e:
797
- return jsonify({"success": False, "error": str(e)})
798
-
799
  if __name__ == '__main__':
800
  # Initialize system
801
- log_message("πŸš€ 4K AI Upscaler starting...")
802
 
803
  try:
804
  # Optimize GPU if available
805
  if optimize_gpu():
806
- log_message("βœ… GPU optimized")
807
- else:
808
- log_message("⚠️ GPU optimization failed, using CPU fallback")
809
-
810
- # Initialize Real-ESRGAN if available
811
- if REALESRGAN_AVAILABLE:
812
- log_message("🧠 Initializing Real-ESRGAN...")
813
- if initialize_realesrgan():
814
- log_message("βœ… Real-ESRGAN ready")
815
- else:
816
- log_message("⚠️ Real-ESRGAN initialization failed, using fallback methods")
817
  else:
818
- log_message("πŸ“ Real-ESRGAN not available, using bicubic upscaling")
819
 
820
- log_message("βœ… 4K AI Upscaler ready")
821
  log_message("πŸ“€ Upload images or videos to upscale to 4K resolution")
822
 
823
  except Exception as e:
 
11
  import numpy as np
12
  from PIL import Image
13
 
14
+ # Real-ESRGAN imports with better error handling
15
  try:
16
  from realesrgan import RealESRGANer
17
  from basicsr.archs.rrdbnet_arch import RRDBNet
 
20
  except ImportError as e:
21
  REALESRGAN_AVAILABLE = False
22
  print(f"⚠️ Real-ESRGAN not available: {e}")
 
23
 
24
  # Configuration
25
  UPLOAD_FOLDER = '/data/uploads'
 
89
  model_path = os.path.join(MODEL_FOLDER, f"{model_name}.pth")
90
  if not os.path.exists(model_path):
91
  log_message(f"πŸ“₯ Downloading {model_name}...")
92
+ try:
93
+ urllib.request.urlretrieve(url, model_path)
94
+ log_message(f"βœ… Downloaded {model_name}")
95
+ except Exception as e:
96
+ log_message(f"❌ Failed to download {model_name}: {e}")
97
+ return False
98
  return True
99
  except Exception as e:
100
  log_message(f"❌ Error downloading models: {str(e)}")
 
107
  return None
108
 
109
  try:
110
+ log_message(f"πŸ”§ Initializing Real-ESRGAN with {model_name}...")
111
+
112
  model_path = os.path.join(MODEL_FOLDER, f"{model_name}.pth")
113
 
114
  # Check if model exists, download if not
115
  if not os.path.exists(model_path):
116
  log_message(f"πŸ“₯ Model {model_name} not found, downloading...")
117
  if not download_realesrgan_models():
118
+ log_message("❌ Failed to download models")
119
  return None
120
 
121
  # Initialize model architecture
 
129
  log_message(f"❌ Unknown model: {model_name}")
130
  return None
131
 
132
+ # Always use CPU for compatibility
133
+ device = torch.device('cpu')
134
+ log_message(f"πŸ–₯️ Using device: {device}")
135
 
136
  # Initialize upscaler with conservative settings
137
  upscaler = RealESRGANer(
138
  scale=netscale,
139
  model_path=model_path,
140
  model=model,
141
+ tile=200, # Small tile size for CPU
142
  tile_pad=10,
143
  pre_pad=0,
144
+ half=False, # No half precision on CPU
145
  device=device
146
  )
147
 
 
152
 
153
  except Exception as e:
154
  log_message(f"❌ Error initializing Real-ESRGAN: {str(e)}")
155
+ app_state["upscaler"] = None
156
+ app_state["current_model"] = None
157
  return None
158
 
159
  def optimize_gpu():
 
172
  log_message("βœ… GPU optimized")
173
  return True
174
  else:
175
+ log_message("⚠️ CUDA not available, using CPU")
176
  return False
177
  except Exception as e:
178
  log_message(f"❌ Error optimizing GPU: {str(e)}")
179
  return False
180
 
181
+ def upscale_image_4k(input_path, output_path):
182
+ """Main upscaling function - tries Real-ESRGAN first, falls back to enhanced bicubic"""
183
  def process_worker():
184
  try:
185
+ log_message(f"🎨 Starting 4K upscaling: {os.path.basename(input_path)}")
186
  app_state["processing_active"] = True
187
 
188
+ start_time = time.time()
 
 
 
 
 
 
 
 
189
 
190
  # Read image
191
  img = cv2.imread(input_path, cv2.IMREAD_COLOR)
 
196
  h, w = img.shape[:2]
197
  log_message(f"πŸ“ Original resolution: {w}x{h}")
198
 
199
+ success = False
200
+ method_used = "Unknown"
 
 
 
201
 
202
+ # Try Real-ESRGAN first if available
203
+ if REALESRGAN_AVAILABLE:
204
+ try:
205
+ if app_state["upscaler"] is None:
206
+ log_message("πŸ”§ Initializing Real-ESRGAN...")
207
+ upscaler = initialize_realesrgan()
208
+ else:
209
+ upscaler = app_state["upscaler"]
210
+
211
+ if upscaler is not None:
212
+ log_message("🧠 Applying Real-ESRGAN neural upscaling...")
213
+ output, _ = upscaler.enhance(img, outscale=4)
214
+ cv2.imwrite(output_path, output)
215
+ method_used = f"Real-ESRGAN ({app_state['current_model']})"
216
+ success = True
217
+ log_message("βœ… Real-ESRGAN upscaling successful")
218
+ else:
219
+ log_message("⚠️ Real-ESRGAN initialization failed")
220
+
221
+ except Exception as e:
222
+ log_message(f"⚠️ Real-ESRGAN failed: {str(e)}")
223
+ log_message("πŸ”„ Falling back to enhanced bicubic...")
224
 
225
+ # Fallback to enhanced bicubic if Real-ESRGAN failed or not available
226
+ if not success:
227
+ log_message("πŸ”„ Using enhanced bicubic upscaling...")
 
 
228
 
229
+ target_h, target_w = h * 4, w * 4
 
230
 
231
+ if torch.cuda.is_available():
232
+ try:
233
+ # GPU enhanced bicubic
234
+ device = torch.device('cuda')
235
+ image_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
236
+ image_tensor = torch.from_numpy(image_rgb).float().to(device) / 255.0
237
+ image_tensor = image_tensor.permute(2, 0, 1).unsqueeze(0)
238
+
239
+ with torch.no_grad():
240
+ # Progressive upscaling for better quality
241
+ intermediate = torch.nn.functional.interpolate(
242
+ image_tensor,
243
+ size=(h * 2, w * 2),
244
+ mode='bicubic',
245
+ align_corners=False,
246
+ antialias=True
247
+ )
248
+
249
+ upscaled = torch.nn.functional.interpolate(
250
+ intermediate,
251
+ size=(target_h, target_w),
252
+ mode='bicubic',
253
+ align_corners=False,
254
+ antialias=True
255
+ )
256
+
257
+ # Enhanced sharpening
258
+ kernel = torch.tensor([
259
+ [-0.5, -1, -0.5],
260
+ [-1, 7, -1],
261
+ [-0.5, -1, -0.5]
262
+ ], dtype=torch.float32, device=device).unsqueeze(0).unsqueeze(0)
263
+
264
+ enhanced_channels = []
265
+ for i in range(3):
266
+ channel = upscaled[:, i:i+1, :, :]
267
+ padded = torch.nn.functional.pad(channel, (1, 1, 1, 1), mode='reflect')
268
+ enhanced = torch.nn.functional.conv2d(padded, kernel)
269
+ enhanced_channels.append(enhanced)
270
+
271
+ enhanced = torch.cat(enhanced_channels, dim=1)
272
+ final_result = torch.clamp(enhanced, 0, 1)
273
+
274
+ result_cpu = final_result.squeeze(0).permute(1, 2, 0).cpu().numpy()
275
+ result_image = (result_cpu * 255).astype(np.uint8)
276
+ result_bgr = cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR)
277
+
278
+ cv2.imwrite(output_path, result_bgr)
279
+ method_used = "Enhanced Bicubic (GPU)"
280
+ success = True
281
+ log_message("βœ… GPU enhanced bicubic completed")
282
+
283
+ except Exception as e:
284
+ log_message(f"⚠️ GPU processing failed: {e}")
285
 
286
+ if not success:
287
+ # CPU enhanced bicubic
288
+ log_message("πŸ’» Using CPU enhanced bicubic...")
289
+
290
+ # Progressive upscaling
291
+ intermediate = cv2.resize(img, (w * 2, h * 2), interpolation=cv2.INTER_CUBIC)
292
+ upscaled = cv2.resize(intermediate, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
293
+
294
+ # Apply sharpening
295
+ kernel = np.array([
296
+ [-0.5, -1, -0.5],
297
+ [-1, 7, -1],
298
+ [-0.5, -1, -0.5]
299
+ ])
300
+ sharpened = cv2.filter2D(upscaled, -1, kernel)
301
+
302
+ # Blend for final result
303
+ result = cv2.addWeighted(upscaled, 0.7, sharpened, 0.3, 0)
304
+
305
+ cv2.imwrite(output_path, result)
306
+ method_used = "Enhanced Bicubic (CPU)"
307
+ success = True
308
+ log_message("βœ… CPU enhanced bicubic completed")
309
+
310
+ if success:
311
+ # Verify output
312
+ final_img = cv2.imread(output_path)
313
+ if final_img is not None:
314
+ final_h, final_w = final_img.shape[:2]
315
+ processing_time = time.time() - start_time
316
 
317
+ log_message(f"βœ… Upscaling completed: {final_w}x{final_h}")
318
+ log_message(f"πŸ“ˆ Scale factor: {final_w/w:.1f}x")
319
+ log_message(f"⏱️ Processing time: {processing_time:.1f}s")
320
+ log_message(f"πŸ”§ Method used: {method_used}")
321
+
322
+ # Add to processed files
323
+ app_state["processed_files"].append({
324
+ "input_file": os.path.basename(input_path),
325
+ "output_file": os.path.basename(output_path),
326
+ "original_size": f"{w}x{h}",
327
+ "upscaled_size": f"{final_w}x{final_h}",
328
+ "method": method_used,
329
+ "processing_time": f"{processing_time:.1f}s",
330
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
331
+ })
332
+ else:
333
+ log_message("❌ Error: Output file could not be read")
334
+ else:
335
+ log_message("❌ All upscaling methods failed")
336
+
337
  except Exception as e:
338
+ log_message(f"❌ Critical error in upscaling: {str(e)}")
 
 
339
  finally:
340
  app_state["processing_active"] = False
341
  if torch.cuda.is_available():
 
345
  thread.daemon = True
346
  thread.start()
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  def upscale_video_4k(input_path, output_path):
349
  """Upscale video to 4K frame by frame"""
350
  def process_worker():
 
372
 
373
  # Process frames
374
  frame_num = 0
375
+ start_time = time.time()
376
+
377
  while True:
378
  ret, frame = cap.read()
379
  if not ret:
 
382
  frame_num += 1
383
 
384
  try:
385
+ # Enhanced bicubic for video frames
386
  upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
387
+
388
+ # Light sharpening
389
+ kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
390
+ sharpened = cv2.filter2D(upscaled_frame, -1, kernel)
391
+ final_frame = cv2.addWeighted(upscaled_frame, 0.8, sharpened, 0.2, 0)
392
+
393
+ out.write(final_frame)
394
 
395
  # Progress logging
396
  if frame_num % 30 == 0:
397
  progress = (frame_num / frame_count) * 100
398
+ elapsed = time.time() - start_time
399
+ eta = (elapsed / frame_num) * (frame_count - frame_num)
400
+ log_message(f"🎞️ Frame {frame_num}/{frame_count} ({progress:.1f}%) - ETA: {eta:.0f}s")
401
 
402
  except Exception as e:
403
  log_message(f"⚠️ Error processing frame {frame_num}: {e}")
 
409
  # Verify output
410
  if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
411
  file_size = os.path.getsize(output_path)
412
+ total_time = time.time() - start_time
413
  log_message(f"βœ… Video upscaling completed: {target_w}x{target_h}")
414
+ log_message(f"πŸ“ Output size: {file_size / (1024**2):.1f}MB")
415
+ log_message(f"⏱️ Total time: {total_time:.1f}s")
416
 
417
  # Add to processed files
418
  app_state["processed_files"].append({
 
422
  "upscaled_size": f"{target_w}x{target_h}",
423
  "frame_count": frame_count,
424
  "fps": fps,
425
+ "method": "Enhanced Bicubic (Video)",
426
+ "processing_time": f"{total_time:.1f}s",
427
  "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
428
  })
429
  else:
 
440
  thread.daemon = True
441
  thread.start()
442
 
443
+ # Initialize directories and try to set up Real-ESRGAN
444
  ensure_directories()
445
 
446
+ # Try to initialize Real-ESRGAN on startup
447
+ if REALESRGAN_AVAILABLE:
448
+ try:
449
+ log_message("πŸš€ Attempting to initialize Real-ESRGAN on startup...")
450
+ initialize_realesrgan()
451
+ except Exception as e:
452
+ log_message(f"⚠️ Could not initialize Real-ESRGAN on startup: {e}")
453
+
454
  app = Flask(__name__)
455
 
456
  @app.route('/')
457
  def index():
458
+ return render_template('index.html')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
 
460
  @app.route('/api/system')
461
  def api_system():
 
473
  info["gpu_memory_used"] = f"{allocated_memory / (1024**3):.1f}GB"
474
  info["gpu_memory_free"] = f"{(total_memory - allocated_memory) / (1024**3):.1f}GB"
475
  info["cuda_version"] = torch.version.cuda
 
476
  else:
477
  info["gpu_available"] = False
478
  info["gpu_name"] = "CPU Only"
 
480
  info["gpu_memory_used"] = "N/A"
481
  info["gpu_memory_free"] = "N/A"
482
  info["cuda_version"] = "Not available"
483
+
484
+ info["pytorch_version"] = torch.__version__
485
 
486
  # Real-ESRGAN info
487
+ info["realesrgan_available"] = REALESRGAN_AVAILABLE and app_state["upscaler"] is not None
488
  info["current_model"] = app_state.get("current_model", "None")
489
 
490
  # Storage info
491
+ try:
492
+ upload_files = os.listdir(UPLOAD_FOLDER) if os.path.exists(UPLOAD_FOLDER) else []
493
+ output_files = os.listdir(OUTPUT_FOLDER) if os.path.exists(OUTPUT_FOLDER) else []
494
+
495
+ upload_size = sum(os.path.getsize(os.path.join(UPLOAD_FOLDER, f))
496
+ for f in upload_files if os.path.isfile(os.path.join(UPLOAD_FOLDER, f)))
497
+ output_size = sum(os.path.getsize(os.path.join(OUTPUT_FOLDER, f))
498
+ for f in output_files if os.path.isfile(os.path.join(OUTPUT_FOLDER, f)))
499
+
500
+ info["storage_uploads"] = f"{upload_size / (1024**2):.1f}MB"
501
+ info["storage_outputs"] = f"{output_size / (1024**2):.1f}MB"
502
+ info["upload_files_count"] = len(upload_files)
503
+ info["output_files_count"] = len(output_files)
504
+ except Exception as e:
505
+ info["storage_uploads"] = "Error"
506
+ info["storage_outputs"] = "Error"
507
+ info["upload_files_count"] = 0
508
+ info["output_files_count"] = 0
 
 
 
 
509
 
510
  return jsonify({"success": True, "data": info})
511
  except Exception as e:
 
535
  output_path = os.path.join(OUTPUT_FOLDER, output_filename)
536
 
537
  if file_ext in ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp']:
538
+ upscale_image_4k(input_path, output_path)
 
 
 
 
 
 
539
  media_type = "image"
540
  elif file_ext in ['mp4', 'avi', 'mov', 'mkv']:
541
  upscale_video_4k(input_path, output_path)
 
542
  media_type = "video"
543
 
544
  log_message(f"πŸ“€ File uploaded: {filename}")
545
+ log_message(f"🎯 Starting 4K upscaling process...")
546
 
547
  return jsonify({
548
  "success": True,
 
550
  "filename": filename,
551
  "output_filename": output_filename,
552
  "media_type": media_type,
 
553
  "message": "Upload successful, processing started"
554
  })
555
  else:
 
617
  """Optimize GPU for processing"""
618
  try:
619
  success = optimize_gpu()
620
+ return jsonify({"success": success})
621
+ except Exception as e:
622
+ return jsonify({"success": False, "error": str(e)})
623
+
624
+ @app.route('/api/init-realesrgan', methods=['POST'])
625
+ def api_init_realesrgan():
626
+ """Initialize Real-ESRGAN manually"""
627
+ try:
628
+ if not REALESRGAN_AVAILABLE:
629
+ return jsonify({"success": False, "error": "Real-ESRGAN not available"})
630
+
631
+ upscaler = initialize_realesrgan()
632
+ if upscaler:
633
+ return jsonify({"success": True, "message": "Real-ESRGAN initialized successfully"})
634
  else:
635
+ return jsonify({"success": False, "error": "Failed to initialize Real-ESRGAN"})
636
  except Exception as e:
637
  return jsonify({"success": False, "error": str(e)})
638
 
639
  @app.route('/api/clear-cache', methods=['POST'])
640
  def api_clear_cache():
641
+ """Clear cache and processed files"""
642
  try:
643
  if torch.cuda.is_available():
644
  torch.cuda.empty_cache()
 
650
  except Exception as e:
651
  return jsonify({"success": False, "error": str(e)})
652
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  if __name__ == '__main__':
654
  # Initialize system
655
+ log_message("πŸš€ 4K Upscaler starting...")
656
 
657
  try:
658
  # Optimize GPU if available
659
  if optimize_gpu():
660
+ log_message("βœ… GPU optimization completed")
 
 
 
 
 
 
 
 
 
 
661
  else:
662
+ log_message("⚠️ Using CPU mode")
663
 
664
+ log_message("βœ… 4K Upscaler ready")
665
  log_message("πŸ“€ Upload images or videos to upscale to 4K resolution")
666
 
667
  except Exception as e: