Saravutw commited on
Commit
4481c8e
·
verified ·
1 Parent(s): f9943df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -830
app.py CHANGED
@@ -1,833 +1,93 @@
1
- from flask import Flask, render_template, jsonify, request, send_file
2
- import torch
3
- import os
4
- import time
5
- import threading
6
- from datetime import datetime, timedelta
7
- import cv2
8
- from werkzeug.utils import secure_filename
9
- import uuid
10
- import mimetypes
11
- import numpy as np
12
  from PIL import Image
13
- import schedule
14
-
15
- # Configuration
16
- UPLOAD_FOLDER = '/data/uploads'
17
- OUTPUT_FOLDER = '/data/outputs'
18
- CLEANUP_INTERVAL_MINUTES = 10
19
- FILE_MAX_AGE_HOURS = 1
20
 
21
- # Global application state
22
- app_state = {
23
- "cuda_available": torch.cuda.is_available(),
24
- "processing_active": False,
25
- "logs": [],
26
- "processed_files": [],
27
- "cleanup_stats": {
28
- "last_cleanup": None,
29
- "files_deleted": 0,
30
- "space_freed_mb": 0
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
32
- }
33
-
34
- def ensure_directories():
35
- """Create necessary directories"""
36
- directories = [UPLOAD_FOLDER, OUTPUT_FOLDER]
37
- for directory in directories:
38
- try:
39
- os.makedirs(directory, exist_ok=True)
40
- print(f" Directory verified: {directory}")
41
- except Exception as e:
42
- print(f"⚠️ Error creating directory {directory}: {e}")
43
-
44
- def allowed_file(filename):
45
- """Check if file has allowed extension"""
46
- return '.' in filename and \
47
- filename.rsplit('.', 1)[1].lower() in ['png', 'jpg', 'jpeg', 'gif', 'mp4', 'avi', 'mov', 'mkv']
48
-
49
- def get_file_mimetype(filename):
50
- """Get correct mimetype for file"""
51
- mimetype, _ = mimetypes.guess_type(filename)
52
- if mimetype is None:
53
- ext = filename.lower().rsplit('.', 1)[1] if '.' in filename else ''
54
- if ext in ['mp4', 'avi', 'mov', 'mkv']:
55
- mimetype = f'video/{ext}'
56
- elif ext in ['png', 'jpg', 'jpeg', 'gif']:
57
- mimetype = f'image/{ext}'
58
- else:
59
- mimetype = 'application/octet-stream'
60
- return mimetype
61
-
62
- def log_message(message):
63
- """Add message to log with timestamp"""
64
- timestamp = datetime.now().strftime("%H:%M:%S")
65
- app_state["logs"].append(f"[{timestamp}] {message}")
66
- if len(app_state["logs"]) > 100:
67
- app_state["logs"] = app_state["logs"][-100:]
68
- print(f"[{timestamp}] {message}")
69
-
70
- def cleanup_old_files():
71
- """Delete files older than FILE_MAX_AGE_HOURS"""
72
- try:
73
- current_time = datetime.now()
74
- cutoff_time = current_time - timedelta(hours=FILE_MAX_AGE_HOURS)
75
-
76
- files_deleted = 0
77
- space_freed = 0
78
-
79
- # Clean upload folder
80
- for folder_path in [UPLOAD_FOLDER, OUTPUT_FOLDER]:
81
- if not os.path.exists(folder_path):
82
- continue
83
-
84
- for filename in os.listdir(folder_path):
85
- file_path = os.path.join(folder_path, filename)
86
-
87
- if os.path.isfile(file_path):
88
- try:
89
- # Get file modification time
90
- file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
91
-
92
- if file_time < cutoff_time:
93
- # Get file size before deletion
94
- file_size = os.path.getsize(file_path)
95
-
96
- # Delete the file
97
- os.remove(file_path)
98
-
99
- files_deleted += 1
100
- space_freed += file_size
101
-
102
- log_message(f"🗑️ Deleted old file: {filename} ({file_size / (1024*1024):.1f}MB)")
103
-
104
- except Exception as e:
105
- log_message(f"⚠️ Error deleting {filename}: {str(e)}")
106
-
107
- # Update cleanup stats
108
- app_state["cleanup_stats"]["last_cleanup"] = current_time.strftime("%Y-%m-%d %H:%M:%S")
109
- app_state["cleanup_stats"]["files_deleted"] += files_deleted
110
- app_state["cleanup_stats"]["space_freed_mb"] += space_freed / (1024*1024)
111
-
112
- if files_deleted > 0:
113
- log_message(f"🧹 Cleanup completed: {files_deleted} files deleted, {space_freed / (1024*1024):.1f}MB freed")
114
- else:
115
- log_message(f"🧹 Cleanup completed: No old files to delete")
116
-
117
- # Clean up processed files list to remove references to deleted files
118
- valid_processed_files = []
119
- for file_info in app_state["processed_files"]:
120
- output_path = os.path.join(OUTPUT_FOLDER, file_info["output_file"])
121
- if os.path.exists(output_path):
122
- valid_processed_files.append(file_info)
123
-
124
- app_state["processed_files"] = valid_processed_files
125
-
126
- except Exception as e:
127
- log_message(f"❌ Error during cleanup: {str(e)}")
128
-
129
- def run_scheduler():
130
- """Run the file cleanup scheduler in background"""
131
- def scheduler_worker():
132
- while True:
133
- try:
134
- schedule.run_pending()
135
- time.sleep(60) # Check every minute
136
- except Exception as e:
137
- log_message(f"❌ Scheduler error: {str(e)}")
138
- time.sleep(300) # Wait 5 minutes before retrying
139
-
140
- thread = threading.Thread(target=scheduler_worker, daemon=True)
141
- thread.start()
142
- log_message(f"🕒 File cleanup scheduler started (every {CLEANUP_INTERVAL_MINUTES} minutes)")
143
-
144
- def optimize_gpu():
145
- """Optimize GPU configuration for 4K upscaling"""
146
- try:
147
- if torch.cuda.is_available():
148
- torch.backends.cudnn.benchmark = True
149
- torch.backends.cudnn.allow_tf32 = True
150
- torch.backends.cuda.matmul.allow_tf32 = True
151
- torch.cuda.empty_cache()
152
-
153
- # Test GPU
154
- test_tensor = torch.randn(100, 100, device='cuda')
155
- _ = torch.mm(test_tensor, test_tensor)
156
-
157
- log_message("✅ GPU optimized for 4K upscaling")
158
- return True
159
- else:
160
- log_message("⚠️ CUDA not available")
161
- return False
162
- except Exception as e:
163
- log_message(f"❌ Error optimizing GPU: {str(e)}")
164
- return False
165
-
166
- def upscale_image_4k(input_path, output_path):
167
- """Upscale image to 4K using neural methods"""
168
- def process_worker():
169
- try:
170
- log_message(f"🎨 Starting 4K upscaling: {os.path.basename(input_path)}")
171
- app_state["processing_active"] = True
172
-
173
- # Read original image
174
- image = cv2.imread(input_path)
175
- if image is None:
176
- log_message("❌ Error: Could not read image")
177
- return
178
-
179
- h, w = image.shape[:2]
180
- log_message(f"📏 Original resolution: {w}x{h}")
181
-
182
- # Define target dimensions first
183
- target_h, target_w = h * 4, w * 4
184
-
185
- # Check GPU memory availability
186
- if torch.cuda.is_available():
187
- device = torch.device('cuda')
188
- available_memory = torch.cuda.get_device_properties(0).total_memory - torch.cuda.memory_allocated()
189
- required_memory = w * h * 4 * 4 * 3 * 4 # Conservative estimation
190
-
191
- if required_memory > available_memory * 0.8:
192
- log_message(f"⚠️ Image too large for available GPU memory, using CPU")
193
- device = torch.device('cpu')
194
- else:
195
- log_message(f"🚀 Using GPU: {torch.cuda.get_device_name()}")
196
-
197
- if device.type == 'cuda':
198
- # Convert image to normalized tensor
199
- image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
200
- image_tensor = torch.from_numpy(image_rgb).float().to(device) / 255.0
201
- image_tensor = image_tensor.permute(2, 0, 1).unsqueeze(0) # BCHW format
202
-
203
- log_message("🧠 Applying neural upscaling...")
204
-
205
- with torch.no_grad():
206
- # Step 1: 2x upscaling with bicubic
207
- intermediate = torch.nn.functional.interpolate(
208
- image_tensor,
209
- size=(h * 2, w * 2),
210
- mode='bicubic',
211
- align_corners=False,
212
- antialias=True
213
- )
214
-
215
- # Step 2: Final 2x upscaling with smoothing
216
- upscaled = torch.nn.functional.interpolate(
217
- intermediate,
218
- size=(target_h, target_w),
219
- mode='bicubic',
220
- align_corners=False,
221
- antialias=True
222
- )
223
-
224
- # Enhanced sharpening filters
225
- kernel_size = 3
226
- sigma = 0.5
227
- kernel = torch.zeros((kernel_size, kernel_size), device=device)
228
- center = kernel_size // 2
229
-
230
- # Create inverted Gaussian kernel for sharpening
231
- for i in range(kernel_size):
232
- for j in range(kernel_size):
233
- dist = ((i - center) ** 2 + (j - center) ** 2) ** 0.5
234
- kernel[i, j] = torch.exp(-0.5 * (dist / sigma) ** 2)
235
-
236
- kernel = kernel / kernel.sum()
237
- sharpen_kernel = torch.zeros_like(kernel)
238
- sharpen_kernel[center, center] = 2.0
239
- sharpen_kernel = sharpen_kernel - kernel
240
- sharpen_kernel = sharpen_kernel.unsqueeze(0).unsqueeze(0)
241
-
242
- # Apply sharpening to each channel
243
- enhanced_channels = []
244
- for i in range(3):
245
- channel = upscaled[:, i:i+1, :, :]
246
- padded = torch.nn.functional.pad(channel, (1, 1, 1, 1), mode='reflect')
247
- enhanced = torch.nn.functional.conv2d(padded, sharpen_kernel)
248
- enhanced_channels.append(enhanced)
249
-
250
- enhanced = torch.cat(enhanced_channels, dim=1)
251
-
252
- # Light smoothing to reduce noise
253
- gaussian_kernel = torch.tensor([
254
- [1, 4, 6, 4, 1],
255
- [4, 16, 24, 16, 4],
256
- [6, 24, 36, 24, 6],
257
- [4, 16, 24, 16, 4],
258
- [1, 4, 6, 4, 1]
259
- ], dtype=torch.float32, device=device).unsqueeze(0).unsqueeze(0) / 256.0
260
-
261
- smoothed_channels = []
262
- for i in range(3):
263
- channel = enhanced[:, i:i+1, :, :]
264
- padded = torch.nn.functional.pad(channel, (2, 2, 2, 2), mode='reflect')
265
- smoothed = torch.nn.functional.conv2d(padded, gaussian_kernel)
266
- smoothed_channels.append(smoothed)
267
-
268
- smoothed = torch.cat(smoothed_channels, dim=1)
269
-
270
- # Blend: 70% enhanced + 30% smoothed for quality/smoothness balance
271
- final_result = 0.7 * enhanced + 0.3 * smoothed
272
-
273
- # Clamp values and optimize contrast
274
- final_result = torch.clamp(final_result, 0, 1)
275
-
276
- # Adaptive contrast optimization
277
- for i in range(3):
278
- channel = final_result[:, i, :, :]
279
- min_val = channel.min()
280
- max_val = channel.max()
281
- if max_val > min_val:
282
- final_result[:, i, :, :] = (channel - min_val) / (max_val - min_val)
283
-
284
- # Convert back to image
285
- result_cpu = final_result.squeeze(0).permute(1, 2, 0).cpu().numpy()
286
- result_image = (result_cpu * 255).astype(np.uint8)
287
- result_bgr = cv2.cvtColor(result_image, cv2.COLOR_RGB2BGR)
288
-
289
- # Save result
290
- cv2.imwrite(output_path, result_bgr)
291
- final_h, final_w = result_bgr.shape[:2]
292
- log_message(f"✅ Upscaling completed: {final_w}x{final_h}")
293
- log_message(f"📈 Scale factor: {final_w/w:.1f}x")
294
-
295
- # Memory cleanup
296
- del image_tensor, upscaled, enhanced, final_result
297
- torch.cuda.empty_cache()
298
-
299
- else:
300
- # CPU fallback
301
- log_message("⚠️ Using CPU - optimized processing")
302
-
303
- # Progressive upscaling on CPU
304
- intermediate = cv2.resize(image, (w * 2, h * 2), interpolation=cv2.INTER_CUBIC)
305
- upscaled = cv2.resize(intermediate, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
306
-
307
- # Apply sharpening on CPU
308
- kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
309
- sharpened = cv2.filter2D(upscaled, -1, kernel)
310
-
311
- # Blend for smoothing
312
- final_result = cv2.addWeighted(upscaled, 0.7, sharpened, 0.3, 0)
313
-
314
- cv2.imwrite(output_path, final_result)
315
- log_message(f"✅ CPU upscaling completed: {target_w}x{target_h}")
316
- else:
317
- # CPU only fallback (no CUDA available)
318
- log_message("💻 Using CPU processing (CUDA not available)")
319
-
320
- # Progressive upscaling on CPU
321
- intermediate = cv2.resize(image, (w * 2, h * 2), interpolation=cv2.INTER_CUBIC)
322
- upscaled = cv2.resize(intermediate, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
323
-
324
- # Apply sharpening on CPU
325
- kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]])
326
- sharpened = cv2.filter2D(upscaled, -1, kernel)
327
-
328
- # Blend for smoothing
329
- final_result = cv2.addWeighted(upscaled, 0.7, sharpened, 0.3, 0)
330
-
331
- cv2.imwrite(output_path, final_result)
332
- log_message(f"✅ CPU upscaling completed: {target_w}x{target_h}")
333
-
334
- # Add to processed files list
335
- app_state["processed_files"].append({
336
- "input_file": os.path.basename(input_path),
337
- "output_file": os.path.basename(output_path),
338
- "original_size": f"{w}x{h}",
339
- "upscaled_size": f"{target_w}x{target_h}",
340
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
341
- })
342
-
343
- except Exception as e:
344
- log_message(f"❌ Error in processing: {str(e)}")
345
- finally:
346
- app_state["processing_active"] = False
347
- if torch.cuda.is_available():
348
- torch.cuda.empty_cache()
349
-
350
- thread = threading.Thread(target=process_worker)
351
- thread.daemon = True
352
- thread.start()
353
-
354
- def upscale_video_4k(input_path, output_path):
355
- """Upscale video to 4K frame by frame"""
356
- def process_worker():
357
- try:
358
- log_message(f"🎬 Starting 4K video upscaling: {os.path.basename(input_path)}")
359
- app_state["processing_active"] = True
360
-
361
- # Open video
362
- cap = cv2.VideoCapture(input_path)
363
- if not cap.isOpened():
364
- log_message("❌ Error: Could not open video")
365
- return
366
-
367
- # Get video properties
368
- fps = int(cap.get(cv2.CAP_PROP_FPS))
369
- frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
370
- w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
371
- h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
372
- log_message(f"📹 Video: {w}x{h}, {fps}FPS, {frame_count} frames")
373
-
374
- # Configure 4K output
375
- target_w, target_h = w * 4, h * 4
376
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
377
- out = cv2.VideoWriter(output_path, fourcc, fps, (target_w, target_h))
378
-
379
- if torch.cuda.is_available():
380
- device = torch.device('cuda')
381
- log_message(f"🚀 Processing with GPU: {torch.cuda.get_device_name()}")
382
- process_frames_gpu(cap, out, device, target_h, target_w, frame_count)
383
- else:
384
- log_message("💻 Processing with CPU (may be slower)")
385
- process_frames_cpu(cap, out, target_h, target_w, frame_count)
386
-
387
- cap.release()
388
- out.release()
389
-
390
- # Verify the output file was created and has content
391
- if os.path.exists(output_path):
392
- file_size = os.path.getsize(output_path)
393
- if file_size > 0:
394
- log_message(f"✅ 4K video completed: {target_w}x{target_h}")
395
- log_message(f"📁 Output file size: {file_size / (1024**2):.1f}MB")
396
- else:
397
- log_message(f"❌ Output file is empty: {output_path}")
398
- raise Exception("Output video file is empty")
399
- else:
400
- log_message(f"❌ Output file not created: {output_path}")
401
- raise Exception("Output video file was not created")
402
-
403
- # Add to processed files list
404
- app_state["processed_files"].append({
405
- "input_file": os.path.basename(input_path),
406
- "output_file": os.path.basename(output_path),
407
- "original_size": f"{w}x{h}",
408
- "upscaled_size": f"{target_w}x{target_h}",
409
- "frame_count": frame_count,
410
- "fps": fps,
411
- "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
412
- })
413
-
414
- except Exception as e:
415
- log_message(f"❌ Error processing video: {str(e)}")
416
- finally:
417
- app_state["processing_active"] = False
418
- if torch.cuda.is_available():
419
- torch.cuda.empty_cache()
420
-
421
- thread = threading.Thread(target=process_worker)
422
- thread.daemon = True
423
- thread.start()
424
-
425
- def process_frames_cpu(cap, out, target_h, target_w, frame_count):
426
- """Process video frames using CPU"""
427
- frame_num = 0
428
- while True:
429
- ret, frame = cap.read()
430
- if not ret:
431
- break
432
-
433
- frame_num += 1
434
-
435
- # Simple CPU upscaling
436
- upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
437
- out.write(upscaled_frame)
438
-
439
- # Progress logging
440
- if frame_num % 30 == 0:
441
- progress = (frame_num / frame_count) * 100
442
- log_message(f"🎞️ Processing frame {frame_num}/{frame_count} ({progress:.1f}%)")
443
-
444
- def process_frames_gpu(cap, out, device, target_h, target_w, frame_count):
445
- """Process video frames using GPU with PyTorch"""
446
- frame_num = 0
447
- torch.backends.cudnn.benchmark = True
448
-
449
- while True:
450
- ret, frame = cap.read()
451
- if not ret:
452
- break
453
-
454
- frame_num += 1
455
-
456
- try:
457
- # Convert to tensor
458
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
459
- frame_tensor = torch.from_numpy(frame_rgb).float().to(device) / 255.0
460
- frame_tensor = frame_tensor.permute(2, 0, 1).unsqueeze(0)
461
-
462
- with torch.no_grad():
463
- upscaled = torch.nn.functional.interpolate(
464
- frame_tensor,
465
- size=(target_h, target_w),
466
- mode='bicubic',
467
- align_corners=False
468
- )
469
-
470
- # Convert back
471
- result_cpu = upscaled.squeeze(0).permute(1, 2, 0).cpu().numpy()
472
- result_frame = (result_cpu * 255).astype(np.uint8)
473
- result_bgr = cv2.cvtColor(result_frame, cv2.COLOR_RGB2BGR)
474
- out.write(result_bgr)
475
-
476
- except Exception as e:
477
- log_message(f"⚠️ GPU processing failed for frame {frame_num}, using CPU fallback")
478
- # CPU fallback
479
- upscaled_frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_CUBIC)
480
- out.write(upscaled_frame)
481
-
482
- # Progress logging
483
- if frame_num % 30 == 0:
484
- progress = (frame_num / frame_count) * 100
485
- log_message(f"🎞️ Processing frame {frame_num}/{frame_count} ({progress:.1f}%)")
486
-
487
- # Periodic memory cleanup
488
- if frame_num % 60 == 0 and torch.cuda.is_available():
489
- torch.cuda.empty_cache()
490
-
491
- def process_frame_batch(frame_batch, out, device, target_h, target_w):
492
- """Process batch of frames on GPU for efficiency"""
493
- try:
494
- with torch.no_grad():
495
- # Convert batch to tensor
496
- batch_tensors = []
497
- for frame in frame_batch:
498
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
499
- frame_tensor = torch.from_numpy(frame_rgb).float().to(device) / 255.0
500
- frame_tensor = frame_tensor.permute(2, 0, 1) # CHW
501
- batch_tensors.append(frame_tensor)
502
-
503
- # Stack in batch
504
- batch_tensor = torch.stack(batch_tensors, dim=0) # BCHW
505
-
506
- # Upscale entire batch
507
- upscaled_batch = torch.nn.functional.interpolate(
508
- batch_tensor,
509
- size=(target_h, target_w),
510
- mode='bicubic',
511
- align_corners=False,
512
- antialias=True
513
- )
514
-
515
- # Convert each frame back
516
- for i in range(upscaled_batch.shape[0]):
517
- result_cpu = upscaled_batch[i].permute(1, 2, 0).cpu().numpy()
518
- result_frame = (result_cpu * 255).astype(np.uint8)
519
- result_bgr = cv2.cvtColor(result_frame, cv2.COLOR_RGB2BGR)
520
- out.write(result_bgr)
521
-
522
- except Exception as e:
523
- log_message(f"❌ Error in batch processing: {str(e)}")
524
- # Fallback: process frames individually
525
- for frame in frame_batch:
526
- frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
527
- frame_tensor = torch.from_numpy(frame_rgb).float().to(device) / 255.0
528
- frame_tensor = frame_tensor.permute(2, 0, 1).unsqueeze(0)
529
-
530
- upscaled = torch.nn.functional.interpolate(
531
- frame_tensor,
532
- size=(target_h, target_w),
533
- mode='bicubic',
534
- align_corners=False
535
- )
536
-
537
- result_cpu = upscaled.squeeze(0).permute(1, 2, 0).cpu().numpy()
538
- result_frame = (result_cpu * 255).astype(np.uint8)
539
- result_bgr = cv2.cvtColor(result_frame, cv2.COLOR_RGB2BGR)
540
- out.write(result_bgr)
541
-
542
- # Initialize directories
543
- ensure_directories()
544
-
545
- # Set up file cleanup scheduler
546
- schedule.every(CLEANUP_INTERVAL_MINUTES).minutes.do(cleanup_old_files)
547
-
548
- app = Flask(__name__)
549
-
550
- @app.route('/')
551
- def index():
552
- return render_template('index.html')
553
-
554
- @app.route('/api/system')
555
- def api_system():
556
- """Get system information"""
557
- try:
558
- info = {}
559
-
560
- # GPU Info
561
- if torch.cuda.is_available():
562
- info["gpu_available"] = True
563
- info["gpu_name"] = torch.cuda.get_device_name()
564
-
565
- total_memory = torch.cuda.get_device_properties(0).total_memory
566
- allocated_memory = torch.cuda.memory_allocated()
567
-
568
- info["gpu_memory"] = f"{total_memory / (1024**3):.1f}GB"
569
- info["gpu_memory_used"] = f"{allocated_memory / (1024**3):.1f}GB"
570
- info["gpu_memory_free"] = f"{(total_memory - allocated_memory) / (1024**3):.1f}GB"
571
- info["cuda_version"] = torch.version.cuda
572
- info["pytorch_version"] = torch.__version__
573
- else:
574
- info["gpu_available"] = False
575
- info["gpu_name"] = "CPU Only (No GPU detected)"
576
- info["gpu_memory"] = "N/A"
577
- info["gpu_memory_used"] = "N/A"
578
- info["gpu_memory_free"] = "N/A"
579
- info["cuda_version"] = "Not available"
580
- info["pytorch_version"] = torch.__version__
581
-
582
- # Storage info
583
- if os.path.exists("/data"):
584
- info["persistent_storage"] = True
585
- try:
586
- upload_files = os.listdir(UPLOAD_FOLDER) if os.path.exists(UPLOAD_FOLDER) else []
587
- output_files = os.listdir(OUTPUT_FOLDER) if os.path.exists(OUTPUT_FOLDER) else []
588
-
589
- upload_size = sum(os.path.getsize(os.path.join(UPLOAD_FOLDER, f))
590
- for f in upload_files if os.path.isfile(os.path.join(UPLOAD_FOLDER, f)))
591
- output_size = sum(os.path.getsize(os.path.join(OUTPUT_FOLDER, f))
592
- for f in output_files if os.path.isfile(os.path.join(OUTPUT_FOLDER, f)))
593
-
594
- info["storage_uploads"] = f"{upload_size / (1024**2):.1f}MB"
595
- info["storage_outputs"] = f"{output_size / (1024**2):.1f}MB"
596
- info["upload_files_count"] = len(upload_files)
597
- info["output_files_count"] = len(output_files)
598
-
599
- # Add cleanup info
600
- info["cleanup_stats"] = app_state["cleanup_stats"]
601
- info["cleanup_interval"] = f"{CLEANUP_INTERVAL_MINUTES} minutes"
602
- info["file_max_age"] = f"{FILE_MAX_AGE_HOURS} hour(s)"
603
-
604
- except Exception as e:
605
- info["storage_uploads"] = f"Error: {str(e)}"
606
- info["storage_outputs"] = "N/A"
607
- info["upload_files_count"] = 0
608
- info["output_files_count"] = 0
609
- else:
610
- info["persistent_storage"] = False
611
-
612
- return jsonify({"success": True, "data": info})
613
- except Exception as e:
614
- return jsonify({"success": False, "error": str(e)})
615
-
616
- @app.route('/api/upload', methods=['POST'])
617
- def api_upload():
618
- """Upload and process file for 4K upscaling"""
619
- try:
620
- if 'file' not in request.files:
621
- return jsonify({"success": False, "error": "No file provided"})
622
-
623
- file = request.files['file']
624
- if file.filename == '':
625
- return jsonify({"success": False, "error": "No file selected"})
626
-
627
- if file and allowed_file(file.filename):
628
- file_id = str(uuid.uuid4())
629
- filename = secure_filename(file.filename)
630
- file_ext = filename.rsplit('.', 1)[1].lower()
631
-
632
- input_filename = f"{file_id}_input.{file_ext}"
633
- input_path = os.path.join(UPLOAD_FOLDER, input_filename)
634
- file.save(input_path)
635
-
636
- output_filename = f"{file_id}_4k.{file_ext}"
637
- output_path = os.path.join(OUTPUT_FOLDER, output_filename)
638
-
639
- if file_ext in ['png', 'jpg', 'jpeg', 'gif']:
640
- upscale_image_4k(input_path, output_path)
641
- media_type = "image"
642
- elif file_ext in ['mp4', 'avi', 'mov', 'mkv']:
643
- upscale_video_4k(input_path, output_path)
644
- media_type = "video"
645
-
646
- log_message(f"📤 File uploaded: {filename}")
647
- log_message(f"🎯 Starting 4K transformation...")
648
-
649
- return jsonify({
650
- "success": True,
651
- "file_id": file_id,
652
- "filename": filename,
653
- "output_filename": output_filename,
654
- "media_type": media_type,
655
- "message": "Upload successful, processing started"
656
- })
657
- else:
658
- return jsonify({"success": False, "error": "File type not allowed"})
659
- except Exception as e:
660
- return jsonify({"success": False, "error": str(e)})
661
-
662
- @app.route('/api/processing-status')
663
- def api_processing_status():
664
- """Get processing status"""
665
- return jsonify({
666
- "success": True,
667
- "processing": app_state["processing_active"],
668
- "processed_files": app_state["processed_files"]
669
- })
670
-
671
- @app.route('/api/download/<filename>')
672
- def api_download(filename):
673
- """Download processed file"""
674
- try:
675
- file_path = os.path.join(OUTPUT_FOLDER, filename)
676
- if os.path.exists(file_path):
677
- mimetype = get_file_mimetype(filename)
678
- file_ext = filename.lower().rsplit('.', 1)[1] if '.' in filename else ''
679
-
680
- if file_ext in ['mp4', 'avi', 'mov', 'mkv']:
681
- return send_file(
682
- file_path,
683
- as_attachment=True,
684
- download_name=f"4k_upscaled_{filename}",
685
- mimetype=mimetype
686
- )
687
- else:
688
- return send_file(
689
- file_path,
690
- as_attachment=True,
691
- download_name=f"4k_upscaled_{filename}",
692
- mimetype=mimetype
693
- )
694
- else:
695
- return jsonify({"error": "File not found"}), 404
696
- except Exception as e:
697
- return jsonify({"error": str(e)}), 500
698
-
699
- @app.route('/api/preview/<filename>')
700
- def api_preview(filename):
701
- """Preview processed file"""
702
- try:
703
- file_path = os.path.join(OUTPUT_FOLDER, filename)
704
- if os.path.exists(file_path):
705
- mimetype = get_file_mimetype(filename)
706
- return send_file(file_path, mimetype=mimetype)
707
- else:
708
- return jsonify({"error": "File not found"}), 404
709
- except Exception as e:
710
- return jsonify({"error": str(e)}), 500
711
-
712
- @app.route('/api/logs')
713
- def api_logs():
714
- """Get application logs"""
715
- return jsonify({
716
- "success": True,
717
- "logs": app_state["logs"]
718
- })
719
-
720
- @app.route('/api/clear-logs', methods=['POST'])
721
- def api_clear_logs():
722
- """Clear application logs"""
723
- app_state["logs"] = []
724
- log_message("🧹 Logs cleared")
725
- return jsonify({"success": True, "message": "Logs cleared"})
726
-
727
- @app.route('/api/optimize-gpu', methods=['POST'])
728
- def api_optimize_gpu():
729
- """Optimize GPU for processing"""
730
- try:
731
- success = optimize_gpu()
732
- if success:
733
- return jsonify({"success": True, "message": "GPU optimized"})
734
- else:
735
- return jsonify({"success": False, "message": "GPU optimization failed"})
736
- except Exception as e:
737
- return jsonify({"success": False, "error": str(e)})
738
-
739
- @app.route('/api/clear-cache', methods=['POST'])
740
- def api_clear_cache():
741
- """Clear GPU cache and processed files"""
742
- try:
743
- if torch.cuda.is_available():
744
- torch.cuda.empty_cache()
745
-
746
- app_state["processed_files"] = []
747
- log_message("🧹 Cache and history cleared")
748
-
749
- return jsonify({"success": True, "message": "Cache cleared"})
750
- except Exception as e:
751
- return jsonify({"success": False, "error": str(e)})
752
-
753
- @app.route('/api/cleanup-now', methods=['POST'])
754
- def api_cleanup_now():
755
- """Manually trigger file cleanup"""
756
- try:
757
- cleanup_old_files()
758
- return jsonify({"success": True, "message": "Manual cleanup completed"})
759
- except Exception as e:
760
- return jsonify({"success": False, "error": str(e)})
761
-
762
- @app.route('/api/storage-stats')
763
- def api_storage_stats():
764
- """Get detailed storage statistics"""
765
- try:
766
- stats = {
767
- "cleanup_stats": app_state["cleanup_stats"],
768
- "current_files": {},
769
- "total_storage_mb": 0
770
- }
771
-
772
- for folder_name, folder_path in [("uploads", UPLOAD_FOLDER), ("outputs", OUTPUT_FOLDER)]:
773
- if os.path.exists(folder_path):
774
- files = []
775
- total_size = 0
776
-
777
- for filename in os.listdir(folder_path):
778
- file_path = os.path.join(folder_path, filename)
779
- if os.path.isfile(file_path):
780
- file_size = os.path.getsize(file_path)
781
- file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
782
-
783
- files.append({
784
- "name": filename,
785
- "size_mb": file_size / (1024*1024),
786
- "created": file_time.strftime("%Y-%m-%d %H:%M:%S"),
787
- "age_hours": (datetime.now() - file_time).total_seconds() / 3600
788
- })
789
- total_size += file_size
790
-
791
- stats["current_files"][folder_name] = {
792
- "files": files,
793
- "count": len(files),
794
- "total_size_mb": total_size / (1024*1024)
795
- }
796
- stats["total_storage_mb"] += total_size / (1024*1024)
797
-
798
- return jsonify({"success": True, "data": stats})
799
- except Exception as e:
800
- return jsonify({"success": False, "error": str(e)})
801
-
802
- if __name__ == '__main__':
803
- # Initialize system
804
- log_message("🚀 4K Upscaler starting...")
805
-
806
- try:
807
- # Start file cleanup scheduler
808
- run_scheduler()
809
-
810
- # Optimize GPU if available
811
- if optimize_gpu():
812
- log_message("✅ GPU optimized for 4K upscaling")
813
- else:
814
- log_message("⚠️ GPU optimization failed, using CPU fallback")
815
-
816
- # Run initial cleanup
817
- log_message("🧹 Running initial file cleanup...")
818
- cleanup_old_files()
819
-
820
- log_message("✅ 4K Upscaler ready")
821
- log_message("📤 Upload images or videos to upscale to 4K resolution")
822
- log_message(f"🗑️ Files will be automatically deleted after {FILE_MAX_AGE_HOURS} hour(s)")
823
-
824
- except Exception as e:
825
- log_message(f"❌ Initialization error: {str(e)}")
826
- log_message("⚠️ Starting in fallback mode...")
827
-
828
- # Run application
829
- try:
830
- app.run(host='0.0.0.0', port=7860, debug=False, threaded=True)
831
- except Exception as e:
832
- log_message(f"❌ Server startup error: {str(e)}")
833
- print(f"Critical error: {str(e)}")
 
