ginipick commited on
Commit
3deced9
·
verified ·
1 Parent(s): cd0adcf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -687
app.py CHANGED
@@ -1,693 +1,35 @@
1
- import gradio as gr
2
- import numpy as np
3
- from PIL import Image, ImageDraw
4
- from gradio_client import Client, handle_file
5
- import random
6
- import tempfile
7
  import os
8
- import logging
9
- import torch
10
- from diffusers import AutoencoderKL, TCDScheduler
11
- from diffusers.models.model_loading_utils import load_state_dict
12
- from huggingface_hub import hf_hub_download
13
- from pathlib import Path
14
- import torchaudio
15
- from einops import rearrange
16
- from scipy.io import wavfile
17
- from transformers import pipeline
18
 
19
- # 환경 변수 설정으로 torch.load 체크 우회 (임시 해결책)
20
- os.environ["TRANSFORMERS_ALLOW_UNSAFE_DESERIALIZATION"] = "1"
21
-
22
- # Spaces GPU
23
- try:
24
- import spaces
25
- except:
26
- # GPU 데코레이터가 없을 때를 위한 더미 데코레이터
27
- class spaces:
28
- @staticmethod
29
- def GPU(duration=None):
30
- def decorator(func):
31
- return func
32
- return decorator
33
-
34
- # MMAudio imports
35
- try:
36
- import mmaudio
37
- except ImportError:
38
- os.system("pip install -e .")
39
- import mmaudio
40
-
41
- from mmaudio.eval_utils import (ModelConfig, all_model_cfg, generate, load_video, make_video,
42
- setup_eval_logging)
43
- from mmaudio.model.flow_matching import FlowMatching
44
- from mmaudio.model.networks import MMAudio, get_my_mmaudio
45
- from mmaudio.model.sequence_config import SequenceConfig
46
- from mmaudio.model.utils.features_utils import FeaturesUtils
47
-
48
- # ControlNet 모델 로드
49
- try:
50
- from controlnet_union import ControlNetModel_Union
51
- from pipeline_fill_sd_xl import StableDiffusionXLFillPipeline
52
-
53
- # ControlNet 설정 및 로드
54
- config_file = hf_hub_download(
55
- "xinsir/controlnet-union-sdxl-1.0",
56
- filename="config_promax.json",
57
- )
58
-
59
- config = ControlNetModel_Union.load_config(config_file)
60
- controlnet_model = ControlNetModel_Union.from_config(config)
61
-
62
- model_file = hf_hub_download(
63
- "xinsir/controlnet-union-sdxl-1.0",
64
- filename="diffusion_pytorch_model_promax.safetensors",
65
- )
66
- state_dict = load_state_dict(model_file)
67
- loaded_keys = list(state_dict.keys())
68
-
69
- result = ControlNetModel_Union._load_pretrained_model(
70
- controlnet_model, state_dict, model_file, "xinsir/controlnet-union-sdxl-1.0", loaded_keys
71
- )
72
-
73
- model = result[0]
74
- model = model.to(device="cuda", dtype=torch.float16)
75
-
76
- # VAE 로드
77
- vae = AutoencoderKL.from_pretrained(
78
- "madebyollin/sdxl-vae-fp16-fix", torch_dtype=torch.float16
79
- ).to("cuda")
80
-
81
- # 파이프라인 로드
82
- pipe = StableDiffusionXLFillPipeline.from_pretrained(
83
- "SG161222/RealVisXL_V5.0_Lightning",
84
- torch_dtype=torch.float16,
85
- vae=vae,
86
- controlnet=model,
87
- variant="fp16",
88
- ).to("cuda")
89
-
90
- pipe.scheduler = TCDScheduler.from_config(pipe.scheduler.config)
91
-
92
- OUTPAINT_MODEL_LOADED = True
93
- except Exception as e:
94
- logging.error(f"Failed to load outpainting models: {str(e)}")
95
- OUTPAINT_MODEL_LOADED = False
96
-
97
- # MMAudio 모델 설정
98
- if torch.cuda.is_available():
99
- device = torch.device("cuda")
100
- torch.backends.cuda.matmul.allow_tf32 = True
101
- torch.backends.cudnn.allow_tf32 = True
102
- torch.backends.cudnn.benchmark = True
103
- else:
104
- device = torch.device("cpu")
105
-
106
- dtype = torch.bfloat16
107
-
108
- # MMAudio 모델 초기화
109
- try:
110
- model_mmaudio: ModelConfig = all_model_cfg['large_44k_v2']
111
- model_mmaudio.download_if_needed()
112
- output_dir = Path('./output/gradio')
113
- setup_eval_logging()
114
-
115
- # 번역기 설정
116
- try:
117
- translator = pipeline("translation",
118
- model="Helsinki-NLP/opus-mt-ko-en",
119
- device="cpu",
120
- use_fast=True,
121
- trust_remote_code=False)
122
- except Exception as e:
123
- logging.warning(f"Failed to load translation model: {e}")
124
- translator = None
125
-
126
- def get_mmaudio_model() -> tuple[MMAudio, FeaturesUtils, SequenceConfig]:
127
- with torch.cuda.device(device):
128
- seq_cfg = model_mmaudio.seq_cfg
129
- net: MMAudio = get_my_mmaudio(model_mmaudio.model_name).to(device, dtype).eval()
130
- net.load_weights(torch.load(model_mmaudio.model_path, map_location=device, weights_only=True))
131
- logging.info(f'Loaded weights from {model_mmaudio.model_path}')
132
-
133
- feature_utils = FeaturesUtils(
134
- tod_vae_ckpt=model_mmaudio.vae_path,
135
- synchformer_ckpt=model_mmaudio.synchformer_ckpt,
136
- enable_conditions=True,
137
- mode=model_mmaudio.mode,
138
- bigvgan_vocoder_ckpt=model_mmaudio.bigvgan_16k_path,
139
- need_vae_encoder=False
140
- ).to(device, dtype).eval()
141
-
142
- return net, feature_utils, seq_cfg
143
-
144
- net_mmaudio, feature_utils, seq_cfg = get_mmaudio_model()
145
- MMAUDIO_MODEL_LOADED = True
146
- except Exception as e:
147
- logging.error(f"Failed to load MMAudio models: {str(e)}")
148
- MMAUDIO_MODEL_LOADED = False
149
- translator = None
150
-
151
- # API URLs
152
- TEXT2IMG_API_URL = "http://211.233.58.201:7896"
153
- VIDEO_API_URL = "http://211.233.58.201:7875"
154
-
155
- # 로깅 설정
156
- logging.basicConfig(level=logging.INFO)
157
-
158
- # Image size presets
159
- IMAGE_PRESETS = {
160
- "커스텀": {"width": 1024, "height": 1024},
161
- "1:1 정사각형": {"width": 1024, "height": 1024},
162
- "4:3 표준": {"width": 1024, "height": 768},
163
- "16:9 와이드스크린": {"width": 1024, "height": 576},
164
- "9:16 세로형": {"width": 576, "height": 1024},
165
- "6:19 특수 세로형": {"width": 324, "height": 1024},
166
- "Instagram 정사각형": {"width": 1080, "height": 1080},
167
- "Instagram 스토리": {"width": 1080, "height": 1920},
168
- "Instagram 가로형": {"width": 1080, "height": 566},
169
- "Facebook 커버": {"width": 820, "height": 312},
170
- "Twitter 헤더": {"width": 1500, "height": 500},
171
- "YouTube 썸네일": {"width": 1280, "height": 720},
172
- "LinkedIn 배너": {"width": 1584, "height": 396},
173
- }
174
-
175
- def update_dimensions(preset):
176
- if preset in IMAGE_PRESETS:
177
- return IMAGE_PRESETS[preset]["width"], IMAGE_PRESETS[preset]["height"]
178
- return 1024, 1024
179
-
180
- def generate_text_to_image(prompt, width, height, guidance, inference_steps, seed):
181
- if not prompt:
182
- return None, "프롬프트를 입력해주세요"
183
-
184
  try:
185
- client = Client(TEXT2IMG_API_URL)
186
- if seed == -1:
187
- seed = random.randint(0, 9999999)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- result = client.predict(
190
- prompt=prompt,
191
- width=int(width),
192
- height=int(height),
193
- guidance=float(guidance),
194
- inference_steps=int(inference_steps),
195
- seed=int(seed),
196
- do_img2img=False,
197
- init_image=None,
198
- image2image_strength=0.8,
199
- resize_img=True,
200
- api_name="/generate_image"
201
- )
202
- return result[0], f"사용된 시드: {result[1]}"
203
  except Exception as e:
204
- logging.error(f"Image generation error: {str(e)}")
205
- return None, f"오류: {str(e)}"
206
-
207
- def generate_video_from_image(image, prompt="", length=4.0):
208
- if image is None:
209
- return None
210
-
211
- try:
212
- # 이미지 저장
213
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as fp:
214
- temp_path = fp.name
215
- Image.fromarray(image).save(temp_path)
216
-
217
- # API 호출
218
- client = Client(VIDEO_API_URL)
219
- result = client.predict(
220
- input_image=handle_file(temp_path),
221
- prompt=prompt if prompt else "Generate natural motion",
222
- n_prompt="",
223
- seed=random.randint(0, 9999999),
224
- use_teacache=True,
225
- video_length=float(length),
226
- api_name="/process"
227
- )
228
-
229
- os.unlink(temp_path)
230
-
231
- if result and len(result) > 0:
232
- video_dict = result[0]
233
- return video_dict.get("video") if isinstance(video_dict, dict) else None
234
-
235
- except Exception as e:
236
- logging.error(f"Video generation error: {str(e)}")
237
- return None
238
-
239
- def prepare_image_and_mask(image, width, height, overlap_percentage, alignment):
240
- """이미지와 마스크를 준비하는 함수"""
241
- if image is None:
242
- return None, None
243
-
244
- # PIL 이미지로 변환
245
- if isinstance(image, np.ndarray):
246
- image = Image.fromarray(image).convert('RGB')
247
-
248
- target_size = (width, height)
249
-
250
- # 이미지를 타겟 크기에 맞게 조정
251
- scale_factor = min(target_size[0] / image.width, target_size[1] / image.height)
252
- new_width = int(image.width * scale_factor)
253
- new_height = int(image.height * scale_factor)
254
-
255
- # 이미지 리사이즈
256
- source = image.resize((new_width, new_height), Image.LANCZOS)
257
-
258
- # 오버랩 계산
259
- overlap_x = int(new_width * (overlap_percentage / 100))
260
- overlap_y = int(new_height * (overlap_percentage / 100))
261
- overlap_x = max(overlap_x, 1)
262
- overlap_y = max(overlap_y, 1)
263
-
264
- # 정렬에 따른 마진 계산
265
- if alignment == "가운데":
266
- margin_x = (target_size[0] - new_width) // 2
267
- margin_y = (target_size[1] - new_height) // 2
268
- elif alignment == "왼쪽":
269
- margin_x = 0
270
- margin_y = (target_size[1] - new_height) // 2
271
- elif alignment == "오른쪽":
272
- margin_x = target_size[0] - new_width
273
- margin_y = (target_size[1] - new_height) // 2
274
- elif alignment == "위":
275
- margin_x = (target_size[0] - new_width) // 2
276
- margin_y = 0
277
- elif alignment == "아래":
278
- margin_x = (target_size[0] - new_width) // 2
279
- margin_y = target_size[1] - new_height
280
-
281
- # 배경 이미지 생성
282
- background = Image.new('RGB', target_size, (255, 255, 255))
283
- background.paste(source, (margin_x, margin_y))
284
-
285
- # 마스크 생성
286
- mask = Image.new('L', target_size, 255)
287
- mask_draw = ImageDraw.Draw(mask)
288
-
289
- # 마스크 영역 그리기 (영어 정렬과 매칭)
290
- white_gaps_patch = 2
291
-
292
- left_overlap = margin_x + overlap_x if alignment != "왼쪽" else margin_x
293
- right_overlap = margin_x + new_width - overlap_x if alignment != "오른쪽" else margin_x + new_width
294
- top_overlap = margin_y + overlap_y if alignment != "위" else margin_y
295
- bottom_overlap = margin_y + new_height - overlap_y if alignment != "아래" else margin_y + new_height
296
-
297
- mask_draw.rectangle([
298
- (left_overlap, top_overlap),
299
- (right_overlap, bottom_overlap)
300
- ], fill=0)
301
-
302
- return background, mask
303
-
304
- def preview_outpaint(image, width, height, overlap_percentage, alignment):
305
- """아웃페인팅 미리보기"""
306
- background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, alignment)
307
- if background is None:
308
- return None
309
-
310
- # 미리보기 이미지 생성
311
- preview = background.copy().convert('RGBA')
312
-
313
- # 반투명 빨간색 오버레이
314
- red_overlay = Image.new('RGBA', background.size, (255, 0, 0, 64))
315
-
316
- # 마스크 적용
317
- red_mask = Image.new('RGBA', background.size, (0, 0, 0, 0))
318
- red_mask.paste(red_overlay, (0, 0), mask)
319
-
320
- # 오버레이 합성
321
- preview = Image.alpha_composite(preview, red_mask)
322
-
323
- return preview
324
-
325
- @spaces.GPU(duration=24)
326
- def outpaint_image(image, prompt, width, height, overlap_percentage, alignment, num_steps=8):
327
- """이미지 아웃페인팅 실행"""
328
- if image is None:
329
- return None
330
-
331
- if not OUTPAINT_MODEL_LOADED:
332
- return Image.new('RGB', (width, height), (200, 200, 200))
333
-
334
- try:
335
- # 이미지와 마스크 준비
336
- background, mask = prepare_image_and_mask(image, width, height, overlap_percentage, alignment)
337
- if background is None:
338
- return None
339
-
340
- # cnet_image 생성 (마스크 영역을 검은색으로)
341
- cnet_image = background.copy()
342
- cnet_image.paste(0, (0, 0), mask)
343
-
344
- # 프롬프트 준비
345
- final_prompt = f"{prompt}, high quality, 4k" if prompt else "high quality, 4k"
346
-
347
- # GPU에서 실행
348
- with torch.autocast(device_type="cuda", dtype=torch.float16):
349
- (
350
- prompt_embeds,
351
- negative_prompt_embeds,
352
- pooled_prompt_embeds,
353
- negative_pooled_prompt_embeds,
354
- ) = pipe.encode_prompt(final_prompt, "cuda", True)
355
-
356
- # 생성 프로세스
357
- for generated_image in pipe(
358
- prompt_embeds=prompt_embeds,
359
- negative_prompt_embeds=negative_prompt_embeds,
360
- pooled_prompt_embeds=pooled_prompt_embeds,
361
- negative_pooled_prompt_embeds=negative_pooled_prompt_embeds,
362
- image=cnet_image,
363
- num_inference_steps=num_steps
364
- ):
365
- # 중간 결과 (필요시 사용)
366
- pass
367
-
368
- # 최종 이미지
369
- final_image = generated_image
370
-
371
- # RGBA로 변환하고 마스크 적용
372
- final_image = final_image.convert("RGBA")
373
- cnet_image.paste(final_image, (0, 0), mask)
374
-
375
- return cnet_image
376
-
377
- except Exception as e:
378
- logging.error(f"Outpainting error: {str(e)}")
379
- return background if 'background' in locals() else None
380
-
381
- # MMAudio 관련 함수들
382
- def translate_prompt(text):
383
- try:
384
- if translator is None:
385
- return text
386
-
387
- if text and any(ord(char) >= 0x3131 and ord(char) <= 0xD7A3 for char in text):
388
- with torch.no_grad():
389
- translation = translator(text)[0]['translation_text']
390
- return translation
391
- return text
392
- except Exception as e:
393
- logging.error(f"Translation error: {e}")
394
- return text
395
-
396
- @spaces.GPU
397
- @torch.inference_mode()
398
- def video_to_audio(video: gr.Video, prompt: str, negative_prompt: str, seed: int, num_steps: int,
399
- cfg_strength: float, duration: float):
400
- if not MMAUDIO_MODEL_LOADED:
401
- return None
402
-
403
- prompt = translate_prompt(prompt)
404
- negative_prompt = translate_prompt(negative_prompt)
405
-
406
- rng = torch.Generator(device=device)
407
- rng.manual_seed(seed)
408
- fm = FlowMatching(min_sigma=0, inference_mode='euler', num_steps=num_steps)
409
-
410
- clip_frames, sync_frames, duration = load_video(video, duration)
411
- clip_frames = clip_frames.unsqueeze(0)
412
- sync_frames = sync_frames.unsqueeze(0)
413
- seq_cfg.duration = duration
414
- net_mmaudio.update_seq_lengths(seq_cfg.latent_seq_len, seq_cfg.clip_seq_len, seq_cfg.sync_seq_len)
415
-
416
- audios = generate(clip_frames,
417
- sync_frames, [prompt],
418
- negative_text=[negative_prompt],
419
- feature_utils=feature_utils,
420
- net=net_mmaudio,
421
- fm=fm,
422
- rng=rng,
423
- cfg_strength=cfg_strength)
424
- audio = audios.float().cpu()[0]
425
-
426
- video_save_path = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name
427
- make_video(video,
428
- video_save_path,
429
- audio,
430
- sampling_rate=seq_cfg.sampling_rate,
431
- duration_sec=seq_cfg.duration)
432
- return video_save_path
433
-
434
- # CSS
435
- css = """
436
- :root {
437
- --primary-color: #f8c3cd;
438
- --secondary-color: #b3e5fc;
439
- --background-color: #f5f5f7;
440
- --card-background: #ffffff;
441
- --text-color: #424242;
442
- --accent-color: #ffb6c1;
443
- --success-color: #c8e6c9;
444
- --warning-color: #fff9c4;
445
- --shadow-color: rgba(0, 0, 0, 0.1);
446
- --border-radius: 12px;
447
- }
448
- .gradio-container {
449
- max-width: 1200px !important;
450
- margin: 0 auto !important;
451
- }
452
- .panel-box {
453
- border-radius: var(--border-radius) !important;
454
- box-shadow: 0 8px 16px var(--shadow-color) !important;
455
- background-color: var(--card-background) !important;
456
- padding: 20px !important;
457
- margin-bottom: 20px !important;
458
- }
459
- #generate-btn, #video-btn, #outpaint-btn, #preview-btn, #audio-btn {
460
- background: linear-gradient(135deg, #ff9a9e, #fad0c4) !important;
461
- font-size: 1.1rem !important;
462
- padding: 12px 24px !important;
463
- margin-top: 10px !important;
464
- width: 100% !important;
465
- }
466
- .tabitem {
467
- min-height: 700px !important;
468
- }
469
- """
470
-
471
- # Gradio Interface
472
- demo = gr.Blocks(css=css, title="AI 이미지 & 비디오 & 오디오 생성기")
473
-
474
- with demo:
475
- gr.Markdown("# 🎨 Ginigen 스튜디오")
476
-
477
- with gr.Tabs() as tabs:
478
- # 첫 번째 탭: 텍스트 to 이미지
479
- with gr.Tab("텍스트→이미지→비디오", elem_classes="tabitem"):
480
- with gr.Row(equal_height=True):
481
- # 입력 컬럼
482
- with gr.Column(scale=1):
483
- with gr.Group(elem_classes="panel-box"):
484
- gr.Markdown("### 📝 이미지 생성 설정")
485
-
486
- prompt = gr.Textbox(
487
- label="프롬프트(한글/영어 가능)",
488
- placeholder="생성하고 싶은 이미지를 설명하세요...",
489
- lines=3
490
- )
491
-
492
- size_preset = gr.Dropdown(
493
- choices=list(IMAGE_PRESETS.keys()),
494
- value="1:1 정사각형",
495
- label="크기 프리셋"
496
- )
497
-
498
- with gr.Row():
499
- width = gr.Slider(256, 2048, 1024, step=64, label="너비")
500
- height = gr.Slider(256, 2048, 1024, step=64, label="높이")
501
-
502
- with gr.Row():
503
- guidance = gr.Slider(1.0, 20.0, 3.5, step=0.1, label="가이던스")
504
- steps = gr.Slider(1, 50, 30, step=1, label="스텝")
505
-
506
- seed = gr.Number(label="시드 (-1=랜덤)", value=-1)
507
-
508
- generate_btn = gr.Button("🎨 이미지 생성", variant="primary", elem_id="generate-btn")
509
-
510
- with gr.Group(elem_classes="panel-box"):
511
- gr.Markdown("### 🎬 비디오 생성 설정")
512
-
513
- video_prompt = gr.Textbox(
514
- label="(선택) 비디오 프롬프트(영어로 입력)",
515
- placeholder="비디오의 움직임을 설명하세요... (비워두면 기본 움직임 적용)",
516
- lines=2
517
- )
518
-
519
- video_length = gr.Slider(
520
- minimum=1,
521
- maximum=60,
522
- value=4,
523
- step=0.5,
524
- label="비디오 길이 (초)",
525
- info="1초에서 60초까지 선택 가능합니다"
526
- )
527
-
528
- video_btn = gr.Button("🎬 비디오로 변환", variant="secondary", elem_id="video-btn")
529
-
530
- # 출력 컬럼
531
- with gr.Column(scale=1):
532
- with gr.Group(elem_classes="panel-box"):
533
- gr.Markdown("### 🖼️ 생성 결과")
534
-
535
- output_image = gr.Image(label="생성된 이미지", type="numpy")
536
- output_seed = gr.Textbox(label="시드 정보")
537
- output_video = gr.Video(label="생성된 비디오")
538
-
539
- # 두 번째 탭: 이미지 아웃페인팅
540
- with gr.Tab("이미지 비율 변경/생성", elem_classes="tabitem"):
541
- with gr.Row(equal_height=True):
542
- # 입력 컬럼
543
- with gr.Column(scale=1):
544
- with gr.Group(elem_classes="panel-box"):
545
- gr.Markdown("### 🖼️ 이미지 업로드")
546
-
547
- input_image = gr.Image(
548
- label="원본 이미지",
549
- type="numpy"
550
- )
551
-
552
- outpaint_prompt = gr.Textbox(
553
- label="프롬프트 (선택)",
554
- placeholder="확장할 영역에 대한 설명...",
555
- lines=2
556
- )
557
-
558
- with gr.Group(elem_classes="panel-box"):
559
- gr.Markdown("### ⚙️ 아웃페인팅 설정")
560
-
561
- outpaint_size_preset = gr.Dropdown(
562
- choices=list(IMAGE_PRESETS.keys()),
563
- value="16:9 와이드스크린",
564
- label="목표 크기 프리셋"
565
- )
566
-
567
- with gr.Row():
568
- outpaint_width = gr.Slider(256, 2048, 1280, step=64, label="목표 너비")
569
- outpaint_height = gr.Slider(256, 2048, 720, step=64, label="목표 높이")
570
-
571
- alignment = gr.Dropdown(
572
- choices=["가운데", "왼쪽", "오른쪽", "위", "아래"],
573
- value="가운데",
574
- label="정렬"
575
- )
576
-
577
- overlap_percentage = gr.Slider(
578
- minimum=1,
579
- maximum=50,
580
- value=10,
581
- step=1,
582
- label="마스크 오버랩 (%)"
583
- )
584
-
585
- outpaint_steps = gr.Slider(
586
- minimum=4,
587
- maximum=12,
588
- value=8,
589
- step=1,
590
- label="추론 스텝"
591
- )
592
-
593
- preview_btn = gr.Button("👁️ 미리보기", elem_id="preview-btn")
594
- outpaint_btn = gr.Button("🎨 아웃페인팅 실행", variant="primary", elem_id="outpaint-btn")
595
-
596
- # 출력 컬럼
597
- with gr.Column(scale=1):
598
- with gr.Group(elem_classes="panel-box"):
599
- gr.Markdown("### 🖼️ 결과")
600
-
601
- preview_image = gr.Image(label="미리보기")
602
- outpaint_result = gr.Image(label="아웃페인팅 결과")
603
-
604
- # 세 번째 탭: 비디오 + 오디오
605
- with gr.Tab("비디오 + 오디오", elem_classes="tabitem"):
606
- with gr.Row(equal_height=True):
607
- # 입력 컬럼
608
- with gr.Column(scale=1):
609
- with gr.Group(elem_classes="panel-box"):
610
- gr.Markdown("### 🎥 비디오 업로드")
611
-
612
- audio_video_input = gr.Video(
613
- label="입력 비디오",
614
- sources=["upload"]
615
- )
616
-
617
- with gr.Group(elem_classes="panel-box"):
618
- gr.Markdown("### 🎵 오디오 생성 설정")
619
-
620
- audio_prompt = gr.Textbox(
621
- label="프롬프트 (한글 지원)" if MMAUDIO_MODEL_LOADED and translator else "프롬프트",
622
- placeholder="생성하고 싶은 오디오를 설명하세요... (예: 평화로운 피아노 음악)",
623
- lines=3
624
- )
625
-
626
- audio_negative_prompt = gr.Textbox(
627
- label="네거티브 프롬프트",
628
- value="music",
629
- placeholder="원하지 않는 요소...",
630
- lines=2
631
- )
632
-
633
- with gr.Row():
634
- audio_seed = gr.Number(label="시드", value=0)
635
- audio_steps = gr.Number(label="스텝", value=25)
636
-
637
- with gr.Row():
638
- audio_cfg = gr.Number(label="가이던스 스케일", value=4.5)
639
- audio_duration = gr.Number(label="지속시간 (초)", value=9999)
640
-
641
- audio_btn = gr.Button("🎵 오디오 생성 및 합성", variant="primary", elem_id="audio-btn")
642
-
643
- # 출력 컬럼
644
- with gr.Column(scale=1):
645
- with gr.Group(elem_classes="panel-box"):
646
- gr.Markdown("### 🎬 생성 결과")
647
-
648
- output_video_with_audio = gr.Video(
649
- label="오디오가 추가된 비디오",
650
- interactive=False
651
- )
652
-
653
- if not MMAUDIO_MODEL_LOADED:
654
- gr.Markdown("⚠️ MMAudio 모델을 로드하지 못했습니다. 이 기능은 사용할 수 없습니다.")
655
-
656
- # 이벤트 연결 - 첫 번째 탭
657
- size_preset.change(update_dimensions, [size_preset], [width, height])
658
-
659
- generate_btn.click(
660
- generate_text_to_image,
661
- [prompt, width, height, guidance, steps, seed],
662
- [output_image, output_seed]
663
- )
664
-
665
- video_btn.click(
666
- lambda img, v_prompt, length: generate_video_from_image(img, v_prompt, length) if img is not None else None,
667
- [output_image, video_prompt, video_length],
668
- [output_video]
669
- )
670
-
671
- # 이벤트 연결 - 두 번째 탭
672
- outpaint_size_preset.change(update_dimensions, [outpaint_size_preset], [outpaint_width, outpaint_height])
673
-
674
- preview_btn.click(
675
- preview_outpaint,
676
- [input_image, outpaint_width, outpaint_height, overlap_percentage, alignment],
677
- [preview_image]
678
- )
679
-
680
- outpaint_btn.click(
681
- outpaint_image,
682
- [input_image, outpaint_prompt, outpaint_width, outpaint_height, overlap_percentage, alignment, outpaint_steps],
683
- [outpaint_result]
684
- )
685
-
686
- # 이벤트 연결 - 세 번째 탭
687
- audio_btn.click(
688
- video_to_audio,
689
- [audio_video_input, audio_prompt, audio_negative_prompt, audio_seed, audio_steps, audio_cfg, audio_duration],
690
- [output_video_with_audio]
691
- )
692
 
693
- demo.launch()
 
 
 
 
 
 
 
 
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()