potato commited on
Commit
3545cb6
·
1 Parent(s): 282cf1d

Add project files, removing large SVG

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. app.py +209 -153
  2. checkpoints/netG_A_latest.pth +3 -0
  3. generated_svgs/1760266666_かわいい猫のイラスト.svg +0 -0
  4. generated_svgs/1760267178_かわいい猫のイラスト.svg +0 -0
  5. generated_svgs/1760267463_かわいい猫のイラスト.svg +0 -0
  6. generated_svgs/1760267518_かわいい猫のイラスト.svg +0 -0
  7. generated_svgs/1760267577_綺麗な空.svg +0 -0
  8. generated_svgs/1760267696_綺麗な花.svg +0 -0
  9. generated_svgs/1760267891_綺麗な花.svg +0 -0
  10. generated_svgs/1760267916_綺麗な花.svg +0 -0
  11. generated_svgs/1760268197_綺麗な花火.svg +0 -0
  12. generated_svgs/1760271739_花火大会.svg +0 -0
  13. generated_svgs/1760271869_木の下に寝ている猫.svg +0 -0
  14. generated_svgs/1760274351_木の下に寝ている猫.svg +0 -0
  15. generated_svgs/1760275635_美人.svg +0 -0
  16. generated_svgs/1760276190_木の下に寝ている犬芝.svg +0 -0
  17. generated_svgs/1760276623_木の下に寝ている犬芝.svg +0 -0
  18. generated_svgs/1760276934_かわいい犬芝.svg +0 -0
  19. generated_svgs/20251012230621_かわいい犬芝.svg +0 -0
  20. generated_svgs/20251012230704_かわいい犬芝.svg +0 -0
  21. generated_svgs/20251012230829_かわいい猫.svg +0 -0
  22. generated_svgs/20251012231033_かわいい猫.svg +0 -0
  23. generated_svgs/20251012232924_図書館でテーブルの上に寝ている猫がいる.svg +0 -0
  24. generated_svgs/20251013000204_図書館でテーブルの上に寝ている猫がいる.svg +0 -0
  25. generated_svgs/20251013000305_図書館でテーブルの上に寝ている猫がいる.svg +0 -0
  26. generated_svgs/20251013000451_図書館でテーブルの上に寝ている猫がいる.svg +0 -0
  27. generated_svgs/20251013000537_図書館でテーブルの上に寝ている猫がいる.svg +0 -0
  28. generated_svgs/20251013000605_図書館でテーブルの上に寝ている猫がいる.svg +0 -0
  29. generated_svgs/20251013000634_図書館でテーブルの上に寝ている猫がいる.svg +0 -0
  30. generated_svgs/20251013002929_人が猫と抱いている.svg +0 -0
  31. requirements.txt +7 -4
  32. thumbnails/1760266666_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png +0 -0
  33. thumbnails/1760267178_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png +0 -0
  34. thumbnails/1760267463_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png +0 -0
  35. thumbnails/1760267518_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png +0 -0
  36. thumbnails/1760267577_/347/266/272/351/272/227/343/201/252/347/251/272.png +0 -0
  37. thumbnails/1760267696_/347/266/272/351/272/227/343/201/252/350/212/261.png +0 -0
  38. thumbnails/1760267891_/347/266/272/351/272/227/343/201/252/350/212/261.png +0 -0
  39. thumbnails/1760267916_/347/266/272/351/272/227/343/201/252/350/212/261.png +0 -0
  40. thumbnails/1760268197_/347/266/272/351/272/227/343/201/252/350/212/261/347/201/253.png +0 -0
  41. thumbnails/1760271739_/350/212/261/347/201/253/345/244/247/344/274/232.png +0 -0
  42. thumbnails/1760271869_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/214/253.png +0 -0
  43. thumbnails/1760274351_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/214/253.png +0 -0
  44. thumbnails/1760275635_/347/276/216/344/272/272.png +0 -0
  45. thumbnails/1760276190_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/212/254/350/212/235.png +0 -0
  46. thumbnails/1760276623_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/212/254/350/212/235.png +0 -0
  47. thumbnails/1760276934_/343/201/213/343/202/217/343/201/204/343/201/204/347/212/254/350/212/235.png +0 -0
  48. thumbnails/20251012230621_/343/201/213/343/202/217/343/201/204/343/201/204/347/212/254/350/212/235.png +0 -0
  49. thumbnails/20251012230704_/343/201/213/343/202/217/343/201/204/343/201/204/347/212/254/350/212/235.png +0 -0
  50. thumbnails/20251012230829_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253.png +0 -0