1
+ !pip install -y diffusers
2
+ !pip install git+https://github.com/huggingface/diffusers.git
3
+ !pip install -U accelerate transformers gradio huggingface_hub sentencepiece
4
+ import torch, random, gc, numpy as np
5
+ import gradio as gr
6
+ from diffusers import StableDiffusionXLPipeline, EulerAncestralDiscreteScheduler
7
+ from huggingface_hub import login
 
 
 
 
8
  from PIL import Image
9
+ import os
 
 
 
 
 
 
10
 
11
+ # --- 2. Load Model ---
12
+ device = "cuda"
13
+ model_id = "stablediffusionapi/pony-diffusion-v6-xl"
14
+ if 'pipe' not in locals():
15
+ pipe = StableDiffusionXLPipeline.from_pretrained(
16
+ model_id, torch_dtype=torch.float16, variant="fp16", use_safetensors=True
17
+ ).to(device)
18
+ pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
19
+ pipe.enable_vae_tiling()
20
+ pipe.enable_vae_slicing()
21
+ # --- 3. Function เจนภาพ (เพิ่มการจัดการนามสกุลไฟล์) ---
22
+ def generate_single(prompt, neg_prompt, steps, cfg, seed, size_preset, style_preset, file_format):
23
+ torch.cuda.empty_cache()
24
+ style_map = {
25
+ "None (ค่าดั้งเดิม)": "raw photo.cute.high resolution",
26
+ "Realistic Photo (ดิบๆ)": "raw photo, (photorealistic:1.3), high fidelity, skin pores, film grain, Fujifilm, ",
27
+ "Cinematic Real (แสงสวย)": "cinematic film still, shallow depth of field, dramatic lighting, highly detailed skin, 8k uhd, ",
28
+ "3D Realistic (เนียนกริบ)": "super-realistic.realistic render,real human, octane render, soft global illumination, ",
29
+ "Semi-Real (นวลตา)": "semi-realistic, digital concept art, smooth skin, detailed lighting, ",
30
+ "Portrait Real (เน้นหน้า)": "close up portrait, highly detailed eyes, skin texture, dslr, soft natural light, ",
31
+ "สไตล์บ้านๆ (Homemade)": "amateur phone photography, (casual lighting:1.2), flash photography, messy room background, (real life:1.3), grainy, ",
32
+ "แนวบิวตี้ (ฟรุ้งฟริ้ง)": "soft focus, (ethereal lighting:1.3), dreamy atmosphere, glowing skin, (pastel tones:1.1), high key lighting, beauty filter, "
33
  }
