ginipick commited on
Commit
b183457
·
verified ·
1 Parent(s): 01504d4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +24 -829
app.py CHANGED
@@ -1,840 +1,35 @@
1
- import gradio as gr
2
- import spaces
3
- import replicate
4
  import os
5
- from PIL import Image, ImageDraw
6
- import requests
7
- from io import BytesIO
8
- import time
9
- import tempfile
10
- import base64
11
- import numpy as np
12
- import gc
13
 
14
- # Set up Replicate API key from environment variable
15
- os.environ['REPLICATE_API_TOKEN'] = os.getenv('REPLICATE_API_TOKEN')
16
-
17
- # --- Helper Functions ---
18
- def get_with_retry(url, max_retries=3, timeout=60):
19
- """URL 요청을 재시도 로직과 함께 처리"""
20
- for i in range(max_retries):
21
- try:
22
- response = requests.get(url, timeout=timeout)
23
- if response.status_code == 200:
24
- return response
25
- except requests.exceptions.Timeout:
26
- if i == max_retries - 1:
27
- raise
28
- time.sleep(2) # 재시도 전 대기
29
- except Exception as e:
30
- if i == max_retries - 1:
31
- raise
32
- time.sleep(2)
33
- return None
34
-
35
- def validate_image_size(image, max_size=2048):
36
- """이미지 크기 검증 및 리사이즈"""
37
- if image is None:
38
- return None
39
-
40
- if image.width > max_size or image.height > max_size:
41
- ratio = min(max_size/image.width, max_size/image.height)
42
- new_size = (int(image.width * ratio), int(image.height * ratio))
43
- image = image.resize(new_size, Image.LANCZOS)
44
- return image
45
-
46
- # --- Outpainting Functions ---
47
- def can_expand(source_width, source_height, target_width, target_height, alignment):
48
- """Checks if the image can be expanded based on the alignment."""
49
- if alignment in ("Left", "Right") and source_width >= target_width:
50
- return False
51
- if alignment in ("Top", "Bottom") and source_height >= target_height:
52
- return False
53
- return True
54
-
55
- def prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
56
- """Prepares the image with white margins and creates a mask for outpainting."""
57
- target_size = (width, height)
58
-
59
- # Calculate the scaling factor to fit the image within the target size
60
- scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
61
- new_width = int(image.width * scale_factor)
62
- new_height = int(image.height * scale_factor)
63
-
64
- # Resize the source image to fit within target size
65
- source = image.resize((new_width, new_height), Image.LANCZOS)
66
-
67
- # Apply resize option using percentages
68
- if resize_option == "Full":
69
- resize_percentage = 100
70
- elif resize_option == "50%":
71
- resize_percentage = 50
72
- elif resize_option == "33%":
73
- resize_percentage = 33
74
- elif resize_option == "25%":
75
- resize_percentage = 25
76
- else: # Custom
77
- resize_percentage = custom_resize_percentage
78
-
79
- # Calculate new dimensions based on percentage
80
- resize_factor = resize_percentage / 100
81
- new_width = int(source.width * resize_factor)
82
- new_height = int(source.height * resize_factor)
83
-
84
- # Ensure minimum size of 64 pixels
85
- new_width = max(new_width, 64)
86
- new_height = max(new_height, 64)
87
-
88
- # Resize the image
89
- source = source.resize((new_width, new_height), Image.LANCZOS)
90
-
91
- # Calculate the overlap in pixels based on the percentage
92
- overlap_x = int(new_width * (overlap_percentage / 100))
93
- overlap_y = int(new_height * (overlap_percentage / 100))
94
-
95
- # Ensure minimum overlap of 1 pixel
96
- overlap_x = max(overlap_x, 1)
97
- overlap_y = max(overlap_y, 1)
98
-
99
- # Calculate margins based on alignment
100
- if alignment == "Middle":
101
- margin_x = (target_size[0] - new_width) // 2
102
- margin_y = (target_size[1] - new_height) // 2
103
- elif alignment == "Left":
104
- margin_x = 0
105
- margin_y = (target_size[1] - new_height) // 2
106
- elif alignment == "Right":
107
- margin_x = target_size[0] - new_width
108
- margin_y = (target_size[1] - new_height) // 2
109
- elif alignment == "Top":
110
- margin_x = (target_size[0] - new_width) // 2
111
- margin_y = 0
112
- elif alignment == "Bottom":
113
- margin_x = (target_size[0] - new_width) // 2
114
- margin_y = target_size[1] - new_height
115
-
116
- # Adjust margins to eliminate gaps
117
- margin_x = max(0, min(margin_x, target_size[0] - new_width))
118
- margin_y = max(0, min(margin_y, target_size[1] - new_height))
119
-
120
- # Create a new background image with white margins and paste the resized source image
121
- background = Image.new('RGB', target_size, (255, 255, 255))
122
- background.paste(source, (margin_x, margin_y))
123
-
124
- # Create the mask
125
- mask = Image.new('L', target_size, 255)
126
- mask_draw = ImageDraw.Draw(mask)
127
-
128
- # Calculate overlap areas
129
- white_gaps_patch = 2
130
- left_overlap = margin_x + overlap_x if overlap_left else margin_x + white_gaps_patch
131
- right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width - white_gaps_patch
132
- top_overlap = margin_y + overlap_y if overlap_top else margin_y + white_gaps_patch
133
- bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height - white_gaps_patch
134
-
135
- if alignment == "Left":
136
- left_overlap = margin_x + overlap_x if overlap_left else margin_x
137
- elif alignment == "Right":
138
- right_overlap = margin_x + new_width - overlap_x if overlap_right else margin_x + new_width
139
- elif alignment == "Top":
140
- top_overlap = margin_y + overlap_y if overlap_top else margin_y
141
- elif alignment == "Bottom":
142
- bottom_overlap = margin_y + new_height - overlap_y if overlap_bottom else margin_y + new_height
143
-
144
- # Draw the mask
145
- mask_draw.rectangle([
146
- (left_overlap, top_overlap),
147
- (right_overlap, bottom_overlap)
148
- ], fill=0)
149
-
150
- return background, mask
151
-
152
- def preview_outpaint(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
153
- """Creates a preview showing the mask overlay for outpainting."""
154
- if not image:
155
- return None
156
-
157
- background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, resize_option, custom_resize_percentage, alignment, overlap_left, overlap_right, overlap_top, overlap_bottom)
158
-
159
- # Create a preview image showing the mask
160
- preview = background.copy().convert('RGBA')
161
-
162
- # Create a semi-transparent red overlay
163
- red_overlay = Image.new('RGBA', background.size, (255, 0, 0, 64)) # Reduced alpha to 64 (25% opacity)
164
-
165
- # Convert black pixels in the mask to semi-transparent red
166
- red_mask = Image.new('RGBA', background.size, (0, 0, 0, 0))
167
- red_mask.paste(red_overlay, (0, 0), mask)
168
-
169
- # Overlay the red mask on the background
170
- preview = Image.alpha_composite(preview, red_mask)
171
-
172
- return preview
173
-
174
- # --- Image Upload Functions ---
175
- def upload_image_to_hosting(image):
176
- """
177
- Upload image to multiple hosting services with fallback
178
- """
179
  try:
180
- # Method 1: Try imgbb.com (most reliable)
181
- buffered = BytesIO()
182
- image.save(buffered, format="PNG")
183
- buffered.seek(0)
184
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
185
-
186
- response = requests.post(
187
- "https://api.imgbb.com/1/upload",
188
- data={
189
- 'key': '6d207e02198a847aa98d0a2a901485a5',
190
- 'image': img_base64,
191
- },
192
- timeout=30
193
- )
194
-
195
- if response.status_code == 200:
196
- data = response.json()
197
- if data.get('success'):
198
- return data['data']['url']
199
- except:
200
- pass
201
-
202
- try:
203
- # Method 2: Try 0x0.st (simple and reliable)
204
- buffered = BytesIO()
205
- image.save(buffered, format="PNG")
206
- buffered.seek(0)
207
-
208
- files = {'file': ('image.png', buffered, 'image/png')}
209
- response = requests.post("https://0x0.st", files=files, timeout=30)
210
-
211
- if response.status_code == 200:
212
- return response.text.strip()
213
- except:
214
- pass
215
-
216
- # Method 3: Fallback to base64
217
- buffered = BytesIO()
218
- image.save(buffered, format="PNG")
219
- buffered.seek(0)
220
- img_base64 = base64.b64encode(buffered.getvalue()).decode()
221
- return f"data:image/png;base64,{img_base64}"
222
-
223
- @spaces.GPU(duration=90)
224
- def upscale_image(image):
225
- """
226
- Upscale the generated image using Real-ESRGAN (mandatory)
227
- """
228
- if not image:
229
- return None, "No image to upscale"
230
-
231
- if not os.getenv('REPLICATE_API_TOKEN'):
232
- return None, "Please set REPLICATE_API_TOKEN"
233
-
234
- start_time = time.time()
235
-
236
- try:
237
- # 시간 체크
238
- if time.time() - start_time > 80:
239
- return image, "Time limit approaching, returning original image"
240
-
241
- # Upload image to hosting
242
- image_url = upload_image_to_hosting(image)
243
 
