seawolf2357 commited on
Commit
9d8651a
ยท
verified ยท
1 Parent(s): 94f3bc2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +276 -64
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  FLUX.1 Kontext Style Transfer
3
  ==============================
4
- Updated: 2025โ€‘07โ€‘12
5
  ---------------------------------
6
  ์ด ์Šคํฌ๋ฆฝํŠธ๋Š” Huggingโ€ฏFace **FLUX.1โ€‘Kontextโ€‘dev** ๋ชจ๋ธ๊ณผ
7
  22โ€ฏ์ข…์˜ ์Šคํƒ€์ผ LoRA ๊ฐ€์ค‘์น˜๋ฅผ ์ด์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์–‘ํ•œ ์˜ˆ์ˆ 
@@ -9,15 +9,15 @@ Updated: 2025โ€‘07โ€‘12
9
 
10
  ์ฃผ์š” ๊ฐœ์„  ์‚ฌํ•ญ
11
  --------------
12
- 1. **๋ชจ๋ธ ์บ์‹ฑ**โ€†โ€“ย `snapshot_download()`๋กœ ์‹คํ–‰ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ
13
- ๋ชจ๋ธ๊ณผ LoRA๋ฅผ ์บ์‹ฑํ•ด ์ดํ›„ GPU ์žก์—์„œ๋„ ์žฌ๋‹ค์šด๋กœ๋“œ๊ฐ€ ์—†๋„๋ก
14
- ํ•จ.
15
- 2. **GPUโ€ฏVRAM ์ž๋™ ํŒ๋ณ„**โ€†โ€“ย GPUโ€ฏVRAM์ด 24โ€ฏGBโ€ฏ๋ฏธ๋งŒ์ด๋ฉด
16
- `torch.float16`ย / `enable_sequential_cpu_offload()`๋ฅผ ์ž๋™ ์ ์šฉ.
17
- 3. **๋‹จ์ผ ๋กœ๋”ฉ ๋ฉ”์‹œ์ง€**โ€†โ€“ย Gradioย `gr.Info()` ๋ฉ”์‹œ์ง€๊ฐ€ ์ตœ์ดˆ 1ํšŒ๋งŒ
18
- ํ‘œ์‹œ๋˜๋„๋ก ์ˆ˜์ •.
19
- 4. **๋ฒ„๊ทธ ํ”ฝ์Šค**โ€†โ€“ย seed ์ฒ˜๋ฆฌ, LoRA ์–ธ๋กœ๋“œ, ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ ๋กœ์ง
20
- ๋“ฑ ์„ธ๋ถ€ ์˜ค๋ฅ˜ ์ˆ˜์ •.
21
 
22
  ------------------------------------------------------------