34
+ selected_style = style_map[style_preset]
35
+ if "score_9" in prompt:
36
+ full_prompt = f"{selected_style}{prompt}"
37
+ else:
38
+ full_prompt = f"score_9, score_8_up, score_7_up, rating_explicit, masterpiece, best quality, {selected_style}{prompt}"
39
+ size_map = {
40
+ "Square (512x512)": (512, 512),
41
+ "Square (1024x1024)": (1024, 1024),
42
+ "Portrait (832x1216)": (720, 1260),
43
+ "Landscape (1216x832)": (1280, 720)
44
+ }
45
+ width, height = size_map[size_preset]
46
+ base_seed = int(seed) if int(seed) != -1 else random.randint(0, 10_000_000)
47
+ with torch.inference_mode():
48
+ generator = torch.manual_seed(base_seed)
49
+ image = pipe(
50
+ prompt=full_prompt,
51
+ negative_prompt=neg_prompt,
52
+ num_inference_steps=int(steps),
53
+ guidance_scale=cfg,
54
+ generator=generator,
55
+ width=width,
56
+ height=height
57
+ ).images[0]
58
+ # บันทึกไฟล์ลงเครื่องตามนามสกุลที่เลือก
59
+ filename = f"output_{base_seed}.{file_format.lower()}"
60
+ if file_format == "JPG":
61
+ image.save(filename, quality=95, optimize=True)
62
+ elif file_format == "PNG":
63
+ image.save(filename, quality=98)
64
+ else: # PNG
65
+ image.save(random)
66
+ return image, base_seed, filename
67
+ # --- 4. UI Layout ---
68
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
69
+ gr.Markdown("# 🚀 Pony V6 XL: Ultimate Realistic Engine")
70
+ with gr.Row():
71
+ with gr.Column(scale=1):
72
+ btn = gr.Button("🚀 GENERATE NOW", variant="primary")
73
+ p_in = gr.Textbox(label="Prompt", value="a young slutty asian cute girl, 2men,double-penetration. gangbang, group sex.sex on top potition.cum in pussy.creampie.deepthroat.eating sperm,public sex,bikini.nipple piercinglook pain mix happy,", lines=4)
74
+ n_in = gr.Textbox(label="Negative", value="anime,,low quality, worst quality, motion artifacts, unstable motion, jitter, frame jitter, wobbling limbs, motion distortion, inconsistent movement, robotic movement, animation-like motion, awkward transitions, incorrect body mechanics, unnatural posing, off-balance poses, broken motion paths, frozen frames, duplicated frames, frame skipping, warped motion, stretching artifacts bad anatomy, incorrect proportions, deformed body, twisted torso, broken joints, dislocated limbs, distorted neck, unnatural spine curvature, malformed hands, extra fingers, missing fingers, fused fingers, distorted legs, extra limbs, collapsed feet, floating feet, foot sliding, foot jitter, backward walking, unnatural gait blurry details, long exposure blur, ghosting, shadow trails, smearing, washed-out colors, overexposure, underexposure, excessive contrast, blown highlights, poorly rendered clothing, fabric glitches, texture warping, clothing merging with body, incorrect cloth physics ugly background, cluttered scene, crowded background, random objects, unwanted text, subtitles, logos, graffiti, grain, noise, static artifacts, compression noise, jpeg artifacts, image-like stillness, painting-like look, cartoon texture, low-resolution textures manga, Disney, cartoon, low resolution, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name, drawing, illustration, boring, 3d render, long neck, out of frame, extra fingers mutation, deformed, canvas frame, high contrast:1.2, over saturated:1.2, glossy:1.1, bad lighting, poorly drawn, bad art, bad angle, low-resolution, bad composition, terrible lighting, score_4, score_5, score_6, (anime, cartoon, manga, illustration, 3d, render:1.3), (extra limbs:1.2), (extra legs:1.2), (extra arms:1.2), low quality, blurry, censorship, mosaic", lines=2)
75
+ with gr.Row():
76
+ style_drop = gr.Dropdown(list(["None (ค่าดั้งเดิม)", "Realistic Photo (ดิบๆ)", "Cinematic Real (แสงสวย)", "3D Realistic (เนียนกริบ)", "Semi-Real (นวลตา)", "Portrait Real (เน้นหน้า)", "สไตล์บ้านๆ (Homemade)", "แนวบิวตี้ (ฟรุ้งฟริ้ง)"]), value="Realistic Photo (ดิบๆ)", label="เลือกสไตล์ภาพ")
77
+ # ตั้งขนาดเริ่มต้นเป็น 1024x1024 ตามสั่ง
78
+ size_drop = gr.Dropdown(["Square (512x512)", "Square (1024x1024)", "Portrait (832x1216)", "Landscape (1216x832)"], value="Square (1024x1024)", label="ขนาดภาพ")
79
+ with gr.Row():
80
+ # เพิ่มช่องเลือกนามสกุลไฟล์
81
+ format_drop = gr.Dropdown(["PNG", "JPG", "WebP"], value="PNG", label="นามสกุลไฟล์")
82
+ c_sld = gr.Slider(1, 15, value=3.0, step=0.5, label="ความคมชัดคำสั่ง (CFG)")
83
+ with gr.Row():
84
+ s_sld = gr.Slider(2, 100, value=70, step=1, label="รอบการวาด (Steps)")
85
+ sd_in = gr.Number(value=-1, label="Seed")
86
+ with gr.Column(scale=1):
87
+ img_out = gr.Image(label="Result")
88
+ file_out = gr.File(label="Download File") # ช่องสำหรับโหลดไฟล์ออกมา
89
+ seed_out = gr.Number(label="Seed ที่ใช้")
90
+ btn.click(fn=generate_single,
91
+ inputs=[p_in, n_in, s_sld, c_sld, sd_in, size_drop, style_drop, format_drop],
92
+ outputs=[img_out, seed_out, file_out])
93
+ demo.launch(share=True)