244
- # Run Real-ESRGAN model
245
- output = replicate.run(
246
- "nightmareai/real-esrgan:f121d640bd286e1fdc67f9799164c1d5be36ff74576ee11c803ae5b665dd46aa",
247
- input={
248
- "image": image_url,
249
- "scale": 4 # 4x upscaling as default
250
- }
251
- )
252
 
253
- if output is None:
254
- return None, "No output received from upscaler"
 
 
255
 
256
- # Get the upscaled image with retry logic
257
- output_url = None
258
- if isinstance(output, str):
259
- output_url = output
260
- elif isinstance(output, list) and len(output) > 0:
261
- output_url = output[0]
262
- elif hasattr(output, 'url'):
263
- output_url = output.url()
264
 
265
- if output_url:
266
- response = get_with_retry(output_url, max_retries=3, timeout=60)
267
- if response and response.status_code == 200:
268
- img = Image.open(BytesIO(response.content))
269
- gc.collect() # 메모리 정리
270
- return img, "🔍 Upscaled 4x successfully!"
271
-
272
- # Try direct read if available
273
- if hasattr(output, 'read'):
274
- img_data = output.read()
275
- img = Image.open(BytesIO(img_data))
276
- gc.collect()
277
- return img, "🔍 Upscaled 4x successfully!"
278
-
279
- return None, "Could not process upscaled output"
280
-
281
- except spaces.GPUError as e:
282
- gc.collect()
283
- return image, "GPU resources exhausted, returning original image"
284
- except Exception as e:
285
- gc.collect()
286
- return image, f"Upscale error (returning original): {str(e)[:50]}"
287
-
288
- def apply_outpainting_to_image(image, outpaint_prompt, target_width, target_height,
289
- overlap_percentage, resize_option, custom_resize_percentage,
290
- alignment, overlap_left, overlap_right, overlap_top, overlap_bottom):
291
- """
292
- Apply outpainting to an image by preparing it with white margins
293
- """
294
- if not image:
295
- return None
296
-
297
- # Check if expansion is possible
298
- if not can_expand(image.width, image.height, target_width, target_height, alignment):
299
- alignment = "Middle"
300
-
301
- # Prepare the image with white margins for outpainting
302
- outpaint_image, mask = prepare_image_and_mask(
303
- image, target_width, target_height, overlap_percentage,
304
- resize_option, custom_resize_percentage, alignment,
305
- overlap_left, overlap_right, overlap_top, overlap_bottom
306
- )
307
-
308
- return outpaint_image
309
-
310
- @spaces.GPU(duration=180)
311
- def process_images(prompt, image1, image2=None, enable_outpaint=False, outpaint_prompt="",
312
- target_width=1280, target_height=720, overlap_percentage=10,
313
- resize_option="Full", custom_resize_percentage=50,
314
- alignment="Middle", overlap_left=True, overlap_right=True,
315
- overlap_top=True, overlap_bottom=True, progress=gr.Progress()):
316
- """
317
- Process uploaded images with Replicate API, apply optional outpainting, and mandatory upscaling
318
- """
319
- if not image1:
320
- return None, "Please upload at least one image"
321
-
322
- if not os.getenv('REPLICATE_API_TOKEN'):
323
- return None, "Please set REPLICATE_API_TOKEN"
324
-
325
- start_time = time.time()
326
-
327
- try:
328
- progress(0.1, desc="Validating images...")
329
-
330
- # Validate and resize images if needed
331
- image1 = validate_image_size(image1)
332
- if image2:
333
- image2 = validate_image_size(image2)
334
-
335
- # Time check
336
- if time.time() - start_time > 170:
337
- return None, "Processing time exceeded. Please try with smaller images."
338
-
339
- progress(0.2, desc="Preparing images...")
340
-
341
- # Step 1: Apply outpainting if enabled
342
- if enable_outpaint:
343
- # Apply outpainting to image1
344
- image1 = apply_outpainting_to_image(
345
- image1, outpaint_prompt, target_width, target_height,
346
- overlap_percentage, resize_option, custom_resize_percentage,
347
- alignment, overlap_left, overlap_right, overlap_top, overlap_bottom
348
- )
349
-
350
- # Apply outpainting to image2 if it exists
351
- if image2:
352
- image2 = apply_outpainting_to_image(
353
- image2, outpaint_prompt, target_width, target_height,
354
- overlap_percentage, resize_option, custom_resize_percentage,
355
- alignment, overlap_left, overlap_right, overlap_top, overlap_bottom
356
- )
357
 
