younginpiniti commited on
Commit
c89df6e
·
1 Parent(s): 0663c69

feat: 애니메이션 모델 지원하는 Stable Diffusion WebUI 추가

Browse files
Files changed (5) hide show
  1. .dockerignore +24 -0
  2. Dockerfile +40 -0
  3. README.md +57 -6
  4. app.py +388 -0
  5. requirements.txt +19 -0
.dockerignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git
2
+ .git
3
+ .gitattributes
4
+
5
+ # Python
6
+ __pycache__
7
+ *.pyc
8
+ *.pyo
9
+ *.pyd
10
+ .Python
11
+ env
12
+ venv
13
+ .venv
14
+
15
+ # IDE
16
+ .vscode
17
+ .idea
18
+ *.swp
19
+ *.swo
20
+
21
+ # 기타
22
+ *.log
23
+ .DS_Store
24
+ Thumbs.db
Dockerfile ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 허깅페이스 스페이스용 스테이블 디퓨전 WebUI Dockerfile
2
+ FROM python:3.10-slim
3
+
4
+ # 시스템 의존성 설치
5
+ RUN apt-get update && apt-get install -y \
6
+ git \
7
+ wget \
8
+ libgl1-mesa-glx \
9
+ libglib2.0-0 \
10
+ libsm6 \
11
+ libxext6 \
12
+ libxrender-dev \
13
+ libgomp1 \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # 작업 디렉토리 설정
17
+ WORKDIR /app
18
+
19
+ # 비root 사용자 생성 (허깅페이스 요구사항)
20
+ RUN useradd -m -u 1000 user
21
+ USER user
22
+ ENV HOME=/home/user \
23
+ PATH=/home/user/.local/bin:$PATH
24
+
25
+ # 작업 디렉토리 설정
26
+ WORKDIR $HOME/app
27
+
28
+ # requirements 복사 및 설치
29
+ COPY --chown=user requirements.txt .
30
+ RUN pip install --no-cache-dir --upgrade pip && \
31
+ pip install --no-cache-dir -r requirements.txt
32
+
33
+ # 애플리케이션 파일 복사
34
+ COPY --chown=user . .
35
+
36
+ # 허깅페이스 스페이스는 포트 7860 사용
37
+ EXPOSE 7860
38
+
39
+ # 애플리케이션 실행
40
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,12 +1,63 @@
1
  ---
2
  title: Stable Diffusion WebUI
3
- emoji: 👁
4
  colorFrom: pink
5
- colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 6.3.0
8
- app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Stable Diffusion WebUI
3
+ emoji: 🌸
4
  colorFrom: pink
5
+ colorTo: purple
6
+ sdk: docker
 
 
7
  pinned: false
8
  ---
9
 
