ginipick commited on
Commit
7d2ccac
·
verified ·
1 Parent(s): b6cefc8

Delete app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +0 -636
app-backup.py DELETED
@@ -1,636 +0,0 @@
1
- import tempfile
2
- import time
3
- from collections.abc import Sequence
4
- from typing import Any, cast
5
- import os
6
- from huggingface_hub import login, hf_hub_download
7
-
8
- import gradio as gr
9
- import numpy as np
10
- import pillow_heif
11
- import spaces
12
- import torch
13
- from gradio_image_annotation import image_annotator
14
- from gradio_imageslider import ImageSlider
15
- from PIL import Image
16
- from pymatting.foreground.estimate_foreground_ml import estimate_foreground_ml
17
- from refiners.fluxion.utils import no_grad
18
- from refiners.solutions import BoxSegmenter
19
- from transformers import GroundingDinoForObjectDetection, GroundingDinoProcessor
20
- from diffusers import FluxPipeline
21
- from transformers import pipeline, AutoTokenizer, AutoModelForSeq2SeqLM
22
- import gc
23
-
24
- def clear_memory():
25
- """메모리 정리 함수"""
26
- gc.collect()
27
- try:
28
- if torch.cuda.is_available():
29
- with torch.cuda.device(0): # 명시적으로 device 0 사용
30
- torch.cuda.empty_cache()
31
- except:
32
- pass
33
-
34
- # GPU 설정
35
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 명시적으로 cuda:0 지정
36
-
37
- # GPU 설정을 try-except로 감싸기
38
- if torch.cuda.is_available():
39
- try:
40
- with torch.cuda.device(0):
41
- torch.cuda.empty_cache()
42
- torch.backends.cudnn.benchmark = True
43
- torch.backends.cuda.matmul.allow_tf32 = True
44
- except:
45
- print("Warning: Could not configure CUDA settings")
46
-
47
- # 번역 모델 초기화
48
- model_name = "Helsinki-NLP/opus-mt-ko-en"
49
- tokenizer = AutoTokenizer.from_pretrained(model_name)
50
- model = AutoModelForSeq2SeqLM.from_pretrained(model_name).to('cpu')
51
- translator = pipeline("translation", model=model, tokenizer=tokenizer, device=-1)
52
-
53
- def translate_to_english(text: str) -> str:
54
- """한글 텍스트를 영어로 번역"""
55
- try:
56
- if any(ord('가') <= ord(char) <= ord('힣') for char in text):
57
- translated = translator(text, max_length=128)[0]['translation_text']
58
- print(f"Translated '{text}' to '{translated}'")
59
- return translated
60
- return text
61
- except Exception as e:
62
- print(f"Translation error: {str(e)}")
63
- return text
64
-
65
- BoundingBox = tuple[int, int, int, int]
66
-
67
- pillow_heif.register_heif_opener()
68
- pillow_heif.register_avif_opener()
69
-
70
- # HF 토큰 설정
71
- HF_TOKEN = os.getenv("HF_TOKEN")
72
- if HF_TOKEN is None:
73
- raise ValueError("Please set the HF_TOKEN environment variable")
74
-
75
- try:
76
- login(token=HF_TOKEN)
77
- except Exception as e:
78
- raise ValueError(f"Failed to login to Hugging Face: {str(e)}")
79
-
80
- # 모델 초기화
81
- segmenter = BoxSegmenter(device="cpu")
82
- segmenter.device = device
83
- segmenter.model = segmenter.model.to(device=segmenter.device)
84
-
85
- gd_model_path = "IDEA-Research/grounding-dino-base"
86
- gd_processor = GroundingDinoProcessor.from_pretrained(gd_model_path)
87
- gd_model = GroundingDinoForObjectDetection.from_pretrained(gd_model_path, torch_dtype=torch.float32)
88
- gd_model = gd_model.to(device=device)
89
- assert isinstance(gd_model, GroundingDinoForObjectDetection)
90
-
91
- # FLUX 파이프라인 초기화
92
- pipe = FluxPipeline.from_pretrained(
93
- "black-forest-labs/FLUX.1-dev",
94
- torch_dtype=torch.float16,
95
- use_auth_token=HF_TOKEN
96
- )
97
- pipe.enable_attention_slicing(slice_size="auto")
98
-
99
- # LoRA 가중치 로드
100
- pipe.load_lora_weights(
101
- hf_hub_download(
102
- "ByteDance/Hyper-SD",
103
- "Hyper-FLUX.1-dev-8steps-lora.safetensors",
104
- use_auth_token=HF_TOKEN
105
- )
106
- )
107
- pipe.fuse_lora(lora_scale=0.125)
108
-
109
- # GPU 설정을 try-except로 감싸기
110
- try:
111
- if torch.cuda.is_available():
112
- pipe = pipe.to("cuda:0") # 명시적으로 cuda:0 지정
113
- except Exception as e:
114
- print(f"Warning: Could not move pipeline to CUDA: {str(e)}")
115
-
116
- class timer:
117
- def __init__(self, method_name="timed process"):
118
- self.method = method_name
119
- def __enter__(self):
120
- self.start = time.time()
121
- print(f"{self.method} starts")
122
- def __exit__(self, exc_type, exc_val, exc_tb):
123
- end = time.time()
124
- print(f"{self.method} took {str(round(end - self.start, 2))}s")
125
-
126
- def bbox_union(bboxes: Sequence[list[int]]) -> BoundingBox | None:
127
- if not bboxes:
128
- return None
129
- for bbox in bboxes:
130
- assert len(bbox) == 4
131
- assert all(isinstance(x, int) for x in bbox)
132
- return (
133
- min(bbox[0] for bbox in bboxes),
134
- min(bbox[1] for bbox in bboxes),
135
- max(bbox[2] for bbox in bboxes),
136
- max(bbox[3] for bbox in bboxes),
137
- )
138
-
139
- def corners_to_pixels_format(bboxes: torch.Tensor, width: int, height: int) -> torch.Tensor:
140
- x1, y1, x2, y2 = bboxes.round().to(torch.int32).unbind(-1)
141
- return torch.stack((x1.clamp_(0, width), y1.clamp_(0, height), x2.clamp_(0, width), y2.clamp_(0, height)), dim=-1)
142
-
143
- def gd_detect(img: Image.Image, prompt: str) -> BoundingBox | None:
144
- inputs = gd_processor(images=img, text=f"{prompt}.", return_tensors="pt").to(device=device)
145
- with no_grad():
146
- outputs = gd_model(**inputs)
147
- width, height = img.size
148
- results: dict[str, Any] = gd_processor.post_process_grounded_object_detection(
149
- outputs,
150
- inputs["input_ids"],
151
- target_sizes=[(height, width)],
152
- )[0]
153
- assert "boxes" in results and isinstance(results["boxes"], torch.Tensor)
154
- bboxes = corners_to_pixels_format(results["boxes"].cpu(), width, height)
155
- return bbox_union(bboxes.numpy().tolist())
156
-
157
- def apply_mask(img: Image.Image, mask_img: Image.Image, defringe: bool = True) -> Image.Image:
158
- assert img.size == mask_img.size
159
- img = img.convert("RGB")
160
- mask_img = mask_img.convert("L")
161
- if defringe:
162
- rgb, alpha = np.asarray(img) / 255.0, np.asarray(mask_img) / 255.0
163
- foreground = cast(np.ndarray[Any, np.dtype[np.uint8]], estimate_foreground_ml(rgb, alpha))
164
- img = Image.fromarray((foreground * 255).astype("uint8"))
165
- result = Image.new("RGBA", img.size)
166
- result.paste(img, (0, 0), mask_img)
167
- return result
168
-
169
-
170
- def adjust_size_to_multiple_of_8(width: int, height: int) -> tuple[int, int]:
171
- """이미지 크기를 8의 배수로 조정하는 함수"""
172
- new_width = ((width + 7) // 8) * 8
173
- new_height = ((height + 7) // 8) * 8
174
- return new_width, new_height
175
-
176
- def calculate_dimensions(aspect_ratio: str, base_size: int = 512) -> tuple[int, int]:
177
- """선택된 비율에 따라 이미지 크기 계산"""
178
- if aspect_ratio == "1:1":
179
- return base_size, base_size
180
- elif aspect_ratio == "16:9":
181
- return base_size * 16 // 9, base_size
182
- elif aspect_ratio == "9:16":
183
- return base_size, base_size * 16 // 9
184
- elif aspect_ratio == "4:3":
185
- return base_size * 4 // 3, base_size
186
- return base_size, base_size
187
-
188
- @spaces.GPU(duration=20) # 40초에서 20초로 감소
189
- def generate_background(prompt: str, aspect_ratio: str) -> Image.Image:
190
- try:
191
- width, height = calculate_dimensions(aspect_ratio)
192
- width, height = adjust_size_to_multiple_of_8(width, height)
193
-
194
- max_size = 768
195
- if width > max_size or height > max_size:
196
- ratio = max_size / max(width, height)
197
- width = int(width * ratio)
198
- height = int(height * ratio)
199
- width, height = adjust_size_to_multiple_of_8(width, height)
200
-
201
- with timer("Background generation"):
202
- try:
203
- with torch.inference_mode():
204
- image = pipe(
205
- prompt=prompt,
206
- width=width,
207
- height=height,
208
- num_inference_steps=8,
209
- guidance_scale=4.0
210
- ).images[0]
211
- except Exception as e:
212
- print(f"Pipeline error: {str(e)}")
213
- return Image.new('RGB', (width, height), 'white')
214
-
215
- return image
216
- except Exception as e:
217
- print(f"Background generation error: {str(e)}")
218
- return Image.new('RGB', (512, 512), 'white')
219
-
220
- def create_position_grid():
221
- return """
222
- <div class="position-grid" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; width: 150px; margin: auto;">
223
- <button class="position-btn" data-pos="top-left">↖</button>
224
- <button class="position-btn" data-pos="top-center">↑</button>
225
- <button class="position-btn" data-pos="top-right">↗</button>
226
- <button class="position-btn" data-pos="middle-left">←</button>
227
- <button class="position-btn" data-pos="middle-center">•</button>
228
- <button class="position-btn" data-pos="middle-right">→</button>
229
- <button class="position-btn" data-pos="bottom-left">↙</button>
230
- <button class="position-btn" data-pos="bottom-center" data-default="true">↓</button>
231
- <button class="position-btn" data-pos="bottom-right">↘</button>
232
- </div>
233
- """
234
-
235
- def calculate_object_position(position: str, bg_size: tuple[int, int], obj_size: tuple[int, int]) -> tuple[int, int]:
236
- """오브젝트의 위치 계산"""
237
- bg_width, bg_height = bg_size
238
- obj_width, obj_height = obj_size
239
-
240
- positions = {
241
- "top-left": (0, 0),
242
- "top-center": ((bg_width - obj_width) // 2, 0),
243
- "top-right": (bg_width - obj_width, 0),
244
- "middle-left": (0, (bg_height - obj_height) // 2),
245
- "middle-center": ((bg_width - obj_width) // 2, (bg_height - obj_height) // 2),
246
- "middle-right": (bg_width - obj_width, (bg_height - obj_height) // 2),
247
- "bottom-left": (0, bg_height - obj_height),
248
- "bottom-center": ((bg_width - obj_width) // 2, bg_height - obj_height),
249
- "bottom-right": (bg_width - obj_width, bg_height - obj_height)
250
- }
251
-
252
- return positions.get(position, positions["bottom-center"])
253
-
254
- def resize_object(image: Image.Image, scale_percent: float) -> Image.Image:
255
- """오브젝트 크기 조정"""
256
- width = int(image.width * scale_percent / 100)
257
- height = int(image.height * scale_percent / 100)
258
- return image.resize((width, height), Image.Resampling.LANCZOS)
259
-
260
- def combine_with_background(foreground: Image.Image, background: Image.Image,
261
- position: str = "bottom-center", scale_percent: float = 100) -> Image.Image:
262
- """전경과 배경 합성 함수"""
263
- # 배경 이미지 준비
264
- result = background.convert('RGBA')
265
-
266
- # 오브젝트 크기 조정
267
- scaled_foreground = resize_object(foreground, scale_percent)
268
-
269
- # 오브젝트 위치 계산
270
- x, y = calculate_object_position(position, result.size, scaled_foreground.size)
271
-
272
- # 합성
273
- result.paste(scaled_foreground, (x, y), scaled_foreground)
274
- return result
275
-
276
- @spaces.GPU(duration=30) # 120초에서 30초로 감소
277
- def _gpu_process(img: Image.Image, prompt: str | BoundingBox | None) -> tuple[Image.Image, BoundingBox | None, list[str]]:
278
- time_log: list[str] = []
279
- try:
280
- if isinstance(prompt, str):
281
- t0 = time.time()
282
- bbox = gd_detect(img, prompt)
283
- time_log.append(f"detect: {time.time() - t0}")
284
- if not bbox:
285
- print(time_log[0])
286
- raise gr.Error("No object detected")
287
- else:
288
- bbox = prompt
289
- t0 = time.time()
290
- mask = segmenter(img, bbox)
291
- time_log.append(f"segment: {time.time() - t0}")
292
- return mask, bbox, time_log
293
- except Exception as e:
294
- print(f"GPU process error: {str(e)}")
295
- raise
296
-
297
- def _process(img: Image.Image, prompt: str | BoundingBox | None, bg_prompt: str | None = None, aspect_ratio: str = "1:1") -> tuple[tuple[Image.Image, Image.Image, Image.Image], gr.DownloadButton]:
298
- try:
299
- # 입력 이미지 크기 제한
300
- max_size = 1024
301
- if img.width > max_size or img.height > max_size:
302
- ratio = max_size / max(img.width, img.height)
303
- new_size = (int(img.width * ratio), int(img.height * ratio))
304
- img = img.resize(new_size, Image.LANCZOS)
305
-
306
- # CUDA 메모리 관리 수정
307
- try:
308
- if torch.cuda.is_available():
309
- current_device = torch.cuda.current_device()
310
- with torch.cuda.device(current_device):
311
- torch.cuda.empty_cache()
312
- except Exception as e:
313
- print(f"CUDA memory management failed: {e}")
314
-
315
- with torch.cuda.amp.autocast(enabled=torch.cuda.is_available()):
316
- mask, bbox, time_log = _gpu_process(img, prompt)
317
- masked_alpha = apply_mask(img, mask, defringe=True)
318
-
319
- if bg_prompt:
320
- background = generate_background(bg_prompt, aspect_ratio)
321
- combined = background
322
- else:
323
- combined = Image.alpha_composite(Image.new("RGBA", masked_alpha.size, "white"), masked_alpha)
324
-
325
- clear_memory()
326
-
327
- with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp:
328
- combined.save(temp.name)
329
- return (img, combined, masked_alpha), gr.DownloadButton(value=temp.name, interactive=True)
330
- except Exception as e:
331
- clear_memory()
332
- print(f"Processing error: {str(e)}")
333
- raise gr.Error(f"Processing failed: {str(e)}")
334
-
335
- def on_change_bbox(prompts: dict[str, Any] | None):
336
- return gr.update(interactive=prompts is not None)
337
-
338
-
339
- def on_change_prompt(img: Image.Image | None, prompt: str | None, bg_prompt: str | None = None):
340
- return gr.update(interactive=bool(img and prompt))
341
-
342
-
343
-
344
- def process_prompt(img: Image.Image, prompt: str, bg_prompt: str | None = None,
345
- aspect_ratio: str = "1:1", position: str = "bottom-center",
346
- scale_percent: float = 100) -> tuple[Image.Image, Image.Image]:
347
- try:
348
- if img is None or prompt.strip() == "":
349
- raise gr.Error("Please provide both image and prompt")
350
-
351
- print(f"Processing with position: {position}, scale: {scale_percent}")
352
-
353
- try:
354
- prompt = translate_to_english(prompt)
355
- if bg_prompt:
356
- bg_prompt = translate_to_english(bg_prompt)
357
- except Exception as e:
358
- print(f"Translation error (continuing with original text): {str(e)}")
359
-
360
- results, _ = _process(img, prompt, bg_prompt, aspect_ratio)
361
-
362
- if bg_prompt:
363
- try:
364
- combined = combine_with_background(
365
- foreground=results[2],
366
- background=results[1],
367
- position=position,
368
- scale_percent=scale_percent
369
- )
370
- print(f"Combined image created with position: {position}")
371
- return combined, results[2]
372
- except Exception as e:
373
- print(f"Combination error: {str(e)}")
374
- return results[1], results[2]
375
-
376
- return results[1], results[2]
377
- except Exception as e:
378
- print(f"Error in process_prompt: {str(e)}")
379
- raise gr.Error(str(e))
380
- finally:
381
- clear_memory()
382
-
383
- def process_bbox(img: Image.Image, box_input: str) -> tuple[Image.Image, Image.Image]:
384
- try:
385
- if img is None or box_input.strip() == "":
386
- raise gr.Error("Please provide both image and bounding box coordinates")
387
-
388
- try:
389
- coords = eval(box_input)
390
- if not isinstance(coords, list) or len(coords) != 4:
391
- raise ValueError("Invalid box format")
392
- bbox = tuple(int(x) for x in coords)
393
- except:
394
- raise gr.Error("Invalid box format. Please provide [xmin, ymin, xmax, ymax]")
395
-
396
- # Process the image
397
- results, _ = _process(img, bbox)
398
-
399
- # 합성된 이미지와 추출된 이미지만 반환
400
- return results[1], results[2]
401
- except Exception as e:
402
- raise gr.Error(str(e))
403
-
404
- # Event handler functions 수정
405
- def update_process_button(img, prompt):
406
- return gr.update(
407
- interactive=bool(img and prompt),
408
- variant="primary" if bool(img and prompt) else "secondary"
409
- )
410
-
411
- def update_box_button(img, box_input):
412
- try:
413
- if img and box_input:
414
- coords = eval(box_input)
415
- if isinstance(coords, list) and len(coords) == 4:
416
- return gr.update(interactive=True, variant="primary")
417
- return gr.update(interactive=False, variant="secondary")
418
- except:
419
- return gr.update(interactive=False, variant="secondary")
420
-
421
-
422
- # CSS 정의
423
- css = """
424
- footer {display: none}
425
- .main-title {
426
- text-align: center;
427
- margin: 2em 0;
428
- padding: 1em;
429
- background: #f7f7f7;
430
- border-radius: 10px;
431
- }
432
- .main-title h1 {
433
- color: #2196F3;
434
- font-size: 2.5em;
435
- margin-bottom: 0.5em;
436
- }
437
- .main-title p {
438
- color: #666;
439
- font-size: 1.2em;
440
- }
441
- .container {
442
- max-width: 1200px;
443
- margin: auto;
444
- padding: 20px;
445
- }
446
- .tabs {
447
- margin-top: 1em;
448
- }
449
- .input-group {
450
- background: white;
451
- padding: 1em;
452
- border-radius: 8px;
453
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
454
- }
455
- .output-group {
456
- background: white;
457
- padding: 1em;
458
- border-radius: 8px;
459
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
460
- }
461
- button.primary {
462
- background: #2196F3;
463
- border: none;
464
- color: white;
465
- padding: 0.5em 1em;
466
- border-radius: 4px;
467
- cursor: pointer;
468
- transition: background 0.3s ease;
469
- }
470
- button.primary:hover {
471
- background: #1976D2;
472
- }
473
- .position-btn {
474
- transition: all 0.3s ease;
475
- }
476
- .position-btn:hover {
477
- background-color: #e3f2fd;
478
- }
479
- .position-btn.selected {
480
- background-color: #2196F3;
481
- color: white;
482
- }
483
- """
484
-
485
- # UI 구성
486
- # UI 구성 부분에서 process_btn을 위로 이동하고 position_grid.click 부분 제거
487
-
488
- # UI 구성
489
- with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
490
- gr.HTML("""
491
- <div class="main-title">
492
- <h1>🎨GiniGen Canvas</h1>
493
- <p>AI Integrated Image Creator: Extract objects, generate backgrounds, and adjust ratios and positions to create complete images with AI.</p>
494
- </div>
495
- """)
496
-
497
- with gr.Row():
498
- with gr.Column(scale=1):
499
- input_image = gr.Image(
500
- type="pil",
501
- label="Upload Image",
502
- interactive=True
503
- )
504
- text_prompt = gr.Textbox(
505
- label="Object to Extract",
506
- placeholder="Enter what you want to extract...",
507
- interactive=True
508
- )
509
- with gr.Row():
510
- bg_prompt = gr.Textbox(
511
- label="Background Prompt (optional)",
512
- placeholder="Describe the background...",
513
- interactive=True,
514
- scale=3
515
- )
516
- aspect_ratio = gr.Dropdown(
517
- choices=["1:1", "16:9", "9:16", "4:3"],
518
- value="1:1",
519
- label="Aspect Ratio",
520
- interactive=True,
521
- visible=True,
522
- scale=1
523
- )
524
-
525
- with gr.Row(visible=False) as object_controls:
526
- with gr.Column(scale=1):
527
- with gr.Row():
528
- position = gr.State(value="bottom-center")
529
- btn_top_left = gr.Button("↖")
530
- btn_top_center = gr.Button("↑")
531
- btn_top_right = gr.Button("↗")
532
- with gr.Row():
533
- btn_middle_left = gr.Button("←")
534
- btn_middle_center = gr.Button("•")
535
- btn_middle_right = gr.Button("→")
536
- with gr.Row():
537
- btn_bottom_left = gr.Button("↙")
538
- btn_bottom_center = gr.Button("↓")
539
- btn_bottom_right = gr.Button("↘")
540
- with gr.Column(scale=1):
541
- scale_slider = gr.Slider(
542
- minimum=10,
543
- maximum=200,
544
- value=50,
545
- step=5,
546
- label="Object Size (%)"
547
- )
548
-
549
- process_btn = gr.Button(
550
- "Process",
551
- variant="primary",
552
- interactive=False
553
- )
554
-
555
- # 각 버튼에 대한 클릭 이벤트 처리
556
- def update_position(new_position):
557
- return new_position
558
-
559
- btn_top_left.click(fn=lambda: update_position("top-left"), outputs=position)
560
- btn_top_center.click(fn=lambda: update_position("top-center"), outputs=position)
561
- btn_top_right.click(fn=lambda: update_position("top-right"), outputs=position)
562
- btn_middle_left.click(fn=lambda: update_position("middle-left"), outputs=position)
563
- btn_middle_center.click(fn=lambda: update_position("middle-center"), outputs=position)
564
- btn_middle_right.click(fn=lambda: update_position("middle-right"), outputs=position)
565
- btn_bottom_left.click(fn=lambda: update_position("bottom-left"), outputs=position)
566
- btn_bottom_center.click(fn=lambda: update_position("bottom-center"), outputs=position)
567
- btn_bottom_right.click(fn=lambda: update_position("bottom-right"), outputs=position)
568
-
569
- with gr.Column(scale=1):
570
- with gr.Row():
571
- combined_image = gr.Image(
572
- label="Combined Result",
573
- show_download_button=True,
574
- type="pil",
575
- height=512
576
- )
577
- with gr.Row():
578
- extracted_image = gr.Image(
579
- label="Extracted Object",
580
- show_download_button=True,
581
- type="pil",
582
- height=256
583
- )
584
-
585
- # Event bindings
586
- input_image.change(
587
- fn=update_process_button,
588
- inputs=[input_image, text_prompt],
589
- outputs=process_btn,
590
- queue=False
591
- )
592
-
593
- text_prompt.change(
594
- fn=update_process_button,
595
- inputs=[input_image, text_prompt],
596
- outputs=process_btn,
597
- queue=False
598
- )
599
-
600
- def update_controls(bg_prompt):
601
- """배경 프롬프트 입력 여부에 따라 컨트롤 표시 업데이트"""
602
- is_visible = bool(bg_prompt)
603
- return [
604
- gr.update(visible=is_visible), # aspect_ratio
605
- gr.update(visible=is_visible), # object_controls
606
- ]
607
-
608
- bg_prompt.change(
609
- fn=update_controls,
610
- inputs=bg_prompt,
611
- outputs=[aspect_ratio, object_controls],
612
- queue=False
613
- )
614
-
615
- process_btn.click(
616
- fn=process_prompt,
617
- inputs=[
618
- input_image,
619
- text_prompt,
620
- bg_prompt,
621
- aspect_ratio,
622
- position,
623
- scale_slider
624
- ],
625
- outputs=[combined_image, extracted_image],
626
- queue=True
627
- )
628
-
629
-
630
- demo.queue(max_size=5) # 큐 크기 제한
631
- demo.launch(
632
- server_name="0.0.0.0",
633
- server_port=7860,
634
- share=False,
635
- max_threads=2 # 스레드 수 제한
636
- )