358
- # Update the prompt if outpainting is enabled
359
- if outpaint_prompt:
360
- prompt = f"replace the white margins. {outpaint_prompt}. {prompt}"
361
-
362
- progress(0.3, desc="Uploading to server...")
363
-
364
- # Step 2: Upload images and process with Nano Banana
365
- image_urls = []
366
-
367
- url1 = upload_image_to_hosting(image1)
368
- image_urls.append(url1)
369
-
370
- if image2:
371
- url2 = upload_image_to_hosting(image2)
372
- image_urls.append(url2)
373
-
374
- # Time check
375
- if time.time() - start_time > 170:
376
- return None, "Processing time exceeded during upload."
377
-
378
- progress(0.5, desc="Generating image...")
379
-
380
- # Run the Nano Banana model
381
- output = replicate.run(
382
- "google/nano-banana",
383
- input={
384
- "prompt": prompt,
385
- "image_input": image_urls
386
- }
387
- )
388
-
389
- if output is None:
390
- return None, "No output received"
391
-
392
- # Clear intermediate variables
393
- del image_urls
394
- gc.collect()
395
-
396
- progress(0.7, desc="Processing output...")
397
-
398
- # Get the generated image with retry logic
399
- generated_image = None
400
-
401
- # Try different methods to get the output
402
- output_url = None
403
- if isinstance(output, str):
404
- output_url = output
405
- elif isinstance(output, list) and len(output) > 0:
406
- output_url = output[0]
407
- elif hasattr(output, 'url'):
408
- output_url = output.url()
409
-
410
- if output_url:
411
- response = get_with_retry(output_url, max_retries=3, timeout=60)
412
- if response and response.status_code == 200:
413
- generated_image = Image.open(BytesIO(response.content))
414
-
415
- # Try direct read if available
416
- if not generated_image and hasattr(output, 'read'):
417
- img_data = output.read()
418
- generated_image = Image.open(BytesIO(img_data))
419
-
420
- if not generated_image:
421
- return None, "Could not process output"
422
-
423
- # Clear output variable
424
- del output
425
- gc.collect()
426
-
427
- # Time check before upscaling
428
- if time.time() - start_time > 170:
429
- return generated_image, "✨ Generated (time limit reached, skipping upscale)"
430
-
431
- progress(0.8, desc="Upscaling image...")
432
-
433
- # Step 3: Apply mandatory upscaling
434
- upscaled_image, upscale_status = upscale_image(generated_image)
435
-
436
- # Clear generated image from memory
437
- del generated_image
438
- gc.collect()
439
-
440
- progress(1.0, desc="Complete!")
441
-
442
- if upscaled_image:
443
- return upscaled_image, f"✨ Generated and {upscale_status}"
444
- else:
445
- # If upscaling fails, return the generated image with a warning
446
- return generated_image, "✨ Generated (upscaling failed, returning original)"
447
-
448
- except spaces.GPUError as e:
449
- gc.collect()
450
- return None, "GPU resources exhausted. Please try again later."
451
- except requests.exceptions.Timeout:
452
- gc.collect()
453
- return None, "Request timeout. Please try with smaller images."
454
  except Exception as e:
