File size: 4,537 Bytes
b446b48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""
๋ชฝ๊ธ€๋งˆ์„ ํ”ผ๋“œ ์ด๋ฏธ์ง€ ์ƒ์„ฑ๊ธฐ
์ž…๋ ฅ: ์บ๋ฆญํ„ฐ ์™ธํ˜•(Florence-2 ์ถ”์ถœ) + ํ€˜์ŠคํŠธ(์˜์–ด)
์ถœ๋ ฅ: ํ”ผ๋“œ ์ด๋ฏธ์ง€ (์บ๋ฆญํ„ฐ๊ฐ€ ํ€˜์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ์žฅ๋ฉด)
"""
import os as _os; _os.chdir(_os.path.dirname(_os.path.dirname(_os.path.dirname(_os.path.abspath(__file__)))))
import os, gc
from pathlib import Path
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"

from diffusers import StableDiffusionXLPipeline, DPMSolverMultistepScheduler
import torch

CHARACTER_LORA = "models/lora_v3_32bit/pytorch_lora_weights.safetensors"
BG_LORA        = "models/lora_bg_v1/pytorch_lora_weights.safetensors"

NEGATIVE = (
    "realistic, 3d render, photograph, blurry, dark, gloomy, "
    "watercolor, sketch, painterly, smooth illustration, "
    "modern city, urban, scary, violence, text, watermark, "
    "multiple characters, tiling, repeated"
)


def _to_appearance_str(appearance) -> str:
    """dict ๋˜๋Š” string โ†’ ์™ธํ˜• ํ…์ŠคํŠธ ๋ณ€ํ™˜"""
    if isinstance(appearance, str):
        return appearance.strip()
    if not isinstance(appearance, dict):
        return str(appearance).strip()

    animal   = appearance.get("animal_type", "bear")
    color    = appearance.get("body_color", "")
    shape    = appearance.get("body_shape", "")
    eye      = appearance.get("eye_style", "")
    nose     = appearance.get("nose_mouth") or appearance.get("nose_shape", "")
    cheeks   = appearance.get("cheeks", "")
    ears     = appearance.get("ear_shape", "")
    texture  = appearance.get("texture", "")
    acc      = appearance.get("accessories", "")
    features = appearance.get("distinctive_features", [])
    secondary = appearance.get("secondary_colors", [])

    parts = [f"a {color} pixel art stuffed {animal} character".strip()]
    if shape:    parts.append(shape)
    if isinstance(secondary, list):
        # 3๋‹จ์–ด ์ดํ•˜์ธ ์งง์€ ์ƒ‰์ƒ ๊ฐ’๋งŒ ํฌํ•จ (๋ฌธ์žฅํ˜• ๋ฌ˜์‚ฌ ์ œ์™ธ)
        parts.extend(s for s in secondary[:2] if s and len(str(s).split()) <= 3)
    if eye:      parts.append(eye)
    if cheeks and "no" not in cheeks.lower(): parts.append(cheeks)
    if ears:     parts.append(ears)
    if nose:     parts.append(nose)
    if texture:  parts.append(texture)
    if isinstance(acc, list):
        acc = ", ".join(str(a) for a in acc if a)
    if acc and str(acc).lower() != "none": parts.append(acc)
    # distinctive_features๋Š” eye_style, cheeks ๋“ฑ๊ณผ ์ค‘๋ณต๋˜๋ฏ€๋กœ ์ œ์™ธ

    return ", ".join(p for p in parts if p)


def build_prompt(appearance, quest_en: str):
    """
    prompt   โ†’ ์žฅ๋ฉด/์Šคํƒ€์ผ (CLIP-L)
    prompt_2 โ†’ ์žฅ๋ฉด + ์บ๋ฆญํ„ฐ ์™ธํ˜• (OpenCLIP-bigG, ๋” ๊ฐ•ํ•œ ์ธ์ฝ”๋”์— ๋‘˜ ๋‹ค ์ „๋‹ฌ)
    """
    appearance_str = _to_appearance_str(appearance)
    prompt = (
        f"monglestyle, {quest_en}, "
        f"cute chibi stuffed animal character in action, "
        f"full body pose, 32-bit pixel art scene, pastel colors"
    )
    prompt_2 = f"monglestyle, {appearance_str}"
    return prompt, prompt_2


def load_pipeline():
    print("SDXL ๋กœ๋“œ ์ค‘ (์บ๋ฆญํ„ฐ LoRA + ๋ฐฐ๊ฒฝ LoRA)...")
    pipe = StableDiffusionXLPipeline.from_pretrained(
        "stabilityai/stable-diffusion-xl-base-1.0",
        torch_dtype=torch.float16,
        use_safetensors=True,
    ).to("cuda")

    if not Path(CHARACTER_LORA).exists():
        raise FileNotFoundError(f"Character LoRA not found: {CHARACTER_LORA}")
    if not Path(BG_LORA).exists():
        raise FileNotFoundError(f"Background LoRA not found: {BG_LORA}")

    pipe.load_lora_weights(CHARACTER_LORA, adapter_name="character")
    pipe.load_lora_weights(BG_LORA,        adapter_name="bg")
    pipe.set_adapters(
        ["character", "bg"],
        adapter_weights=[0.8, 0.8],
    )
    pipe.scheduler = DPMSolverMultistepScheduler.from_config(
        pipe.scheduler.config, use_karras_sigmas=True
    )
    pipe.enable_attention_slicing()
    print("๋กœ๋“œ ์™„๋ฃŒ!\n")
    return pipe


def generate(appearance, quest_en: str, pipe, seed: int = 42):
    prompt, prompt_2 = build_prompt(appearance, quest_en)
    print(f"  prompt  : {prompt}")
    print(f"  prompt_2: {prompt_2}")

    return pipe(
        prompt=prompt,
        prompt_2=prompt_2,
        negative_prompt=NEGATIVE,
        num_inference_steps=20,
        guidance_scale=7.5,
        height=1024,
        width=1024,
        generator=torch.Generator("cuda").manual_seed(seed),
    ).images[0]


def unload_pipeline(pipe):
    del pipe
    gc.collect()
    torch.cuda.empty_cache()
    print("ํŒŒ์ดํ”„๋ผ์ธ VRAM ํ•ด์ œ ์™„๋ฃŒ")