23
  """
@@ -25,7 +25,8 @@ import os
25
  import gradio as gr
26
  import spaces
27
  import torch
28
- from huggingface_hub import snapshot_download
 
29
  from diffusers import FluxKontextPipeline
30
  from diffusers.utils import load_image
31
  from PIL import Image
@@ -40,19 +41,29 @@ MODEL_ID = "black-forest-labs/FLUX.1-Kontext-dev"
40
  LORA_REPO = "Owen777/Kontext-Style-Loras"
41
  CACHE_DIR = os.getenv("HF_HOME", os.path.expanduser("~/.cache/huggingface"))
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  # --- ์ตœ์ดˆ ์‹คํ–‰ ์‹œ์—๋งŒ ๋‹ค์šด๋กœ๋“œ(์ด๋ฏธ ์บ์‹œ์— ์žˆ์œผ๋ฉด ๊ฑด๋„ˆ๋œ€) ---
44
- MODEL_DIR = snapshot_download(
45
- repo_id=MODEL_ID,
46
- cache_dir=CACHE_DIR,
47
- resume_download=True,
48
- token=True # HF ํ† ํฐ(ํ•„์š” ์‹œ ํ™˜๊ฒฝ๋ณ€์ˆ˜ HF_TOKEN ์ง€์ •)
49
- )
50
- LORA_DIR = snapshot_download(
51
- repo_id=LORA_REPO,
52
- cache_dir=CACHE_DIR,
53
- resume_download=True,
54
- token=True
55
- )
56
 
57
  # ------------------------------------------------------------------
58
  # ์Šคํƒ€์ผย โ†’ย LoRA ํŒŒ์ผ ๋งคํ•‘ & ์„ค๋ช…
@@ -112,7 +123,6 @@ STYLE_DESCRIPTIONS = {
112
  # ------------------------------------------------------------------
113
  _pipeline = None # ๋‚ด๋ถ€ ๊ธ€๋กœ๋ฒŒ ์บ์‹œ
114
 
115
-
116
  def load_pipeline():
117
  """Load (or return cached) FluxKontextPipeline."""
118
  global _pipeline
@@ -157,10 +167,9 @@ def style_transfer(input_image, style_name, prompt_suffix, num_inference_steps,
157
  pipe = load_pipeline()
158
 
159
  # --- Torchย Generator ์„ค์ • ---
160
- if seed > 0:
 
161
  generator = torch.Generator(device="cuda").manual_seed(int(seed))
162
- else:
163
- generator = None # random
164
 
165
  # --- ์ž…๋ ฅ ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ ---
166
  img = input_image if isinstance(input_image, Image.Image) else load_image(input_image)
@@ -178,7 +187,7 @@ def style_transfer(input_image, style_name, prompt_suffix, num_inference_steps,
178
  if prompt_suffix and prompt_suffix.strip():
179
  prompt += f" {prompt_suffix.strip()}"
180
 
181
- gr.Info("Generating styled imageโ€ฆ (24โ€‘60โ€ฏs)")
182
 
183
  result = pipe(
184
  image=img,
@@ -204,7 +213,7 @@ def style_transfer(input_image, style_name, prompt_suffix, num_inference_steps,
204
  # ------------------------------------------------------------------
205
  # Gradio UI ์ •์˜
206
  # ------------------------------------------------------------------
207
- with gr.Blocks(title="FLUX.1 Kontext Style Transfer", theme=gr.themes.Soft()) as demo:
208
  gr.Markdown("""
209
  # ๐ŸŽจ FLUX.1 Kontext Style Transfer
210
 
@@ -231,44 +240,247 @@ with gr.Blocks(title="FLUX.1 Kontext Style Transfer", theme=gr.themes.Soft()) as
231
  generate_btn = gr.Button("๐ŸŽจ Transform Image", variant="primary", size="lg")
232
 
233
  with gr.Column(scale=1):