455
- gc.collect()
456
- return None, f"Error: {str(e)[:100]}"
457
- finally:
458
- gc.collect() # Final cleanup
459
-
460
- def toggle_outpaint_options(enable):
461
- """Toggle visibility of outpainting options"""
462
- return gr.update(visible=enable)
463
-
464
- def preload_presets(target_ratio):
465
- """Updates the width and height based on the selected aspect ratio."""
466
- if target_ratio == "9:16":
467
- return 720, 1280
468
- elif target_ratio == "16:9":
469
- return 1280, 720
470
- elif target_ratio == "1:1":
471
- return 1024, 1024
472
- else: # Custom
473
- return 1280, 720
474
-
475
- def toggle_custom_resize_slider(resize_option):
476
- return gr.update(visible=(resize_option == "Custom"))
477
-
478
- # Enhanced CSS with modern, minimal design
479
- css = """
480
- .gradio-container {
481
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
482
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
483
- min-height: 100vh;
484
- }
485
- .header-container {
486
- background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%);
487
- padding: 2.5rem;
488
- border-radius: 24px;
489
- margin-bottom: 2.5rem;
490
- box-shadow: 0 20px 60px rgba(255, 179, 71, 0.25);
491
- }
492
- .logo-text {
493
- font-size: 3.5rem;
494
- font-weight: 900;
495
- color: #2d3436;
496
- text-align: center;
497
- margin: 0;
498
- letter-spacing: -2px;
499
- }
500
- .subtitle {
501
- color: #2d3436;
502
- text-align: center;
503
- font-size: 1rem;
504
- margin-top: 0.5rem;
505
- opacity: 0.8;
506
- }
507
- .main-content {
508
- background: rgba(255, 255, 255, 0.95);
509
- backdrop-filter: blur(20px);
510
- border-radius: 24px;
511
- padding: 2.5rem;
512
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
513
- }
514
- .gr-button-primary {
515
- background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%) !important;
516
- border: none !important;
517
- color: #2d3436 !important;
518
- font-weight: 700 !important;
519
- font-size: 1.1rem !important;
520
- padding: 1.2rem 2rem !important;
521
- border-radius: 14px !important;
522
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
523
- text-transform: uppercase;
524
- letter-spacing: 1px;
525
- width: 100%;
526
- margin-top: 1rem !important;
527
- }
528
- .gr-button-primary:hover {
529
- transform: translateY(-3px) !important;
530
- box-shadow: 0 15px 40px rgba(255, 179, 71, 0.35) !important;
531
- }
532
- .gr-button-secondary {
533
- background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%) !important;
534
- border: none !important;
535
- color: white !important;
536
- font-weight: 600 !important;
537
- font-size: 0.95rem !important;
538
- padding: 0.8rem 1.5rem !important;
539
- border-radius: 12px !important;
540
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
541
- }
542
- .gr-button-secondary:hover {
543
- transform: translateY(-2px) !important;
544
- box-shadow: 0 10px 30px rgba(9, 132, 227, 0.3) !important;
545
- }
546
- .gr-input, .gr-textarea {
547
- background: #ffffff !important;
548
- border: 2px solid #e1e8ed !important;
549
- border-radius: 14px !important;
550
- color: #2d3436 !important;
551
- font-size: 1rem !important;
552
- padding: 0.8rem 1rem !important;
553
- }
554
- .gr-input:focus, .gr-textarea:focus {
555
- border-color: #ffd93d !important;
556
- box-shadow: 0 0 0 4px rgba(255, 217, 61, 0.15) !important;
557
- }
558
- .gr-form {
559
- background: transparent !important;
560
- border: none !important;
561
- }
562
- .gr-panel {
563
- background: #ffffff !important;
564
- border: 2px solid #e1e8ed !important;
565
- border-radius: 16px !important;
566
- padding: 1.5rem !important;
567
- }
568
- .gr-box {
569
- border-radius: 14px !important;
570
- border-color: #e1e8ed !important;
571
- }
572
- label {
573
- color: #636e72 !important;
574
- font-weight: 600 !important;
575
- font-size: 0.85rem !important;
576
- text-transform: uppercase;
577
- letter-spacing: 0.5px;
578
- margin-bottom: 0.5rem !important;
579
- }
580
- .status-text {
581
- font-family: 'SF Mono', 'Monaco', monospace;
582
- color: #00b894;
583
- font-size: 0.9rem;
584
- }
585
- .image-container {
586
- border-radius: 14px !important;
587
- overflow: hidden;
588
- border: 2px solid #e1e8ed !important;
589
- background: #fafbfc !important;
590
- }
591
- .preview-container {
592
- border: 2px dashed #ff6b6b !important;
593
- border-radius: 14px !important;
594
- padding: 1rem !important;
595
- background: rgba(255, 107, 107, 0.05) !important;
596
- }
597
- footer {
598
- display: none !important;
599
- }
600
- /* Equal sizing for all image containers */
601
- .image-upload {
602
- min-height: 200px !important;
603
- max-height: 200px !important;
604
- }
605
- .output-image {
606
- min-height: 420px !important;
607
- max-height: 420px !important;
608
- }
609
- /* Ensure consistent spacing */
610
- .gr-row {
611
- gap: 1rem !important;
612
- }
613
- .gr-column {
614
- gap: 1rem !important;
615
- }
616
- /* Outpainting options styling */
617
- .outpaint-section {
618
- background: rgba(116, 185, 255, 0.1) !important;
619
- border: 2px solid #74b9ff !important;
620
- border-radius: 14px !important;
621
- padding: 1rem !important;
622
- margin-top: 1rem !important;
623
- }
624
- """
625
-
626
- with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
627
- with gr.Column(elem_classes="header-container"):
628
- gr.HTML("""
629
- <h1 class="logo-text">🍌 Nano Banana PRO</h1>
630
- <p class="subtitle">AI-Powered Image Style Transfer with Outpainting & Auto-Upscaling(X4)</p>
631
- <div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px;">
632
- <a href="https://ginigen.com" target="_blank">
633
- <img src="https://img.shields.io/static/v1?label=ginigen&message=special%20ai%20sERVICE&color=%230000ff&labelColor=%23800080&logo=google&logoColor=white&style=for-the-badge" alt="badge">
634
- </a>
635
-
636
- <a href="https://discord.gg/openfreeai" target="_blank">
637
- <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord Openfree AI">
638
- </a>
639
- </div>
640
- """)
641
-
642
- with gr.Column(elem_classes="main-content"):
643
- with gr.Row(equal_height=True):
644
- # Left Column - Inputs
645
- with gr.Column(scale=1):
646
- prompt = gr.Textbox(
647
- label="Style Description",
648
- placeholder="Describe your style...",
649
- lines=3,
650
- value="Make the sheets in the style of the logo. Make the scene natural.",
651
- elem_classes="prompt-input"
652
- )
653
-
654
- with gr.Row(equal_height=True):
655
- image1 = gr.Image(
656
- label="Primary Image",
657
- type="pil",
658
- height=200,
659
- elem_classes="image-container image-upload"
660
- )
661
- image2 = gr.Image(
662
- label="Secondary Image (Optional)",
663
- type="pil",
664
- height=200,
665
- elem_classes="image-container image-upload"
666
- )
667
-
668
- # Outpainting Options
669
- enable_outpaint = gr.Checkbox(
670
- label="🎨 Enable Outpainting (Expand Image)",
671
- value=False
672
- )
673
-
674
- with gr.Column(visible=False, elem_classes="outpaint-section") as outpaint_options:
675
- outpaint_prompt = gr.Textbox(
676
- label="Outpaint Prompt",
677
- placeholder="Describe what should appear in the extended areas",
678
- value="extend the image naturally",
679
- lines=2
680
- )
681
-
682
- with gr.Row():
683
- target_ratio = gr.Radio(
684
- label="Target Ratio",
685
- choices=["9:16", "16:9", "1:1", "Custom"],
686
- value="16:9"
687
- )
688
- alignment_dropdown = gr.Dropdown(
689
- choices=["Middle", "Left", "Right", "Top", "Bottom"],
690
- value="Middle",
691
- label="Alignment"
692
- )
693
-
694
- with gr.Row():
695
- target_width = gr.Slider(
696
- label="Target Width",
697
- minimum=512,
698
- maximum=2048,
699
- step=8,
700
- value=1280
701
- )
702
- target_height = gr.Slider(
703
- label="Target Height",
704
- minimum=512,
705
- maximum=2048,
706
- step=8,
707
- value=720
708
- )
709
-
710
- with gr.Accordion("Advanced Outpaint Settings", open=False):
711
- overlap_percentage = gr.Slider(
712
- label="Mask overlap (%)",
713
- minimum=1,
714
- maximum=50,
715
- value=10,
716
- step=1,
717
- info="Controls the blending area"
718
- )
719
-
720
- with gr.Row():
721
- overlap_top = gr.Checkbox(label="Overlap Top", value=True)
722
- overlap_right = gr.Checkbox(label="Overlap Right", value=True)
723
- with gr.Row():
724
- overlap_left = gr.Checkbox(label="Overlap Left", value=True)
725
- overlap_bottom = gr.Checkbox(label="Overlap Bottom", value=True)
726
-
727
- with gr.Row():
728
- resize_option = gr.Radio(
729
- label="Resize input image",
730
- choices=["Full", "50%", "33%", "25%", "Custom"],
731
- value="Full"
732
- )
733
- custom_resize_percentage = gr.Slider(
734
- label="Custom resize (%)",
735
- minimum=1,
736
- maximum=100,
737
- step=1,
738
- value=50,
739
- visible=False
740
- )
741
-
742
- preview_outpaint_btn = gr.Button(
743
- "👁️ Preview Outpaint Mask",
744
- variant="secondary"
745
- )
746
-
747
- generate_btn = gr.Button(
748
- "Generate Magic with Auto-Upscale ✨",
749
- variant="primary",
750
- size="lg"
751
- )
752
-
753
- # Right Column - Output
754
- with gr.Column(scale=1):
755
- output_image = gr.Image(
756
- label="Generated & Upscaled Result",
757
- type="pil",
758
- height=420,
759
- elem_classes="image-container output-image"
760
- )
761
-
762
- status = gr.Textbox(
763
- label="Status",
764
- interactive=False,
765
- lines=1,
766
- elem_classes="status-text",
767
- value="Ready to generate..."
768
- )
769
-
770
- outpaint_preview = gr.Image(
771
- label="Outpaint Preview (red area will be generated)",
772
- type="pil",
773
- visible=False,
774
- elem_classes="preview-container"
775
- )
776
-
777
- gr.Markdown("""
778
- ### 📌 Features:
779
- - **Style Transfer**: Apply artistic styles to your images
780
- - **Outpainting** (Optional): Expand your images with AI
781
- - **Auto 4x Upscaling**: All outputs are automatically upscaled for maximum quality
782
- - **Optimized for ZeroGPU**: Improved stability and reduced timeout issues
783
-
784
- ### 💡 Tips:
785
- - Upload 1-2 images to apply style transfer
786
- - Enable outpainting to expand image boundaries
787
- - All generated images are automatically upscaled 4x
788
- - Large images are automatically resized for optimal processing
789
- """)
790
-
791
- # Event handlers
792
- enable_outpaint.change(
793
- fn=toggle_outpaint_options,
794
- inputs=[enable_outpaint],
795
- outputs=[outpaint_options]
796
- )
797
-
798
- target_ratio.change(
799
- fn=preload_presets,
800
- inputs=[target_ratio],
801
- outputs=[target_width, target_height]
802
- )
803
-
804
- resize_option.change(
805
- fn=toggle_custom_resize_slider,
806
- inputs=[resize_option],
807
- outputs=[custom_resize_percentage]
808
- )
809
-
810
- preview_outpaint_btn.click(
811
- fn=preview_outpaint,
812
- inputs=[
813
- image1, target_width, target_height, overlap_percentage,
814
- resize_option, custom_resize_percentage, alignment_dropdown,
815
- overlap_left, overlap_right, overlap_top, overlap_bottom
816
- ],
817
- outputs=[outpaint_preview]
818
- ).then(
819
- fn=lambda: gr.update(visible=True),
820
- outputs=[outpaint_preview]
821
- )
822
-
823
- generate_btn.click(
824
- fn=process_images,
825
- inputs=[
826
- prompt, image1, image2, enable_outpaint, outpaint_prompt,
827
- target_width, target_height, overlap_percentage,
828
- resize_option, custom_resize_percentage, alignment_dropdown,
829
- overlap_left, overlap_right, overlap_top, overlap_bottom
830
- ],
831
- outputs=[output_image, status]
832
- )
833
 
