Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,833 +1,93 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
import
|
| 5 |
-
import
|
| 6 |
-
from
|
| 7 |
-
import
|
| 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
|
| 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 |
-
#
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|