234
- output_image = gr.Image(label="Styled Result", type="pil", height=400)
235
- gr.Markdown("""
236
- ### ๐Ÿ’ก Tips
237
- * ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋Š” 1024ร—1024๋กœ ๋ฆฌ์‚ฌ์ด์ฆˆ๋ฉ๋‹ˆ๋‹ค.
238
- * ์ตœ์ดˆ 1ํšŒย ๋ชจ๋ธย +ย LoRA ๋‹ค์šด๋กœ๋“œ ํ›„์—๋Š” **์บ์‹œ**๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ 10โ€‘20โ€ฏs ๋‚ด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
239
- * "Additional Instructions"์— ์ƒ‰๊ฐยท์กฐ๋ช…ยทํšจ๊ณผ ๋“ฑ์„ ์˜์–ด๋กœ ๊ฐ„๋‹จํžˆ ์ ์œผ๋ฉด ๊ฒฐ๊ณผ๋ฅผ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
240
- """)
241
-
242
- # --- ์Šคํƒ€์ผ ์„ค๋ช… ์ž๋™ ์—…๋ฐ์ดํŠธ ---
243
- def _update_desc(style):
244
- return STYLE_DESCRIPTIONS.get(style, "")
245
-
246
- style_dropdown.change(fn=_update_desc, inputs=[style_dropdown], outputs=[style_info])
247
-
248
- # --- ์˜ˆ์ œ ---
249
- gr.Examples(
250
- examples=[
251
- ["https://huggingface.co/datasets/black-forest-labs/kontext-bench/resolve/main/test/images/0003.jpg", "Ghibli", ""],
252
- ["https://huggingface.co/datasets/black-forest-labs/kontext-bench/resolve/main/test/images/0003.jpg", "3D_Chibi", "make it extra cute"],
253
- ["https://huggingface.co/datasets/black-forest-labs/kontext-bench/resolve/main/test/images/0003.jpg", "Van_Gogh", "with swirling sky"],
254
- ],
255
- inputs=[input_image, style_dropdown, prompt_suffix],
256
- outputs=output_image,
257
- fn=lambda img, style, prompt: style_transfer(img, style, prompt, 24, 2.5, 42),
258
- cache_examples=False,
259
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
261
- # --- ๋ฒ„ํŠผ ์—ฐ๊ฒฐ ---
262
- generate_btn.click(
263
- fn=style_transfer,
264
- inputs=[input_image, style_dropdown, prompt_suffix, num_steps, guidance, seed],
265
- outputs=output_image,
266
  )
267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  gr.Markdown("""
269
- ---
270
- **Created with โค๏ธ by GiniGEN (2025)**
 
 
271
  """)
272
 
273
- if __name__ == "__main__":
274
- demo.launch(inline=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
  FLUX.1 Kontext Style Transfer
3
  ==============================
4
+ Updated: 2025โ€‘07โ€‘12 (HF_TOKEN ์ง€์›)
5
  ---------------------------------
6
  ์ด ์Šคํฌ๋ฆฝํŠธ๋Š” Huggingโ€ฏFace **FLUX.1โ€‘Kontextโ€‘dev** ๋ชจ๋ธ๊ณผ
7
  22โ€ฏ์ข…์˜ ์Šคํƒ€์ผ LoRA ๊ฐ€์ค‘์น˜๋ฅผ ์ด์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์–‘ํ•œ ์˜ˆ์ˆ 
 
9
 
10
  ์ฃผ์š” ๊ฐœ์„  ์‚ฌํ•ญ
11
  --------------
12
+ 1. **HF_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ง€์›**โ€†โ€“ย `LocalTokenNotFoundError`๋ฅผ
13
+ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด `HF_TOKEN`(๋˜๋Š” ๋Ÿฐํƒ€์ž„ ๋กœ๊ทธ์ธ) ๊ฐ’์„ ์ž๋™์œผ๋กœ
14
+ ๊ฐ์ง€ํ•ด `snapshot_download()`์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
15
+ 2. **๋ชจ๋ธ ์บ์‹ฑ**โ€†โ€“ย `snapshot_download()`๋กœ ์‹คํ–‰ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ
16
+ ๋ชจ๋ธ๊ณผ LoRA๋ฅผ ์บ์‹ฑ.
17
+ 3. **GPUโ€ฏVRAM ์ž๋™ ํŒ๋ณ„**โ€†โ€“ย 24โ€ฏGBย ๋ฏธ๋งŒ์ด๋ฉด FP16ย / CPUย offload.
18
+ 4. **๋‹จ์ผ ๋กœ๋”ฉ ๋ฉ”์‹œ์ง€**โ€†โ€“ย Gradioย `gr.Info()` ๋ฉ”์‹œ์ง€๊ฐ€ ์ตœ์ดˆ 1ํšŒ๋งŒ
19
+ ํ‘œ์‹œ๋˜๋„๋ก ์œ ์ง€.
20
+ 5. **๋ฒ„๊ทธ ํ”ฝ์Šค**โ€†โ€“ย seed ์ฒ˜๋ฆฌ, LoRA ์–ธ๋กœ๋“œ, ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ.
21
 
22
  ------------------------------------------------------------
23
  """
 
25
  import gradio as gr
26
  import spaces
27
  import torch
28
+ from huggingface_hub import snapshot_download, login as hf_login
29
+ from huggingface_hub.errors import LocalTokenNotFoundError
30
  from diffusers import FluxKontextPipeline
31
  from diffusers.utils import load_image
32
  from PIL import Image
 
41
  LORA_REPO = "Owen777/Kontext-Style-Loras"
42
  CACHE_DIR = os.getenv("HF_HOME", os.path.expanduser("~/.cache/huggingface"))
43
 
44
+ # --- HF ํ† ํฐ ์ฒ˜๋ฆฌ --------------------------------------------------
45
+ HF_TOKEN = os.getenv("HF_TOKEN") # ๋Ÿฐํƒ€์ž„์— ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…ํ•˜๊ฑฐ๋‚˜
46
+ # dockerย /ย Space ์„ค์ •์˜ Secrets โ†’ HF_TOKEN ๋กœ ๋“ฑ๋ก
47
+
48
+ def _download_with_token(repo_id: str) -> str:
49
+ """Download repo snapshot with optional token handling."""
50
+ try:
51
+ return snapshot_download(
52
+ repo_id=repo_id,
53
+ cache_dir=CACHE_DIR,
54
+ resume_download=True,
55
+ token=HF_TOKEN if HF_TOKEN else True, # True โ†’ HF_CACHED_TOKEN(orย login)
56
+ )
57
+ except LocalTokenNotFoundError:
58
+ # ๋ฏธ๋กœ๊ทธ์ธย +ย ํ•„์ˆ˜ย ๋™์˜ ๋ชจ๋ธ์ด๋ฉด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ›„ ์ข…๋ฃŒ
59
+ raise RuntimeError(
60
+ "Huggingย Face ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜ HF_TOKEN์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜\n"
61
+ "`huggingface-cli login`์œผ๋กœ ๋กœ๊ทธ์ธํ•ด ์ฃผ์„ธ์š”."
62
+ )
63
+
64
  # --- ์ตœ์ดˆ ์‹คํ–‰ ์‹œ์—๋งŒ ๋‹ค์šด๋กœ๋“œ(์ด๋ฏธ ์บ์‹œ์— ์žˆ์œผ๋ฉด ๊ฑด๋„ˆ๋œ€) ---
65
+ MODEL_DIR = _download_with_token(MODEL_ID)
66
+ LORA_DIR = _download_with_token(LORA_REPO)
 
 
 
 
 
 
 
 
 
 
67
 
68
  # ------------------------------------------------------------------
69
  # ์Šคํƒ€์ผย โ†’ย LoRA ํŒŒ์ผ ๋งคํ•‘ & ์„ค๋ช…
 
123
  # ------------------------------------------------------------------
124
  _pipeline = None # ๋‚ด๋ถ€ ๊ธ€๋กœ๋ฒŒ ์บ์‹œ
125
 
 
126
  def load_pipeline():
127
  """Load (or return cached) FluxKontextPipeline."""
128
  global _pipeline
 
167
  pipe = load_pipeline()
168
 
169
  # --- Torchย Generator ์„ค์ • ---
170
+ generator = None
171
+ if seed and int(seed) > 0:
172
  generator = torch.Generator(device="cuda").manual_seed(int(seed))
 
 
173
 
174
  # --- ์ž…๋ ฅ ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ ---
175
  img = input_image if isinstance(input_image, Image.Image) else load_image(input_image)
 
187
  if prompt_suffix and prompt_suffix.strip():
188
  prompt += f" {prompt_suffix.strip()}"
189
 
190
+ gr.Info("Generating styled imageโ€ฆ (20โ€‘60โ€ฏs)")
191
 
192
  result = pipe(
193
  image=img,
 
213
  # ------------------------------------------------------------------
214
  # Gradio UI ์ •์˜
215
  # ------------------------------------------------------------------
216
+ with gr.Blocks(title="FLUX.1 Context Style Transfer", theme=gr.themes.Soft()) as demo:
217
  gr.Markdown("""
218
  # ๐ŸŽจ FLUX.1 Kontext Style Transfer
219
 
 
240
  generate_btn = gr.Button("๐ŸŽจ Transform Image", variant="primary", size="lg")
241
 
242
  with gr.Column(scale=1):
243
+ output_image = gr.Image(label="Styled Result", type="pil", height=
244
+ """
245
+ FLUX.1 Kontext Style Transfer
246
+ ==============================
247
+ Updated: 2025โ€‘07โ€‘12 (HF_TOKEN ์ง€์›)
248
+ ---------------------------------
249
+ ์ด ์Šคํฌ๋ฆฝํŠธ๋Š” Huggingโ€ฏFace **FLUX.1โ€‘Kontextโ€‘dev** ๋ชจ๋ธ๊ณผ
250
+ 22โ€ฏ์ข…์˜ ์Šคํƒ€์ผ LoRA ๊ฐ€์ค‘์น˜๋ฅผ ์ด์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์–‘ํ•œ ์˜ˆ์ˆ 
251
+ ์Šคํƒ€์ผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” Gradio ๋ฐ๋ชจ์ž…๋‹ˆ๋‹ค.
252
+
253
+ ์ฃผ์š” ๊ฐœ์„  ์‚ฌํ•ญ
254
+ --------------
255
+ 1. **HF_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ง€์›**โ€†โ€“ย `LocalTokenNotFoundError`๋ฅผ
256
+ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด `HF_TOKEN`(๋˜๋Š” ๋Ÿฐํƒ€์ž„ ๋กœ๊ทธ์ธ) ๊ฐ’์„ ์ž๋™์œผ๋กœ
257
+ ๊ฐ์ง€ํ•ด `snapshot_download()`์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
258
+ 2. **๋ชจ๋ธ ์บ์‹ฑ**โ€†โ€“ย `snapshot_download()`๋กœ ์‹คํ–‰ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ
259
+ ๋ชจ๋ธ๊ณผ LoRA๋ฅผ ์บ์‹ฑ.
260
+ 3. **GPUโ€ฏVRAM ์ž๋™ ํŒ๋ณ„**โ€†โ€“ย 24โ€ฏGBย ๋ฏธ๋งŒ์ด๋ฉด FP16ย / CPUย offload.
261
+ 4. **๋‹จ์ผ ๋กœ๋”ฉ ๋ฉ”์‹œ์ง€**โ€†โ€“ย Gradioย `gr.Info()` ๋ฉ”์‹œ์ง€๊ฐ€ ์ตœ์ดˆ 1ํšŒ๋งŒ
262
+ ํ‘œ์‹œ๋˜๋„๋ก ์œ ์ง€.
263
+ 5. **๋ฒ„๊ทธ ํ”ฝ์Šค**โ€†โ€“ย seed ์ฒ˜๋ฆฌ, LoRA ์–ธ๋กœ๋“œ, ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ.
264
+
265
+ ------------------------------------------------------------
266
+ """
267
+ import os
268
+ import gradio as gr
269
+ import spaces
270
+ import torch
271
+ from huggingface_hub import snapshot_download, login as hf_login
272
+ from huggingface_hub.errors import LocalTokenNotFoundError
273
+ from diffusers import FluxKontextPipeline
274
+ from diffusers.utils import load_image
275
+ from PIL import Image
276
+
277
+ # ------------------------------------------------------------------
278
+ # ํ™˜๊ฒฝ ์„ค์ • & ๋ชจ๋ธย /ย LoRA ์‚ฌ์ „ ๋‹ค์šด๋กœ๋“œ
279
+ # ------------------------------------------------------------------
280
+ # ํฐ ํŒŒ์ผ์„ ๋น ๋ฅด๊ฒŒ ๋ฐ›๋„๋ก ๊ฐ€์† ํ”Œ๋ž˜๊ทธ ํ™œ์„ฑํ™”
281
+ os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1")
282
+
283
+ MODEL_ID = "black-forest-labs/FLUX.1-Kontext-dev"
284
+ LORA_REPO = "Owen777/Kontext-Style-Loras"
285
+ CACHE_DIR = os.getenv("HF_HOME", os.path.expanduser("~/.cache/huggingface"))
286
+
287
+ # --- HF ํ† ํฐ ์ฒ˜๋ฆฌ --------------------------------------------------
288
+ HF_TOKEN = os.getenv("HF_TOKEN") # ๋Ÿฐํƒ€์ž„์— ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ฃผ์ž…ํ•˜๊ฑฐ๋‚˜
289
+ # dockerย /ย Space ์„ค์ •์˜ Secrets โ†’ HF_TOKEN ๋กœ ๋“ฑ๋ก
290
+
291
+ def _download_with_token(repo_id: str) -> str:
292
+ """Download repo snapshot with optional token handling."""
293
+ try:
294
+ return snapshot_download(
295
+ repo_id=repo_id,
296
+ cache_dir=CACHE_DIR,
297
+ resume_download=True,
298
+ token=HF_TOKEN if HF_TOKEN else True, # True โ†’ HF_CACHED_TOKEN(orย login)
299
+ )
300
+ except LocalTokenNotFoundError:
301
+ # ๋ฏธ๋กœ๊ทธ์ธย +ย ํ•„์ˆ˜ย ๋™์˜ ๋ชจ๋ธ์ด๋ฉด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ›„ ์ข…๋ฃŒ
302
+ raise RuntimeError(
303
+ "Huggingย Face ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜ HF_TOKEN์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜\n"
304
+ "`huggingface-cli login`์œผ๋กœ ๋กœ๊ทธ์ธํ•ด ์ฃผ์„ธ์š”."
305
+ )
306
+
307
+ # --- ์ตœ์ดˆ ์‹คํ–‰ ์‹œ์—๋งŒ ๋‹ค์šด๋กœ๋“œ(์ด๋ฏธ ์บ์‹œ์— ์žˆ์œผ๋ฉด ๊ฑด๋„ˆ๋œ€) ---
308
+ MODEL_DIR = _download_with_token(MODEL_ID)
309
+ LORA_DIR = _download_with_token(LORA_REPO)
310
+
311
+ # ------------------------------------------------------------------
312
+ # ์Šคํƒ€์ผย โ†’ย LoRA ํŒŒ์ผ ๋งคํ•‘ & ๏ฟฝ๏ฟฝ๏ฟฝ๋ช…
313
+ # ------------------------------------------------------------------
314
+ STYLE_LORA_MAP = {
315
+ "3D_Chibi": "3D_Chibi_lora_weights.safetensors",
316
+ "American_Cartoon": "American_Cartoon_lora_weights.safetensors",
317
+ "Chinese_Ink": "Chinese_Ink_lora_weights.safetensors",
318
+ "Clay_Toy": "Clay_Toy_lora_weights.safetensors",
319
+ "Fabric": "Fabric_lora_weights.safetensors",
320
+ "Ghibli": "Ghibli_lora_weights.safetensors",
321
+ "Irasutoya": "Irasutoya_lora_weights.safetensors",
322
+ "Jojo": "Jojo_lora_weights.safetensors",
323
+ "Oil_Painting": "Oil_Painting_lora_weights.safetensors",
324
+ "Pixel": "Pixel_lora_weights.safetensors",
325
+ "Snoopy": "Snoopy_lora_weights.safetensors",
326
+ "Poly": "Poly_lora_weights.safetensors",
327
+ "LEGO": "LEGO_lora_weights.safetensors",
328
+ "Origami": "Origami_lora_weights.safetensors",
329
+ "Pop_Art": "Pop_Art_lora_weights.safetensors",
330
+ "Van_Gogh": "Van_Gogh_lora_weights.safetensors",
331
+ "Paper_Cutting": "Paper_Cutting_lora_weights.safetensors",
332
+ "Line": "Line_lora_weights.safetensors",
333
+ "Vector": "Vector_lora_weights.safetensors",
334
+ "Picasso": "Picasso_lora_weights.safetensors",
335
+ "Macaron": "Macaron_lora_weights.safetensors",
336
+ "Rick_Morty": "Rick_Morty_lora_weights.safetensors",
337
+ }
338
+
339
+ STYLE_DESCRIPTIONS = {
340
+ "3D_Chibi": "Cute, miniature 3D character style with big heads",
341
+ "American_Cartoon": "Classic American animation style",
342
+ "Chinese_Ink": "Traditional Chinese ink painting aesthetic",
343
+ "Clay_Toy": "Playful clay/plasticine toy appearance",
344
+ "Fabric": "Soft, textile-like rendering",
345
+ "Ghibli": "Studio Ghibli's distinctive anime style",
346
+ "Irasutoya": "Simple, flat Japanese illustration style",
347
+ "Jojo": "JoJo's Bizarre Adventure manga style",
348
+ "Oil_Painting": "Classic oil painting texture and strokes",
349
+ "Pixel": "Retro pixel art style",
350
+ "Snoopy": "Peanuts comic strip style",
351
+ "Poly": "Low-poly 3D geometric style",
352
+ "LEGO": "LEGO brick construction style",
353
+ "Origami": "Paper folding art style",
354
+ "Pop_Art": "Bold, colorful pop art style",
355
+ "Van_Gogh": "Van Gogh's expressive brushstroke style",
356
+ "Paper_Cutting": "Paper cut-out art style",
357
+ "Line": "Clean line art/sketch style",
358
+ "Vector": "Clean vector graphics style",
359
+ "Picasso": "Cubist art style inspired by Picasso",
360
+ "Macaron": "Soft, pastel macaron-like style",
361
+ "Rick_Morty": "Rick and Morty cartoon style",
362
+ }
363
+
364
+ # ------------------------------------------------------------------
365
+ # ํŒŒ์ดํ”„๋ผ์ธ ๋กœ๋” (๋‹จ์ผ ์ธ์Šคํ„ด์Šค)
366
+ # ------------------------------------------------------------------
367
+ _pipeline = None # ๋‚ด๋ถ€ ๊ธ€๋กœ๋ฒŒ ์บ์‹œ
368
+
369
+ def load_pipeline():
370
+ """Load (or return cached) FluxKontextPipeline."""
371
+ global _pipeline
372
+ if _pipeline is not None:
373
+ return _pipeline
374
+
375
+ # VRAM์ด 24โ€ฏGBย ๋ฏธ๋งŒ์ด๋ฉด FP16 ์‚ฌ์šฉ + CPU ์˜คํ”„๋กœ๋”ฉ
376
+ dtype = torch.bfloat16
377
+ vram_gb = torch.cuda.get_device_properties(0).total_memory / 1024**3
378
+ if vram_gb < 24:
379
+ dtype = torch.float16
380
+
381
+ gr.Info("FLUX.1โ€‘Kontext ํŒŒ์ดํ”„๋ผ์ธ ๋กœ๋”ฉ ์ค‘โ€ฆย (์ตœ์ดˆ 1ํšŒ)")
382
 
383
+ pipe = FluxKontextPipeline.from_pretrained(
384
+ MODEL_DIR,
385
+ torch_dtype=dtype,
386
+ local_files_only=True,
 
387
  )
388
 
389
+ pipe.to("cuda")
390
+
391
+ if vram_gb < 24:
392
+ pipe.enable_sequential_cpu_offload()
393
+ else:
394
+ pipe.enable_model_cpu_offload()
395
+
396
+ _pipeline = pipe
397
+ return _pipeline
398
+
399
+ # ------------------------------------------------------------------
400
+ # ์Šคํƒ€์ผ ๋ณ€ํ™˜ ํ•จ์ˆ˜ (Spaces GPU ์žก)
401
+ # ------------------------------------------------------------------
402
+ @spaces.GPU(duration=600)
403
+ def style_transfer(input_image, style_name, prompt_suffix, num_inference_steps, guidance_scale, seed):
404
+ """Apply selected style to the uploaded image."""
405
+ if input_image is None:
406
+ gr.Warning("Please upload an image first!")
407
+ return None
408
+
409
+ try:
410
+ pipe = load_pipeline()
411
+
412
+ # --- Torchย Generator ์„ค์ • ---
413
+ generator = None
414
+ if seed and int(seed) > 0:
415
+ generator = torch.Generator(device="cuda").manual_seed(int(seed))
416
+
417
+ # --- ์ž…๋ ฅ ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ ---
418
+ img = input_image if isinstance(input_image, Image.Image) else load_image(input_image)
419
+ img = img.convert("RGB").resize((1024, 1024), Image.Resampling.LANCZOS)
420
+
421
+ # --- LoRA ๋กœ๋“œ ---
422
+ lora_file = STYLE_LORA_MAP[style_name]
423
+ adapter_name = "style"
424
+ pipe.load_lora_weights(LORA_DIR, weight_name=lora_file, adapter_name=adapter_name)
425
+ pipe.set_adapters([adapter_name], [1.0])
426
+
427
+ # --- ํ”„๋กฌํ”„ํŠธ ๋นŒ๋“œ ---
428
+ human_readable_style = style_name.replace("_", " ")
429
+ prompt = f"Turn this image into the {human_readable_style} style."
430
+ if prompt_suffix and prompt_suffix.strip():
431
+ prompt += f" {prompt_suffix.strip()}"
432
+
433
+ gr.Info("Generating styled imageโ€ฆ (20โ€‘60โ€ฏs)")
434
+
435
+ result = pipe(
436
+ image=img,
437
+ prompt=prompt,
438
+ guidance_scale=float(guidance_scale),
439
+ num_inference_steps=int(num_inference_steps),
440
+ generator=generator,
441
+ height=1024,
442
+ width=1024,
443
+ )
444
+
445
+ # --- LoRA ์–ธ๋กœ๋“œ & GPU ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ---
446
+ pipe.unload_lora_weights(adapter_name=adapter_name)
447
+ torch.cuda.empty_cache()
448
+
449
+ return result.images[0]
450
+
451
+ except Exception as e:
452
+ torch.cuda.empty_cache()
453
+ gr.Error(f"Error during style transfer: {e}")
454
+ return None
455
+
456
+ # ------------------------------------------------------------------
457
+ # Gradio UI ์ •์˜
458
+ # ------------------------------------------------------------------
459
+ with gr.Blocks(title="FLUX.1 Context Style Transfer", theme=gr.themes.Soft()) as demo:
460
  gr.Markdown("""
461
+ # ๐ŸŽจ FLUX.1 Kontext Style Transfer
462
+
463
+ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ 22โ€ฏ์ข…์˜ ์˜ˆ์ˆ  ์Šคํƒ€์ผ๋กœ ๋ณ€ํ™˜ํ•˜์„ธ์š”!
464
+ (๋ชจ๋ธโ€ฏ/โ€ฏLoRA๋Š” ์ตœ์ดˆ ์‹คํ–‰ ์‹œ์—๋งŒ ๋‹ค์šด๋กœ๋“œ๋˜๋ฉฐ, ์ดํ›„ ์‹คํ–‰์€ ๋น ๋ฆ…๋‹ˆ๋‹ค.)
465
  """)
466
 
467
+ with gr.Row():
468
+ with gr.Column(scale=1):
469
+ input_image = gr.Image(label="Upload Image", type="pil", height=400)
470
+ style_dropdown = gr.Dropdown(
471
+ choices=list(STYLE_LORA_MAP.keys()),
472
+ value="Ghibli",
473
+ label="Select Style",
474
+ )
475
+ style_info = gr.Textbox(label="Style Description", value=STYLE_DESCRIPTIONS["Ghibli"], interactive=False, lines=2)
476
+ prompt_suffix = gr.Textbox(label="Additional Instructions (Optional)", placeholder="e.g. add dramatic lighting", lines=2)
477
+
478
+ with gr.Accordion("Advanced Settings", open=False):
479
+ num_steps = gr.Slider(minimum=10, maximum=50, value=24, step=1, label="Inference Steps")
480
+ guidance = gr.Slider(minimum=1.0, maximum=7.5, value=2.5, step=0.1, label="Guidance Scale")
481
+ seed = gr.Number(label="Seed (0 = random)", value=42)
482
+
483
+ generate_btn = gr.Button("๐ŸŽจ Transform Image", variant="primary", size="lg")
484
+
485
+ with gr.Column(scale=1):
486
+ output_image = gr.Image(label="Styled Result", type="pil", height=