app.py CHANGED
@@ -1,154 +1,210 @@
1
- import gradio as gr
2
- import numpy as np
3
- import random
4
-
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
  import torch
8
-
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
-
12
- if torch.cuda.is_available():
13
- torch_dtype = torch.float16
14
- else:
15
- torch_dtype = torch.float32
16
-
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
-
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
22
-
23
-
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
42
- prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
- ]
59
-
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
64
- }
65
- """
66
-
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
-
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
78
- )
79
-
80
- run_button = gr.Button("Run", scale=0, variant="primary")
81
-
82
- result = gr.Image(label="Result", show_label=False)
83
-
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
90
- )
91
-
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
98
- )
99
-
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
101
-
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
110
-
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
117
- )
118
-
119
- with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
126
- )
127
-
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
134
- )
135
-
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
149
- ],
150
- outputs=[result, seed],
151
- )
152
-
153
- if __name__ == "__main__":
154
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
 
 
 
 
 
2
  import torch
3
+ import vtracer
4
+ import tempfile
5
+ import cairosvg
6
+ import re
7
+ from PIL import Image
8
+ from datetime import datetime
9
+
10
+ from flask import Flask, request, jsonify, send_from_directory
11
+ from flask_cors import CORS
12
+
13
+ from diffusers import StableDiffusionPipeline, LMSDiscreteScheduler
14
+
15
+ import torchvision.transforms as transforms
16
+ from model import Generator
17
+
18
+ def setup_directories():
19
+ os.makedirs(SVG_DIR, exist_ok=True)
20
+ os.makedirs(THUMBNAIL_DIR, exist_ok=True)
21
+ print(f"Directories '{SVG_DIR}' and '{THUMBNAIL_DIR}' are ready.")
22
+
23
+ def sanitize_filename(prompt):
24
+ """Removes characters that are invalid for filenames."""
25
+
26
+ s = re.sub(r'[\\/*?:"<>|]', "", prompt)
27
+
28
+ return s[:100]
29
+
30
+ SVG_DIR = os.path.join(os.getcwd(), 'generated_svgs')
31
+ THUMBNAIL_DIR = os.path.join(os.getcwd(), 'thumbnails')
32
+ SKETCH_MODEL_WEIGHTS = 'checkpoints/netG_A_latest.pth'
33
+
34
+ class ImageToSvgPipeline:
35
+ """
36
+ A class to handle the entire pipeline from text prompt to SVG.
37
+ Initializes models once to be reused.
38
+ """
39
+ def __init__(self, sketch_model_path: str):
40
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
41
+ print(f"Using device: {self.device}")
42
+
43
+ self._initialize_rinna_model()
44
+ self._initialize_sketch_model(sketch_model_path)
45
+
46
+ def _initialize_rinna_model(self):
47
+ print("Loading Rinna Stable Diffusion model...")
48
+ model_id = "rinna/japanese-stable-diffusion"
49
+
50
+ self.rinna_pipe = StableDiffusionPipeline.from_pretrained(
51
+ model_id,
52
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
53
+ )
54
+ self.rinna_pipe.scheduler = LMSDiscreteScheduler(
55
+ beta_start=0.00085, beta_end=0.012,
56
+ beta_schedule="scaled_linear", num_train_timesteps=1000
57
+ )
58
+ self.rinna_pipe.tokenizer.model_max_length = 77
59
+ self.rinna_pipe.to(self.device)
60
+ print("Rinna model loaded.")
61
+
62
+ def _initialize_sketch_model(self, model_path: str):
63
+ print(f"Loading Sketch Generator model from {model_path}...")
64
+ if not os.path.exists(model_path):
65
+ raise FileNotFoundError(f"Sketch model weights not found at: {model_path}")
66
+
67
+ self.sketch_model = Generator(input_nc=3, output_nc=1, n_residual_blocks=3)
68
+ self.sketch_model.to(self.device)
69
+ self.sketch_model.load_state_dict(torch.load(model_path, map_location=self.device))
70
+ self.sketch_model.eval()
71
+
72
+ self.sketch_transform = transforms.Compose([
73
+ transforms.ToTensor(),
74
+ ])
75
+ print("Sketch model loaded.")
76
+
77
+ def _generate_image(self, prompt: str, negative_prompt: str, steps: int = 30) -> Image.Image:
78
+ print(f"Generating image for prompt: '{prompt}'")
79
+ with torch.no_grad():
80
+ image = self.rinna_pipe(
81
+ prompt,
82
+ negative_prompt=negative_prompt,
83
+ num_inference_steps=steps,
84
+ guidance_scale=7.5,
85
+ width=512,
86
+ height=512,
87
+ ).images[0]
88
+ return image
89
+
90
+ def _convert_to_sketch(self, image: Image.Image) -> Image.Image:
91
+ print("Converting image to sketch...")
92
+ with torch.no_grad():
93
+ input_tensor = self.sketch_transform(image.convert("RGB")).unsqueeze(0).to(self.device)
94
+ output_tensor = self.sketch_model(input_tensor)
95
+ output_tensor = output_tensor.squeeze(0).cpu()
96
+ sketch_image = transforms.ToPILImage()(output_tensor)
97
+ return sketch_image
98
+
99
+ def _extract_svg(self, image: Image.Image) -> str:
100
+ print("Extracting SVG from sketch...")
101
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
102
+ image.save(tmp_file.name)
103
+ tmp_path = tmp_file.name
104
+
105
+ try:
106
+ svg_output_path = tmp_path.replace(".png", ".svg")
107
+ vtracer.convert_image_to_svg_py(tmp_path, svg_output_path)
108
+
109
+ with open(svg_output_path, 'r', encoding='utf-8') as f:
110
+ svg_data = f.read()
111
+ finally:
112
+ if os.path.exists(tmp_path): os.remove(tmp_path)
113
+ if 'svg_output_path' in locals() and os.path.exists(svg_output_path): os.remove(svg_output_path)
114
+
115
+ print("SVG extraction complete.")
116
+ return svg_data
117
+
118
+ def process(self, prompt: str, negative_prompt: str) -> str:
119
+ generated_image = self._generate_image(prompt, negative_prompt)
120
+ sketch_image = self._convert_to_sketch(generated_image)
121
+ svg_content = self._extract_svg(sketch_image)
122
+ return svg_content
123
+
124
+ app = Flask(__name__)
125
+
126
+ CORS(app, resources={r"/*": {"origins": "*"}})
127
+
128
+ pipeline = ImageToSvgPipeline(sketch_model_path=SKETCH_MODEL_WEIGHTS)
129
+
130
+ def sanitize_filename(text):
131
+ text = re.sub(r'[\\/*?:"<>|]', "", text)
132
+ return text.strip()
133
+
134
+ @app.route('/generate', methods=['POST'])
135
+ def generate_svg():
136
+ data = request.json
137
+ prompt = data.get('prompt')
138
+ if not prompt: return jsonify({"error": "Prompt is required"}), 400
139
+
140
+ negative_prompt = "低品質、最悪の品質、下手な手、指が6本、指が4本、奇形、醜い、ぼやけている、ぼやけた、ウォーターマーク、署名、テキスト"
141
+ try:
142
+ svg_result = pipeline.process(prompt, negative_prompt)
143
+
144
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
145
+ safe_prompt = sanitize_filename(prompt)[:50]
146
+ filename = f"{timestamp}_{safe_prompt}.svg"
147
+
148
+ svg_path = os.path.join(SVG_DIR, filename)
149
+ with open(svg_path, 'w', encoding='utf-8') as f:
150
+ f.write(svg_result)
151
+
152
+ thumbnail_path = os.path.join(THUMBNAIL_DIR, filename.replace('.svg', '.png'))
153
+ cairosvg.svg2png(bytestring=svg_result.encode('utf-8'), write_to=thumbnail_path, output_width=256, output_height=256)
154
+
155
+ return svg_result, 200, {'Content-Type': 'image/svg+xml'}
156
+ except Exception as e:
157
+ print(f"An error occurred during generation: {e}")
158
+ return jsonify({"error": str(e)}), 500
159
+
160
+ @app.route('/gallery', methods=['GET'])
161
+ def get_gallery():
162
+ try:
163
+ page = int(request.args.get('page', 1))
164
+ limit = int(request.args.get('limit', 8))
165
+
166
+ svg_files = sorted([f for f in os.listdir(SVG_DIR) if f.endswith('.svg')], reverse=True)
167
+
168
+ start_index = (page - 1) * limit
169
+ end_index = start_index + limit
170
+ paginated_files = svg_files[start_index:end_index]
171
+
172
+ drawings = []
173
+ for filename in paginated_files:
174
+ prompt_match = re.match(r"\d+_(.+)\.svg", filename)
175
+ prompt = prompt_match.group(1).replace('_', ' ') if prompt_match else "Prompt not found"
176
+ drawings.append({
177
+ "filename": filename,
178
+ "thumbnail": f"/thumbnails/{filename.replace('.svg', '.png')}",
179
+ "prompt": prompt
180
+ })
181
+
182
+ has_more = end_index < len(svg_files)
183
+ return jsonify({"drawings": drawings, "hasMore": has_more})
184
+ except Exception as e:
185
+ print(f"Error fetching gallery: {e}")
186
+ return jsonify({"error": "Failed to fetch gallery"}), 500
187
+
188
+ @app.route('/svgs/<path:filename>')
189
+ def get_svg(filename):
190
+ return send_from_directory(SVG_DIR, filename)
191
+
192
+ @app.route('/thumbnails/<path:filename>')
193
+ def get_thumbnail(filename):
194
+ return send_from_directory(THUMBNAIL_DIR, filename)
195
+
196
+ @app.route('/drawings/<path:filename>', methods=['DELETE'])
197
+ def delete_drawing_file(filename):
198
+ try:
199
+ svg_path = os.path.join(SVG_DIR, filename)
200
+ thumb_path = os.path.join(THUMBNAIL_DIR, filename.replace('.svg', '.png'))
201
+ if os.path.exists(svg_path): os.remove(svg_path)
202
+ if os.path.exists(thumb_path): os.remove(thumb_path)
203
+ return jsonify({"message": f"Successfully deleted {filename}"})
204
+ except Exception as e:
205
+ print(f"Error deleting file: {e}")
206
+ return jsonify({"error": "Failed to delete file"}), 500
207
+
208
+ if __name__ == '__main__':
209
+ print("Starting Flask server...")
210
+ app.run(host='0.0.0.0', port=5000)
checkpoints/netG_A_latest.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c686ced2a666b4850b4bb6ccf0748031c3eda9f822de73a34b8979970d90f0c6
3
+ size 17173511
generated_svgs/1760266666_かわいい猫のイラスト.svg ADDED
generated_svgs/1760267178_かわいい猫のイラスト.svg ADDED
generated_svgs/1760267463_かわいい猫のイラスト.svg ADDED
generated_svgs/1760267518_かわいい猫のイラスト.svg ADDED
generated_svgs/1760267577_綺麗な空.svg ADDED
generated_svgs/1760267696_綺麗な花.svg ADDED
generated_svgs/1760267891_綺麗な花.svg ADDED
generated_svgs/1760267916_綺麗な花.svg ADDED
generated_svgs/1760268197_綺麗な花火.svg ADDED
generated_svgs/1760271739_花火大会.svg ADDED
generated_svgs/1760271869_木の下に寝ている猫.svg ADDED
generated_svgs/1760274351_木の下に寝ている猫.svg ADDED
generated_svgs/1760275635_美人.svg ADDED
generated_svgs/1760276190_木の下に寝ている犬芝.svg ADDED
generated_svgs/1760276623_木の下に寝ている犬芝.svg ADDED
generated_svgs/1760276934_かわいい犬芝.svg ADDED
generated_svgs/20251012230621_かわいい犬芝.svg ADDED
generated_svgs/20251012230704_かわいい犬芝.svg ADDED
generated_svgs/20251012230829_かわいい猫.svg ADDED
generated_svgs/20251012231033_かわいい猫.svg ADDED
generated_svgs/20251012232924_図書館でテーブルの上に寝ている猫がいる.svg ADDED
generated_svgs/20251013000204_図書館でテーブルの上に寝ている猫がいる.svg ADDED
generated_svgs/20251013000305_図書館でテーブルの上に寝ている猫がいる.svg ADDED
generated_svgs/20251013000451_図書館でテーブルの上に寝ている猫がいる.svg ADDED
generated_svgs/20251013000537_図書館でテーブルの上に寝ている猫がいる.svg ADDED
generated_svgs/20251013000605_図書館でテーブルの上に寝ている猫がいる.svg ADDED
generated_svgs/20251013000634_図書館でテーブルの上に寝ている猫がいる.svg ADDED
generated_svgs/20251013002929_人が猫と抱いている.svg ADDED
requirements.txt CHANGED
@@ -1,6 +1,9 @@
1
- accelerate
2
- diffusers
3
- invisible_watermark
4
  torch
 
5
  transformers
6
- xformers
 
 
 
 
1
+ flask
2
+ flask-cors
 
3
  torch
4
+ diffusers
5
  transformers
6
+ accelerate
7
+ vtracer
8
+ pillow
9
+ cairosvg
thumbnails/1760266666_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png ADDED
thumbnails/1760267178_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png ADDED
thumbnails/1760267463_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png ADDED
thumbnails/1760267518_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253/343/201/256/343/202/244/343/203/251/343/202/271/343/203/210.png ADDED
thumbnails/1760267577_/347/266/272/351/272/227/343/201/252/347/251/272.png ADDED
thumbnails/1760267696_/347/266/272/351/272/227/343/201/252/350/212/261.png ADDED
thumbnails/1760267891_/347/266/272/351/272/227/343/201/252/350/212/261.png ADDED
thumbnails/1760267916_/347/266/272/351/272/227/343/201/252/350/212/261.png ADDED
thumbnails/1760268197_/347/266/272/351/272/227/343/201/252/350/212/261/347/201/253.png ADDED
thumbnails/1760271739_/350/212/261/347/201/253/345/244/247/344/274/232.png ADDED
thumbnails/1760271869_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/214/253.png ADDED
thumbnails/1760274351_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/214/253.png ADDED
thumbnails/1760275635_/347/276/216/344/272/272.png ADDED
thumbnails/1760276190_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/212/254/350/212/235.png ADDED
thumbnails/1760276623_/346/234/250/343/201/256/344/270/213/343/201/253/345/257/235/343/201/246/343/201/204/343/202/213/347/212/254/350/212/235.png ADDED
thumbnails/1760276934_/343/201/213/343/202/217/343/201/204/343/201/204/347/212/254/350/212/235.png ADDED
thumbnails/20251012230621_/343/201/213/343/202/217/343/201/204/343/201/204/347/212/254/350/212/235.png ADDED
thumbnails/20251012230704_/343/201/213/343/202/217/343/201/204/343/201/204/347/212/254/350/212/235.png ADDED
thumbnails/20251012230829_/343/201/213/343/202/217/343/201/204/343/201/204/347/214/253.png ADDED