834
- # Launch
835
  if __name__ == "__main__":
836
- demo.launch(
837
- share=True,
838
- server_name="0.0.0.0",
839
- server_port=7860
840
- )
 
 
 
 
1
  import os
2
+ import sys
3
+ import streamlit as st
4
+ from tempfile import NamedTemporaryFile
 
 
 
 
 
5
 
6
+ def main():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  try:
8
+ # Get the code from secrets
9
+ code = os.environ.get("MAIN_CODE")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ if not code:
12
+ st.error("⚠️ The application code wasn't found in secrets. Please add the MAIN_CODE secret.")
13
+ return
 
 
 
 
 
14
 
15
+ # Create a temporary Python file
16
+ with NamedTemporaryFile(suffix='.py', delete=False, mode='w') as tmp:
17
+ tmp.write(code)
18
+ tmp_path = tmp.name
19
 
20
+ # Execute the code
21
+ exec(compile(code, tmp_path, 'exec'), globals())
 
 
 
 
 
 
22
 
23
+ # Clean up the temporary file
24
+ try:
25
+ os.unlink(tmp_path)
26
+ except:
27
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  except Exception as e:
30
+ st.error(f"⚠️ Error loading or executing the application: {str(e)}")
31
+ import traceback
32
+ st.code(traceback.format_exc())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
 
34
  if __name__ == "__main__":
35
+ main()