10
+ # 🌸 Anime Diffusion WebUI
11
+
12
+ 허깅페이스 스페이스에서 실행되는 애니메이션 스타일 이미지 생성기입니다.
13
+
14
+ ## ✨ 기능
15
+
16
+ - **텍스트-이미지 생성**: 프롬프트를 입력하여 이미지 생성
17
+ - **다양한 애니메이션 모델**: 7가지 인기 모델 지원
18
+ - **네거티브 프롬프트**: 원하지 않는 요소 제외
19
+ - **이미지 크기 조절**: 256x256 ~ 768x768
20
+ - **스텝 수 조절**: 생성 품질 조절
21
+ - **가이던스 스케일**: 프롬프트 준수 정도 조절
22
+ - **시드 설정**: 재현 가능한 결과
23
+
24
+ ## 🤖 지원 모델
25
+
26
+ | 모델 | 특징 |
27
+ |------|------|
28
+ | **Anything V5** | 가장 인기 있는 애니메이션 모델 |
29
+ | **Counterfeit V3** | 고품질 애니메이션/일러스트 |
30
+ | **DreamShaper V8** | 다양한 스타일 지원 |
31
+ | **OpenJourney V4** | Midjourney 스타일 |
32
+ | **Stable Diffusion v1.5** | 기본 모델 |
33
+ | **MeinaMix V11** | 고퀄리티 애니메이션 |
34
+ | **ReV Animated** | 애니메이션 특화 |
35
+
36
+ ## 🚀 사용법
37
+
38
+ 1. 드롭다운에서 원하는 모델을 선택합니다
39
+ 2. 프롬프트 입력창에 원하는 이미지를 설명합니다
40
+ 3. (선택) 네거티브 프롬프트로 원하지 않는 요소를 지정합니다
41
+ 4. 이미지 크기와 생성 옵션을 조절합니다
42
+ 5. "🚀 이미지 생성" 버튼을 클릭합니다
43
+
44
+ ## 💡 프롬프트 예시
45
+
46
+ ```
47
+ 1girl, solo, long blue hair, blue eyes, school uniform,
48
+ cherry blossoms, masterpiece, best quality, detailed
49
+ ```
50
+
51
+ ## ⚠️ 주의사항
52
+
53
+ - CPU 모드에서는 이미지 생성에 2-5분 정도 소요됩니다
54
+ - 첫 실행 시 모델 다운로드로 추가 시간이 필요합니다
55
+ - GPU 스페이스를 사용하면 훨씬 빠른 생성이 가능합니다
56
+
57
+ ## 🛠️ 기술 스택
58
+
59
+ - **Diffusers** - 허깅페이스 디퓨전 라이브러리
60
+ - **Gradio** - 웹 인터페이스
61
+ - **Docker** - 컨테이너 환경
62
+ - **PyTorch** - 딥러닝 프레임워크
63
+
app.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 스테이블 디퓨전 WebUI - 허깅페이스 스페이스용
3
+ Gradio 인터페이스를 통한 이미지 생성 (애니메이션 모델 지원)
4
+ """
5
+
6
+ import gradio as gr
7
+ import torch
8
+ from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
9
+ from PIL import Image
10
+ import os
11
+ import gc
12
+
13
+ # 사용 가능한 모델 목록
14
+ MODELS = {
15
+ "🎨 Anything V5 (애니메이션)": "stablediffusionapi/anything-v5",
16
+ "🌸 Counterfeit V3 (고품질 애니메이션)": "gsdf/Counterfeit-V3.0",
17
+ "✨ DreamShaper V8 (다목적)": "Lykon/DreamShaper",
18
+ "🎭 OpenJourney (Midjourney 스타일)": "prompthero/openjourney-v4",
19
+ "🖼️ Stable Diffusion v1.5 (기본)": "runwayml/stable-diffusion-v1-5",
20
+ "🌟 MeinaMix (애니메이션)": "Meina/MeinaMix_V11",
21
+ "💫 ReV Animated (애니메이션)": "stablediffusionapi/rev-animated",
22
+ }
23
+
24
+ # 디바이스 설정
25
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
26
+ DTYPE = torch.float16 if torch.cuda.is_available() else torch.float32
27
+
28
+ print(f"🚀 디바이스: {DEVICE}, 데이터 타입: {DTYPE}")
29
+
30
+ # 현재 로드된 모델 정보
31
+ current_model_id = None
32
+ pipe = None
33
+
34
+ def clear_memory():
35
+ """메모리 정리"""
36
+ global pipe
37
+ if pipe is not None:
38
+ del pipe
39
+ pipe = None
40
+ gc.collect()
41
+ if torch.cuda.is_available():
42
+ torch.cuda.empty_cache()
43
+
44
+ def load_model(model_name: str):
45
+ """모델 로드 함수"""
46
+ global pipe, current_model_id
47
+
48
+ model_id = MODELS.get(model_name)
49
+ if model_id is None:
50
+ return None, f"❌ 알 수 없는 모델: {model_name}"
51
+
52
+ # 이미 같은 모델이 로드되어 있으면 스킵
53
+ if current_model_id == model_id and pipe is not None:
54
+ return pipe, f"✅ {model_name} 이미 로드됨"
55
+
56
+ # 기존 모델 정리
57
+ clear_memory()
58
+
59
+ print(f"📥 모델 로딩 중: {model_name}...")
60
+
61
+ try:
62
+ pipe = StableDiffusionPipeline.from_pretrained(
63
+ model_id,
64
+ torch_dtype=DTYPE,
65
+ safety_checker=None,
66
+ requires_safety_checker=False,
67
+ use_safetensors=True
68
+ )
69
+
70
+ # 빠른 스케줄러 사용
71
+ pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
72
+
73
+ # 디바이스로 이동
74
+ pipe = pipe.to(DEVICE)
75
+
76
+ # 메모리 최적화
77
+ pipe.enable_attention_slicing()
78
+ if hasattr(pipe, 'enable_vae_slicing'):
79
+ pipe.enable_vae_slicing()
80
+
81
+ current_model_id = model_id
82
+ print(f"✅ 모델 로딩 완료: {model_name}")
83
+ return pipe, f"✅ {model_name} 로딩 완료!"
84
+
85
+ except Exception as e:
86
+ print(f"❌ 모델 로딩 실패: {e}")
87
+ return None, f"❌ 모델 로딩 실패: {str(e)}"
88
+
89
+ def generate_image(
90
+ model_name: str,
91
+ prompt: str,
92
+ negative_prompt: str = "",
93
+ num_inference_steps: int = 25,
94
+ guidance_scale: float = 7.5,
95
+ width: int = 512,
96
+ height: int = 512,
97
+ seed: int = -1,
98
+ progress=gr.Progress()
99
+ ):
100
+ """
101
+ 이미지 생성 함수
102
+
103
+ Args:
104
+ model_name: 사용할 모델 이름
105
+ prompt: 이미지 생성 프롬프트
106
+ negative_prompt: 네거티브 프롬프트
107
+ num_inference_steps: 추론 스텝 수
108
+ guidance_scale: 가이던스 스케일
109
+ width: 이미지 너비
110
+ height: 이미지 높이
111
+ seed: 시드 값 (-1이면 랜덤)
112
+
113
+ Returns:
114
+ 생성된 이미지, 상태 메시지
115
+ """
116
+
117
+ global pipe
118
+
119
+ if not prompt.strip():
120
+ return None, "⚠️ 프롬프트를 입력해주세요!"
121
+
122
+ # 모델 로드
123
+ progress(0.1, desc="모델 로딩 중...")
124
+ pipe, status = load_model(model_name)
125
+ if pipe is None:
126
+ return None, status
127
+
128
+ # 애니메이션 모델용 기본 네거티브 프롬프트
129
+ default_negative = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
130
+
131
+ if negative_prompt.strip():
132
+ full_negative = f"{negative_prompt}, {default_negative}"
133
+ else:
134
+ full_negative = default_negative
135
+
136
+ # 시드 설정
137
+ if seed == -1:
138
+ seed = torch.randint(0, 2**32 - 1, (1,)).item()
139
+
140
+ generator = torch.Generator(device=DEVICE).manual_seed(int(seed))
141
+
142
+ try:
143
+ progress(0.3, desc="이미지 생성 중...")
144
+ print(f"🎨 이미지 생성 중... 프롬프트: {prompt[:50]}...")
145
+
146
+ # 이미지 생성
147
+ result = pipe(
148
+ prompt=prompt,
149
+ negative_prompt=full_negative,
150
+ num_inference_steps=num_inference_steps,
151
+ guidance_scale=guidance_scale,
152
+ width=width,
153
+ height=height,
154
+ generator=generator
155
+ )
156
+
157
+ image = result.images[0]
158
+ progress(1.0, desc="완료!")
159
+ print("✅ 이미지 생성 완료!")
160
+
161
+ return image, f"✅ 생성 완료! (시드: {seed})"
162
+
163
+ except Exception as e:
164
+ print(f"❌ 이미지 생성 실패: {e}")
165
+ return None, f"❌ 이미지 생성 실패: {str(e)}"
166
+
167
+ # Gradio 인터페이스 생성
168
+ def create_interface():
169
+ """Gradio 웹 인터페이스 생성"""
170
+
171
+ # 커스텀 CSS
172
+ custom_css = """
173
+ .gradio-container {
174
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
175
+ max-width: 1200px !important;
176
+ }
177
+
178
+ .generate-btn {
179
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
180
+ border: none !important;
181
+ color: white !important;
182
+ font-weight: bold !important;
183
+ font-size: 1.2em !important;
184
+ padding: 15px 30px !important;
185
+ border-radius: 10px !important;
186
+ transition: all 0.3s ease !important;
187
+ width: 100% !important;
188
+ }
189
+
190
+ .generate-btn:hover {
191
+ transform: translateY(-2px) !important;
192
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5) !important;
193
+ }
194
+
195
+ .title {
196
+ text-align: center;
197
+ background: linear-gradient(135deg, #ff6b9d 0%, #c44569 50%, #764ba2 100%);
198
+ -webkit-background-clip: text;
199
+ -webkit-text-fill-color: transparent;
200
+ font-size: 2.8em;
201
+ font-weight: bold;
202
+ margin-bottom: 5px;
203
+ }
204
+
205
+ .subtitle {
206
+ text-align: center;
207
+ color: #888;
208
+ font-size: 1.1em;
209
+ margin-bottom: 25px;
210
+ }
211
+
212
+ .model-dropdown {
213
+ border: 2px solid #764ba2 !important;
214
+ border-radius: 8px !important;
215
+ }
216
+
217
+ .output-image {
218
+ border-radius: 12px !important;
219
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
220
+ }
221
+
222
+ .status-box {
223
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
224
+ border-radius: 8px;
225
+ padding: 10px;
226
+ text-align: center;
227
+ }
228
+ """
229
+
230
+ with gr.Blocks(css=custom_css, title="Stable Diffusion WebUI - Anime") as demo:
231
+ # 헤더
232
+ gr.HTML("""
233
+ <div class="title">🌸 Anime Diffusion WebUI</div>
234
+ <div class="subtitle">애니메이션 스타일 이미지 생성기 | AI Art Generator</div>
235
+ """)
236
+
237
+ with gr.Row():
238
+ # 왼쪽: 입력 패널
239
+ with gr.Column(scale=1):
240
+ # 모델 선택
241
+ model_dropdown = gr.Dropdown(
242
+ label="🤖 모델 선택 (Model)",
243
+ choices=list(MODELS.keys()),
244
+ value="🎨 Anything V5 (애니메이션)",
245
+ elem_classes=["model-dropdown"]
246
+ )
247
+
248
+ prompt = gr.Textbox(
249
+ label="📝 프롬프트 (Prompt)",
250
+ placeholder="1girl, solo, long hair, blue eyes, school uniform, cherry blossoms, detailed, masterpiece, best quality",
251
+ lines=3
252
+ )
253
+
254
+ negative_prompt = gr.Textbox(
255
+ label="🚫 네거티브 프롬프트 (Negative Prompt)",
256
+ placeholder="추가할 네거티브 프롬프트 (기본값이 자동 적용됩니다)",
257
+ lines=2
258
+ )
259
+
260
+ with gr.Row():
261
+ width = gr.Slider(
262
+ label="📐 너비",
263
+ minimum=256,
264
+ maximum=768,
265
+ value=512,
266
+ step=64
267
+ )
268
+ height = gr.Slider(
269
+ label="📐 높이",
270
+ minimum=256,
271
+ maximum=768,
272
+ value=768,
273
+ step=64
274
+ )
275
+
276
+ with gr.Row():
277
+ num_steps = gr.Slider(
278
+ label="🔄 스텝 수",
279
+ minimum=10,
280
+ maximum=50,
281
+ value=25,
282
+ step=1
283
+ )
284
+ guidance = gr.Slider(
285
+ label="🎯 CFG 스케일",
286
+ minimum=1.0,
287
+ maximum=15.0,
288
+ value=7.0,
289
+ step=0.5
290
+ )
291
+
292
+ seed = gr.Number(
293
+ label="🎲 시드 (-1 = 랜덤)",
294
+ value=-1,
295
+ precision=0
296
+ )
297
+
298
+ generate_btn = gr.Button(
299
+ "🚀 이미지 생성",
300
+ elem_classes=["generate-btn"]
301
+ )
302
+
303
+ status_text = gr.Textbox(
304
+ label="📊 상태",
305
+ value="모델을 선택하고 프롬프트를 입력해주세요",
306
+ interactive=False,
307
+ elem_classes=["status-box"]
308
+ )
309
+
310
+ # 오른쪽: 출력 패널
311
+ with gr.Column(scale=1):
312
+ output_image = gr.Image(
313
+ label="🖼️ 생성된 이미지",
314
+ type="pil",
315
+ elem_classes=["output-image"]
316
+ )
317
+
318
+ # 예제 - 애니메이션 스타일
319
+ gr.Examples(
320
+ examples=[
321
+ ["🎨 Anything V5 (애니메이션)", "1girl, solo, long blue hair, heterochromia, school uniform, cherry blossoms, sitting, detailed background, masterpiece, best quality", ""],
322
+ ["🎨 Anything V5 (애니메이션)", "1boy, solo, white hair, red eyes, dark cape, sword, dark fantasy, dramatic lighting, masterpiece", ""],
323
+ ["🌸 Counterfeit V3 (고품질 애니메이션)", "1girl, kimono, japanese garden, autumn leaves, serene expression, ultra detailed, 8k", ""],
324
+ ["✨ DreamShaper V8 (다목적)", "beautiful anime girl, studio ghibli style, forest spirit, magical atmosphere, soft lighting", ""],
325
+ ["🎭 OpenJourney (Midjourney 스타일)", "mdjrny-v4 style, cute robot character, pastel colors, kawaii, digital art", ""],
326
+ ["🌟 MeinaMix (애니메이션)", "1girl, maid outfit, silver hair, purple eyes, cafe interior, warm lighting, masterpiece", ""],
327
+ ],
328
+ inputs=[model_dropdown, prompt, negative_prompt],
329
+ label="💡 예제 프롬프트 (클릭하여 적용)"
330
+ )
331
+
332
+ # 프롬프트 가이드
333
+ with gr.Accordion("📖 프롬프트 작성 가이드", open=False):
334
+ gr.Markdown("""
335
+ ### 🎨 애니메이션 스타일 프롬프트 팁
336
+
337
+ **품질 태그** (권장):
338
+ - `masterpiece, best quality, high quality` - 고품질 출력
339
+ - `detailed, ultra detailed` - 디테일 강조
340
+ - `8k, highres` - 고해상도 느낌
341
+
342
+ **캐릭터 묘사**:
343
+ - `1girl, 1boy, solo` - 캐릭터 수
344
+ - `long hair, short hair, blue eyes` - 외모
345
+ - `school uniform, dress, armor` - 복장
346
+
347
+ **배경/분위기**:
348
+ - `cherry blossoms, night sky, fantasy` - 배경
349
+ - `dramatic lighting, soft lighting` - 조명
350
+ - `floating, dynamic pose` - 포즈
351
+
352
+ **스타일**:
353
+ - `anime style, manga style` - 일반 애니메이션
354
+ - `studio ghibli style` - 지브리 스타일
355
+ - `chibi, cute` - 귀여운 스타일
356
+ """)
357
+
358
+ # 이벤트 핸들러
359
+ generate_btn.click(
360
+ fn=generate_image,
361
+ inputs=[model_dropdown, prompt, negative_prompt, num_steps, guidance, width, height, seed],
362
+ outputs=[output_image, status_text]
363
+ )
364
+
365
+ # 푸터
366
+ gr.HTML("""
367
+ <div style="text-align: center; margin-top: 30px; padding: 20px; color: #888; border-top: 1px solid #eee;">
368
+ <p>🌸 Powered by Diffusers & Gradio | 🤗 Hugging Face Spaces</p>
369
+ <p style="font-size: 0.9em;">⚠️ CPU 모드에서는 이미지 생성에 2-5분 정도 소요됩니다.</p>
370
+ <p style="font-size: 0.85em; color: #aaa;">첫 실행 시 모델 다운로드로 인해 시간이 더 걸릴 수 있습니다.</p>
371
+ </div>
372
+ """)
373
+
374
+ return demo
375
+
376
+ # 메인 실행
377
+ if __name__ == "__main__":
378
+ print("🌸 Anime Diffusion WebUI 시작...")
379
+
380
+ # Gradio 앱 생성 및 실행
381
+ demo = create_interface()
382
+
383
+ # 허깅페이스 스페이스에서는 포트 7860 사용
384
+ demo.launch(
385
+ server_name="0.0.0.0",
386
+ server_port=7860,
387
+ share=False
388
+ )
requirements.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 스테이블 디퓨전 핵심 라이브러리
2
+ diffusers>=0.25.0
3
+ transformers>=4.36.0
4
+ accelerate>=0.25.0
5
+ safetensors>=0.4.0
6
+
7
+ # 이미지 처리
8
+ Pillow>=10.0.0
9
+ numpy>=1.24.0
10
+
11
+ # 웹 인터페이스
12
+ gradio>=4.15.0
13
+
14
+ # PyTorch (CPU 버전 - 허깅페이스 무료 스페이스용)
15
+ torch>=2.1.0
16
+ torchvision>=0.16.0
17
+
18
+ # 추가 유틸리티
19
+ invisible-watermark>=0.2.0