Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- .gitignore +2 -1
- README.md +62 -33
- app.py +232 -231
- space.py +50 -32
- src/.gitignore +2 -1
- src/README.md +62 -33
- src/backend/gradio_imagemeta/helpers.py +1 -0
- src/backend/gradio_imagemeta/templates/component/index.js +0 -0
- src/backend/gradio_imagemeta/templates/component/style.css +1 -1
- src/demo/app.py +51 -5
- src/demo/space.py +50 -32
- src/frontend/shared/ImagePreview.svelte +1 -1
- src/frontend/shared/ImageUploader.svelte +1 -1
- src/outputs/image_with_meta.png +3 -0
- src/pyproject.toml +1 -1
.gitattributes
CHANGED
@@ -36,3 +36,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
36 |
src/.gradio/cached_examples/24/Upload[[:space:]]Image[[:space:]]All[[:space:]]metadata/7a159874ef63b94d8fa4/image_with_meta.png filter=lfs diff=lfs merge=lfs -text
|
37 |
src/.gradio/cached_examples/24/Upload[[:space:]]Image[[:space:]]Custom[[:space:]]metadata[[:space:]]only/8a0de8f0e921a79b67a8/image_with_meta.png filter=lfs diff=lfs merge=lfs -text
|
38 |
src/examples/image_with_meta.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
36 |
src/.gradio/cached_examples/24/Upload[[:space:]]Image[[:space:]]All[[:space:]]metadata/7a159874ef63b94d8fa4/image_with_meta.png filter=lfs diff=lfs merge=lfs -text
|
37 |
src/.gradio/cached_examples/24/Upload[[:space:]]Image[[:space:]]Custom[[:space:]]metadata[[:space:]]only/8a0de8f0e921a79b67a8/image_with_meta.png filter=lfs diff=lfs merge=lfs -text
|
38 |
src/examples/image_with_meta.png filter=lfs diff=lfs merge=lfs -text
|
39 |
+
src/outputs/image_with_meta.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
@@ -12,4 +12,5 @@ __tmp/*
|
|
12 |
.ruff_cache
|
13 |
node_modules
|
14 |
backend/**/templates/
|
15 |
-
outputs/
|
|
|
|
12 |
.ruff_cache
|
13 |
node_modules
|
14 |
backend/**/templates/
|
15 |
+
outputs/
|
16 |
+
README_TEMPLATE.md
|
README.md
CHANGED
@@ -10,12 +10,23 @@ app_file: space.py
|
|
10 |
---
|
11 |
|
12 |
# `gradio_imagemeta`
|
13 |
-
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%
|
14 |
|
15 |
Image Preview with Metadata for Gradio Interface
|
16 |
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
|
|
19 |
```bash
|
20 |
pip install gradio_imagemeta
|
21 |
```
|
@@ -32,6 +43,7 @@ from gradio_propertysheet import PropertySheet
|
|
32 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
33 |
from pathlib import Path
|
34 |
|
|
|
35 |
output_dir = Path("outputs")
|
36 |
output_dir.mkdir(exist_ok=True)
|
37 |
|
@@ -52,25 +64,50 @@ class PropertyConfig:
|
|
52 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
53 |
description: str = field(default="", metadata={"label": "Description"})
|
54 |
|
55 |
-
def
|
56 |
"""
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
Args:
|
60 |
-
|
61 |
-
img_all_path: File path for the image to display in img_all.
|
62 |
|
63 |
Returns:
|
64 |
-
|
65 |
"""
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
-
#
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
76 |
"""
|
@@ -90,11 +127,11 @@ def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
|
90 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
91 |
|
92 |
output_values = [gr.skip()] * len(output_fields)
|
93 |
-
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
94 |
if hasattr(component, 'root_label'):
|
95 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
96 |
else:
|
97 |
-
output_values[i] = gr.
|
98 |
|
99 |
return output_values
|
100 |
|
@@ -114,10 +151,11 @@ def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
|
|
114 |
|
115 |
params = list(inputs)
|
116 |
image_params = dict(zip(input_fields.keys(), params))
|
117 |
-
dataclass_fields = build_dataclass_fields(PropertyConfig)
|
118 |
-
metadata = {label: image_params.get(label, "") for label in
|
119 |
|
120 |
new_filepath = output_dir / "image_with_meta.png"
|
|
|
121 |
add_metadata(image_data, metadata, new_filepath)
|
122 |
|
123 |
return str(new_filepath)
|
@@ -142,17 +180,18 @@ with gr.Blocks() as demo:
|
|
142 |
label="Upload Image (Custom metadata only)",
|
143 |
type="filepath",
|
144 |
width=300,
|
145 |
-
height=400,
|
146 |
-
disable_preprocess=False,
|
147 |
interactive=True
|
148 |
)
|
149 |
img_all = ImageMeta(
|
150 |
label="Upload Image (All metadata)",
|
151 |
only_custom_metadata=False,
|
|
|
152 |
width=300,
|
153 |
-
height=400,
|
154 |
popup_metadata_height=400,
|
155 |
-
popup_metadata_width=500
|
|
|
156 |
)
|
157 |
|
158 |
gr.Markdown("## Metadata Viewer")
|
@@ -178,17 +217,7 @@ with gr.Blocks() as demo:
|
|
178 |
with gr.Row():
|
179 |
save_button = gr.Button("Add Metadata and Save Image")
|
180 |
saved_file_output = gr.File(label="Download Image")
|
181 |
-
|
182 |
-
with gr.Row():
|
183 |
-
gr.Examples(
|
184 |
-
examples=[
|
185 |
-
["./examples/image_with_meta.png", "./examples/image_with_meta.png"]
|
186 |
-
],
|
187 |
-
fn=process_example_images,
|
188 |
-
inputs=[img_custom, img_all],
|
189 |
-
outputs=[img_custom, img_all],
|
190 |
-
cache_examples=True
|
191 |
-
)
|
192 |
|
193 |
input_fields = {
|
194 |
"Model": model_box,
|
|
|
10 |
---
|
11 |
|
12 |
# `gradio_imagemeta`
|
13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_imagemeta"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_imagemeta'>Component GitHub Code</a></span></p>
|
14 |
|
15 |
Image Preview with Metadata for Gradio Interface
|
16 |
|
17 |
+
Imagine loading a photo with embedded presets (e.g., camera settings or AI model parameters) and instantly populating your app’s UI with those values. With ImageMeta, you can extract and apply these presets effortlessly, streamlining workflows for photographers, data scientists, or creative professionals.
|
18 |
+
|
19 |
+
# Features and Key Characteristics
|
20 |
+
|
21 |
+
**ImageMeta** is a custom Gradio component designed to enhance image handling with robust metadata support. Key features include:
|
22 |
+
|
23 |
+
- **Interactive Image Upload**: Upload PNG/JPEG images via drag-and-drop or file selection, with real-time metadata extraction using `exifr` on the client side.
|
24 |
+
- **Metadata Extraction & Display**: View EXIF, IPTC, and XMP metadata in a customizable popup, with options to filter custom metadata (e.g., `Model`, `Schurn`) or include technical details (e.g., `ImageWidth`).
|
25 |
+
- **Preset Loading**: Load metadata directly into UI components (Textbox, Slider, PropertySheet) to apply saved presets, streamlining workflows like camera settings or AI model parameters.
|
26 |
+
- **Metadata Editing & Saving**: Add or update metadata and save images with embedded metadata for downstream use, powered by PIL on the server side.
|
27 |
+
- **Responsive Design**: Supports fullscreen mode, adjustable popup sizes, and a polished Svelte-based UI for seamless user experiences.
|
28 |
|
29 |
+
## Installation
|
30 |
```bash
|
31 |
pip install gradio_imagemeta
|
32 |
```
|
|
|
43 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
44 |
from pathlib import Path
|
45 |
|
46 |
+
|
47 |
output_dir = Path("outputs")
|
48 |
output_dir.mkdir(exist_ok=True)
|
49 |
|
|
|
64 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
65 |
description: str = field(default="", metadata={"label": "Description"})
|
66 |
|
67 |
+
def infer_type(s: str):
|
68 |
"""
|
69 |
+
Infers and converts a string to the most likely data type.
|
70 |
+
|
71 |
+
It attempts conversions in the following order:
|
72 |
+
1. Integer
|
73 |
+
2. Float
|
74 |
+
3. Boolean (case-insensitive 'true' or 'false')
|
75 |
+
If all conversions fail, it returns the original string.
|
76 |
|
77 |
Args:
|
78 |
+
s: The input string to be converted.
|
|
|
79 |
|
80 |
Returns:
|
81 |
+
The converted value (int, float, bool) or the original string.
|
82 |
"""
|
83 |
+
if not isinstance(s, str):
|
84 |
+
# If the input is not a string, return it as is.
|
85 |
+
return s
|
86 |
+
|
87 |
+
# 1. Try to convert to an integer
|
88 |
+
try:
|
89 |
+
return int(s)
|
90 |
+
except ValueError:
|
91 |
+
# Not an integer, continue...
|
92 |
+
pass
|
93 |
+
|
94 |
+
# 2. Try to convert to a float
|
95 |
+
try:
|
96 |
+
return float(s)
|
97 |
+
except ValueError:
|
98 |
+
# Not a float, continue...
|
99 |
+
pass
|
100 |
|
101 |
+
# 3. Check for a boolean value
|
102 |
+
# This explicit check is important because bool('False') evaluates to True.
|
103 |
+
s_lower = s.lower()
|
104 |
+
if s_lower == 'true':
|
105 |
+
return True
|
106 |
+
if s_lower == 'false':
|
107 |
+
return False
|
108 |
+
|
109 |
+
# 4. If nothing else worked, return the original string
|
110 |
+
return s
|
111 |
|
112 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
113 |
"""
|
|
|
127 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
128 |
|
129 |
output_values = [gr.skip()] * len(output_fields)
|
130 |
+
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
131 |
if hasattr(component, 'root_label'):
|
132 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
133 |
else:
|
134 |
+
output_values[i] = gr.update(value=infer_type(value))
|
135 |
|
136 |
return output_values
|
137 |
|
|
|
151 |
|
152 |
params = list(inputs)
|
153 |
image_params = dict(zip(input_fields.keys(), params))
|
154 |
+
#dataclass_fields = build_dataclass_fields(PropertyConfig)
|
155 |
+
metadata = {label: image_params.get(label, "") for label in image_params.keys()}
|
156 |
|
157 |
new_filepath = output_dir / "image_with_meta.png"
|
158 |
+
|
159 |
add_metadata(image_data, metadata, new_filepath)
|
160 |
|
161 |
return str(new_filepath)
|
|
|
180 |
label="Upload Image (Custom metadata only)",
|
181 |
type="filepath",
|
182 |
width=300,
|
183 |
+
height=400,
|
|
|
184 |
interactive=True
|
185 |
)
|
186 |
img_all = ImageMeta(
|
187 |
label="Upload Image (All metadata)",
|
188 |
only_custom_metadata=False,
|
189 |
+
type="filepath",
|
190 |
width=300,
|
191 |
+
height=400,
|
192 |
popup_metadata_height=400,
|
193 |
+
popup_metadata_width=500,
|
194 |
+
interactive=True
|
195 |
)
|
196 |
|
197 |
gr.Markdown("## Metadata Viewer")
|
|
|
217 |
with gr.Row():
|
218 |
save_button = gr.Button("Add Metadata and Save Image")
|
219 |
saved_file_output = gr.File(label="Download Image")
|
220 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
|
222 |
input_fields = {
|
223 |
"Model": model_box,
|
app.py
CHANGED
@@ -1,231 +1,232 @@
|
|
1 |
-
from dataclasses import dataclass, field
|
2 |
-
from typing import List, Any
|
3 |
-
import gradio as gr
|
4 |
-
from gradio_imagemeta import ImageMeta
|
5 |
-
from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
|
6 |
-
from gradio_propertysheet import PropertySheet
|
7 |
-
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
8 |
-
from pathlib import Path
|
9 |
-
|
10 |
-
|
11 |
-
output_dir
|
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 |
-
return
|
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 |
-
return
|
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 |
-
metadata = {label: image_params.get(label, "") for label in image_params.keys()}
|
119 |
-
|
120 |
-
new_filepath = output_dir / "image_with_meta.png"
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
gr.Markdown(
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
"""
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
gr.Markdown("
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
"
|
187 |
-
"
|
188 |
-
"
|
189 |
-
"
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
|
|
|
1 |
+
from dataclasses import dataclass, field
|
2 |
+
from typing import List, Any
|
3 |
+
import gradio as gr
|
4 |
+
from gradio_imagemeta import ImageMeta
|
5 |
+
from gradio_imagemeta.helpers import extract_metadata, add_metadata, transfer_metadata
|
6 |
+
from gradio_propertysheet import PropertySheet
|
7 |
+
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
8 |
+
from pathlib import Path
|
9 |
+
|
10 |
+
|
11 |
+
output_dir = Path("outputs")
|
12 |
+
output_dir.mkdir(exist_ok=True)
|
13 |
+
|
14 |
+
@dataclass
|
15 |
+
class ImageSettings:
|
16 |
+
"""Configuration for image metadata settings."""
|
17 |
+
model: str = field(default="", metadata={"label": "Model"})
|
18 |
+
f_number: str = field(default="", metadata={"label": "FNumber"})
|
19 |
+
iso_speed_ratings: str = field(default="", metadata={"label": "ISOSpeedRatings"})
|
20 |
+
s_churn: float = field(
|
21 |
+
default=0.0,
|
22 |
+
metadata={"component": "slider", "label": "Schurn", "minimum": 0.0, "maximum": 1.0, "step": 0.01},
|
23 |
+
)
|
24 |
+
|
25 |
+
@dataclass
|
26 |
+
class PropertyConfig:
|
27 |
+
"""Root configuration for image properties, including nested image settings."""
|
28 |
+
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
29 |
+
description: str = field(default="", metadata={"label": "Description"})
|
30 |
+
|
31 |
+
def infer_type(s: str):
|
32 |
+
"""
|
33 |
+
Infers and converts a string to the most likely data type.
|
34 |
+
|
35 |
+
It attempts conversions in the following order:
|
36 |
+
1. Integer
|
37 |
+
2. Float
|
38 |
+
3. Boolean (case-insensitive 'true' or 'false')
|
39 |
+
If all conversions fail, it returns the original string.
|
40 |
+
|
41 |
+
Args:
|
42 |
+
s: The input string to be converted.
|
43 |
+
|
44 |
+
Returns:
|
45 |
+
The converted value (int, float, bool) or the original string.
|
46 |
+
"""
|
47 |
+
if not isinstance(s, str):
|
48 |
+
# If the input is not a string, return it as is.
|
49 |
+
return s
|
50 |
+
|
51 |
+
# 1. Try to convert to an integer
|
52 |
+
try:
|
53 |
+
return int(s)
|
54 |
+
except ValueError:
|
55 |
+
# Not an integer, continue...
|
56 |
+
pass
|
57 |
+
|
58 |
+
# 2. Try to convert to a float
|
59 |
+
try:
|
60 |
+
return float(s)
|
61 |
+
except ValueError:
|
62 |
+
# Not a float, continue...
|
63 |
+
pass
|
64 |
+
|
65 |
+
# 3. Check for a boolean value
|
66 |
+
# This explicit check is important because bool('False') evaluates to True.
|
67 |
+
s_lower = s.lower()
|
68 |
+
if s_lower == 'true':
|
69 |
+
return True
|
70 |
+
if s_lower == 'false':
|
71 |
+
return False
|
72 |
+
|
73 |
+
# 4. If nothing else worked, return the original string
|
74 |
+
return s
|
75 |
+
|
76 |
+
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
77 |
+
"""
|
78 |
+
Processes image metadata and maps it to output components.
|
79 |
+
|
80 |
+
Args:
|
81 |
+
image_data: ImageMeta object containing image data and metadata, or None.
|
82 |
+
|
83 |
+
Returns:
|
84 |
+
A list of values for output components (Textbox, Slider, or PropertySheet instances).
|
85 |
+
"""
|
86 |
+
if not image_data:
|
87 |
+
return [gr.Textbox(value="") for _ in output_fields]
|
88 |
+
|
89 |
+
metadata = extract_metadata(image_data, only_custom_metadata=True)
|
90 |
+
dataclass_fields = build_dataclass_fields(PropertyConfig)
|
91 |
+
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
92 |
+
|
93 |
+
output_values = [gr.skip()] * len(output_fields)
|
94 |
+
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
95 |
+
if hasattr(component, 'root_label'):
|
96 |
+
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
97 |
+
else:
|
98 |
+
output_values[i] = gr.update(value=infer_type(value))
|
99 |
+
|
100 |
+
return output_values
|
101 |
+
|
102 |
+
def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
|
103 |
+
"""
|
104 |
+
Saves an image with updated metadata to a file.
|
105 |
+
|
106 |
+
Args:
|
107 |
+
image_data: Input image data (e.g., file path or PIL Image).
|
108 |
+
*inputs: Variable number of input values from UI components (Textbox, Slider).
|
109 |
+
|
110 |
+
Returns:
|
111 |
+
The file path of the saved image, or None if no image is provided.
|
112 |
+
"""
|
113 |
+
if not image_data:
|
114 |
+
return None
|
115 |
+
|
116 |
+
params = list(inputs)
|
117 |
+
image_params = dict(zip(input_fields.keys(), params))
|
118 |
+
metadata = {label: image_params.get(label, "") for label in image_params.keys()}
|
119 |
+
|
120 |
+
new_filepath = output_dir / "image_with_meta.png"
|
121 |
+
|
122 |
+
add_metadata(image_data, metadata, new_filepath)
|
123 |
+
|
124 |
+
return str(new_filepath)
|
125 |
+
|
126 |
+
initial_property_from_meta_config = PropertyConfig()
|
127 |
+
|
128 |
+
with gr.Blocks() as demo:
|
129 |
+
gr.Markdown("# ImageMeta Component Demo")
|
130 |
+
gr.Markdown(
|
131 |
+
"""
|
132 |
+
**To Test:**
|
133 |
+
1. Upload an image with EXIF or PNG metadata using either the "Upload Imagem (Custom metadata only)" component or the "Upload Imagem (all metadata)" component.
|
134 |
+
2. Click the 'Info' icon (ⓘ) in the top-left of the image component to view the metadata panel.
|
135 |
+
3. Click 'Load Metadata' in the popup to populate the fields below with metadata values (`Model`, `FNumber`, `ISOSpeedRatings`, `Schurn`, `Description`).
|
136 |
+
4. The section below displays how metadata is rendered in components and the `PropertySheet` custom component, showing the hierarchical structure of the image settings.
|
137 |
+
5. In the "Metadata Viewer" section, you can add field values as metadata to a previously uploaded image in "Upload Image (Custom metadata only)." Then click 'Add metadata and save image' to save a new image with the metadata.
|
138 |
+
"""
|
139 |
+
)
|
140 |
+
property_sheet_state = gr.State(value=initial_property_from_meta_config)
|
141 |
+
with gr.Row():
|
142 |
+
img_custom = ImageMeta(
|
143 |
+
label="Upload Image (Custom metadata only)",
|
144 |
+
type="filepath",
|
145 |
+
width=300,
|
146 |
+
height=400,
|
147 |
+
interactive=True
|
148 |
+
)
|
149 |
+
img_all = ImageMeta(
|
150 |
+
label="Upload Image (All metadata)",
|
151 |
+
only_custom_metadata=False,
|
152 |
+
type="filepath",
|
153 |
+
width=300,
|
154 |
+
height=400,
|
155 |
+
popup_metadata_height=400,
|
156 |
+
popup_metadata_width=500,
|
157 |
+
interactive=True
|
158 |
+
)
|
159 |
+
|
160 |
+
gr.Markdown("## Metadata Viewer")
|
161 |
+
gr.Markdown("### Individual Components")
|
162 |
+
with gr.Row():
|
163 |
+
model_box = gr.Textbox(label="Model")
|
164 |
+
fnumber_box = gr.Textbox(label="FNumber")
|
165 |
+
iso_box = gr.Textbox(label="ISOSpeedRatings")
|
166 |
+
s_churn = gr.Slider(label="Schurn", value=1.0, minimum=0.0, maximum=1.0, step=0.1)
|
167 |
+
description_box = gr.Textbox(label="Description")
|
168 |
+
|
169 |
+
gr.Markdown("### PropertySheet Component")
|
170 |
+
with gr.Row():
|
171 |
+
property_sheet = PropertySheet(
|
172 |
+
value=initial_property_from_meta_config,
|
173 |
+
label="Image Settings",
|
174 |
+
width=400,
|
175 |
+
height=550,
|
176 |
+
visible=True,
|
177 |
+
root_label="General"
|
178 |
+
)
|
179 |
+
gr.Markdown("## Metadata Editor")
|
180 |
+
with gr.Row():
|
181 |
+
save_button = gr.Button("Add Metadata and Save Image")
|
182 |
+
saved_file_output = gr.File(label="Download Image")
|
183 |
+
|
184 |
+
|
185 |
+
input_fields = {
|
186 |
+
"Model": model_box,
|
187 |
+
"FNumber": fnumber_box,
|
188 |
+
"ISOSpeedRatings": iso_box,
|
189 |
+
"Schurn": s_churn,
|
190 |
+
"Description": description_box
|
191 |
+
}
|
192 |
+
|
193 |
+
output_fields = [
|
194 |
+
property_sheet,
|
195 |
+
model_box,
|
196 |
+
fnumber_box,
|
197 |
+
iso_box,
|
198 |
+
s_churn,
|
199 |
+
description_box
|
200 |
+
]
|
201 |
+
|
202 |
+
img_custom.load_metadata(handle_load_metadata, inputs=img_custom, outputs=output_fields)
|
203 |
+
img_all.load_metadata(handle_load_metadata, inputs=img_all, outputs=output_fields)
|
204 |
+
|
205 |
+
def handle_render_change(updated_config: PropertyConfig, current_state: PropertyConfig):
|
206 |
+
"""
|
207 |
+
Updates the PropertySheet state when its configuration changes.
|
208 |
+
|
209 |
+
Args:
|
210 |
+
updated_config: The new PropertyConfig instance from the PropertySheet.
|
211 |
+
current_state: The current PropertyConfig state.
|
212 |
+
|
213 |
+
Returns:
|
214 |
+
A tuple of (updated_config, updated_config) or (current_state, current_state) if updated_config is None.
|
215 |
+
"""
|
216 |
+
if updated_config is None:
|
217 |
+
return current_state, current_state
|
218 |
+
return updated_config, updated_config
|
219 |
+
|
220 |
+
property_sheet.change(
|
221 |
+
fn=handle_render_change,
|
222 |
+
inputs=[property_sheet, property_sheet_state],
|
223 |
+
outputs=[property_sheet, property_sheet_state]
|
224 |
+
)
|
225 |
+
save_button.click(
|
226 |
+
save_image_with_metadata,
|
227 |
+
inputs=[img_custom, *input_fields.values()],
|
228 |
+
outputs=[saved_file_output]
|
229 |
+
)
|
230 |
+
|
231 |
+
if __name__ == "__main__":
|
232 |
+
demo.launch()
|
space.py
CHANGED
@@ -21,7 +21,7 @@ with gr.Blocks(
|
|
21 |
# `gradio_imagemeta`
|
22 |
|
23 |
<div style="display: flex; gap: 7px;">
|
24 |
-
<img alt="
|
25 |
</div>
|
26 |
|
27 |
Image Preview with Metadata for Gradio Interface
|
@@ -47,6 +47,7 @@ from gradio_propertysheet import PropertySheet
|
|
47 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
48 |
from pathlib import Path
|
49 |
|
|
|
50 |
output_dir = Path("outputs")
|
51 |
output_dir.mkdir(exist_ok=True)
|
52 |
|
@@ -67,25 +68,50 @@ class PropertyConfig:
|
|
67 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
68 |
description: str = field(default="", metadata={"label": "Description"})
|
69 |
|
70 |
-
def
|
71 |
\"\"\"
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
Args:
|
75 |
-
|
76 |
-
img_all_path: File path for the image to display in img_all.
|
77 |
|
78 |
Returns:
|
79 |
-
|
80 |
\"\"\"
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
-
#
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
|
90 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
91 |
\"\"\"
|
@@ -105,11 +131,11 @@ def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
|
105 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
106 |
|
107 |
output_values = [gr.skip()] * len(output_fields)
|
108 |
-
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
109 |
if hasattr(component, 'root_label'):
|
110 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
111 |
else:
|
112 |
-
output_values[i] = gr.
|
113 |
|
114 |
return output_values
|
115 |
|
@@ -129,10 +155,11 @@ def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
|
|
129 |
|
130 |
params = list(inputs)
|
131 |
image_params = dict(zip(input_fields.keys(), params))
|
132 |
-
dataclass_fields = build_dataclass_fields(PropertyConfig)
|
133 |
-
metadata = {label: image_params.get(label, "") for label in
|
134 |
|
135 |
new_filepath = output_dir / "image_with_meta.png"
|
|
|
136 |
add_metadata(image_data, metadata, new_filepath)
|
137 |
|
138 |
return str(new_filepath)
|
@@ -157,17 +184,18 @@ with gr.Blocks() as demo:
|
|
157 |
label="Upload Image (Custom metadata only)",
|
158 |
type="filepath",
|
159 |
width=300,
|
160 |
-
height=400,
|
161 |
-
disable_preprocess=False,
|
162 |
interactive=True
|
163 |
)
|
164 |
img_all = ImageMeta(
|
165 |
label="Upload Image (All metadata)",
|
166 |
only_custom_metadata=False,
|
|
|
167 |
width=300,
|
168 |
-
height=400,
|
169 |
popup_metadata_height=400,
|
170 |
-
popup_metadata_width=500
|
|
|
171 |
)
|
172 |
|
173 |
gr.Markdown("## Metadata Viewer")
|
@@ -193,17 +221,7 @@ with gr.Blocks() as demo:
|
|
193 |
with gr.Row():
|
194 |
save_button = gr.Button("Add Metadata and Save Image")
|
195 |
saved_file_output = gr.File(label="Download Image")
|
196 |
-
|
197 |
-
with gr.Row():
|
198 |
-
gr.Examples(
|
199 |
-
examples=[
|
200 |
-
["./examples/image_with_meta.png", "./examples/image_with_meta.png"]
|
201 |
-
],
|
202 |
-
fn=process_example_images,
|
203 |
-
inputs=[img_custom, img_all],
|
204 |
-
outputs=[img_custom, img_all],
|
205 |
-
cache_examples=True
|
206 |
-
)
|
207 |
|
208 |
input_fields = {
|
209 |
"Model": model_box,
|
|
|
21 |
# `gradio_imagemeta`
|
22 |
|
23 |
<div style="display: flex; gap: 7px;">
|
24 |
+
<a href="https://pypi.org/project/gradio_imagemeta/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_imagemeta"></a>
|
25 |
</div>
|
26 |
|
27 |
Image Preview with Metadata for Gradio Interface
|
|
|
47 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
48 |
from pathlib import Path
|
49 |
|
50 |
+
|
51 |
output_dir = Path("outputs")
|
52 |
output_dir.mkdir(exist_ok=True)
|
53 |
|
|
|
68 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
69 |
description: str = field(default="", metadata={"label": "Description"})
|
70 |
|
71 |
+
def infer_type(s: str):
|
72 |
\"\"\"
|
73 |
+
Infers and converts a string to the most likely data type.
|
74 |
+
|
75 |
+
It attempts conversions in the following order:
|
76 |
+
1. Integer
|
77 |
+
2. Float
|
78 |
+
3. Boolean (case-insensitive 'true' or 'false')
|
79 |
+
If all conversions fail, it returns the original string.
|
80 |
|
81 |
Args:
|
82 |
+
s: The input string to be converted.
|
|
|
83 |
|
84 |
Returns:
|
85 |
+
The converted value (int, float, bool) or the original string.
|
86 |
\"\"\"
|
87 |
+
if not isinstance(s, str):
|
88 |
+
# If the input is not a string, return it as is.
|
89 |
+
return s
|
90 |
+
|
91 |
+
# 1. Try to convert to an integer
|
92 |
+
try:
|
93 |
+
return int(s)
|
94 |
+
except ValueError:
|
95 |
+
# Not an integer, continue...
|
96 |
+
pass
|
97 |
+
|
98 |
+
# 2. Try to convert to a float
|
99 |
+
try:
|
100 |
+
return float(s)
|
101 |
+
except ValueError:
|
102 |
+
# Not a float, continue...
|
103 |
+
pass
|
104 |
|
105 |
+
# 3. Check for a boolean value
|
106 |
+
# This explicit check is important because bool('False') evaluates to True.
|
107 |
+
s_lower = s.lower()
|
108 |
+
if s_lower == 'true':
|
109 |
+
return True
|
110 |
+
if s_lower == 'false':
|
111 |
+
return False
|
112 |
+
|
113 |
+
# 4. If nothing else worked, return the original string
|
114 |
+
return s
|
115 |
|
116 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
117 |
\"\"\"
|
|
|
131 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
132 |
|
133 |
output_values = [gr.skip()] * len(output_fields)
|
134 |
+
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
135 |
if hasattr(component, 'root_label'):
|
136 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
137 |
else:
|
138 |
+
output_values[i] = gr.update(value=infer_type(value))
|
139 |
|
140 |
return output_values
|
141 |
|
|
|
155 |
|
156 |
params = list(inputs)
|
157 |
image_params = dict(zip(input_fields.keys(), params))
|
158 |
+
#dataclass_fields = build_dataclass_fields(PropertyConfig)
|
159 |
+
metadata = {label: image_params.get(label, "") for label in image_params.keys()}
|
160 |
|
161 |
new_filepath = output_dir / "image_with_meta.png"
|
162 |
+
|
163 |
add_metadata(image_data, metadata, new_filepath)
|
164 |
|
165 |
return str(new_filepath)
|
|
|
184 |
label="Upload Image (Custom metadata only)",
|
185 |
type="filepath",
|
186 |
width=300,
|
187 |
+
height=400,
|
|
|
188 |
interactive=True
|
189 |
)
|
190 |
img_all = ImageMeta(
|
191 |
label="Upload Image (All metadata)",
|
192 |
only_custom_metadata=False,
|
193 |
+
type="filepath",
|
194 |
width=300,
|
195 |
+
height=400,
|
196 |
popup_metadata_height=400,
|
197 |
+
popup_metadata_width=500,
|
198 |
+
interactive=True
|
199 |
)
|
200 |
|
201 |
gr.Markdown("## Metadata Viewer")
|
|
|
221 |
with gr.Row():
|
222 |
save_button = gr.Button("Add Metadata and Save Image")
|
223 |
saved_file_output = gr.File(label="Download Image")
|
224 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
|
226 |
input_fields = {
|
227 |
"Model": model_box,
|
src/.gitignore
CHANGED
@@ -12,4 +12,5 @@ __tmp/*
|
|
12 |
.ruff_cache
|
13 |
node_modules
|
14 |
backend/**/templates/
|
15 |
-
outputs/
|
|
|
|
12 |
.ruff_cache
|
13 |
node_modules
|
14 |
backend/**/templates/
|
15 |
+
outputs/
|
16 |
+
README_TEMPLATE.md
|
src/README.md
CHANGED
@@ -10,12 +10,23 @@ app_file: space.py
|
|
10 |
---
|
11 |
|
12 |
# `gradio_imagemeta`
|
13 |
-
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%
|
14 |
|
15 |
Image Preview with Metadata for Gradio Interface
|
16 |
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
|
|
19 |
```bash
|
20 |
pip install gradio_imagemeta
|
21 |
```
|
@@ -32,6 +43,7 @@ from gradio_propertysheet import PropertySheet
|
|
32 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
33 |
from pathlib import Path
|
34 |
|
|
|
35 |
output_dir = Path("outputs")
|
36 |
output_dir.mkdir(exist_ok=True)
|
37 |
|
@@ -52,25 +64,50 @@ class PropertyConfig:
|
|
52 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
53 |
description: str = field(default="", metadata={"label": "Description"})
|
54 |
|
55 |
-
def
|
56 |
"""
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
Args:
|
60 |
-
|
61 |
-
img_all_path: File path for the image to display in img_all.
|
62 |
|
63 |
Returns:
|
64 |
-
|
65 |
"""
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
-
#
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
76 |
"""
|
@@ -90,11 +127,11 @@ def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
|
90 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
91 |
|
92 |
output_values = [gr.skip()] * len(output_fields)
|
93 |
-
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
94 |
if hasattr(component, 'root_label'):
|
95 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
96 |
else:
|
97 |
-
output_values[i] = gr.
|
98 |
|
99 |
return output_values
|
100 |
|
@@ -114,10 +151,11 @@ def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
|
|
114 |
|
115 |
params = list(inputs)
|
116 |
image_params = dict(zip(input_fields.keys(), params))
|
117 |
-
dataclass_fields = build_dataclass_fields(PropertyConfig)
|
118 |
-
metadata = {label: image_params.get(label, "") for label in
|
119 |
|
120 |
new_filepath = output_dir / "image_with_meta.png"
|
|
|
121 |
add_metadata(image_data, metadata, new_filepath)
|
122 |
|
123 |
return str(new_filepath)
|
@@ -142,17 +180,18 @@ with gr.Blocks() as demo:
|
|
142 |
label="Upload Image (Custom metadata only)",
|
143 |
type="filepath",
|
144 |
width=300,
|
145 |
-
height=400,
|
146 |
-
disable_preprocess=False,
|
147 |
interactive=True
|
148 |
)
|
149 |
img_all = ImageMeta(
|
150 |
label="Upload Image (All metadata)",
|
151 |
only_custom_metadata=False,
|
|
|
152 |
width=300,
|
153 |
-
height=400,
|
154 |
popup_metadata_height=400,
|
155 |
-
popup_metadata_width=500
|
|
|
156 |
)
|
157 |
|
158 |
gr.Markdown("## Metadata Viewer")
|
@@ -178,17 +217,7 @@ with gr.Blocks() as demo:
|
|
178 |
with gr.Row():
|
179 |
save_button = gr.Button("Add Metadata and Save Image")
|
180 |
saved_file_output = gr.File(label="Download Image")
|
181 |
-
|
182 |
-
with gr.Row():
|
183 |
-
gr.Examples(
|
184 |
-
examples=[
|
185 |
-
["./examples/image_with_meta.png", "./examples/image_with_meta.png"]
|
186 |
-
],
|
187 |
-
fn=process_example_images,
|
188 |
-
inputs=[img_custom, img_all],
|
189 |
-
outputs=[img_custom, img_all],
|
190 |
-
cache_examples=True
|
191 |
-
)
|
192 |
|
193 |
input_fields = {
|
194 |
"Model": model_box,
|
|
|
10 |
---
|
11 |
|
12 |
# `gradio_imagemeta`
|
13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_imagemeta"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_imagemeta'>Component GitHub Code</a></span></p>
|
14 |
|
15 |
Image Preview with Metadata for Gradio Interface
|
16 |
|
17 |
+
Imagine loading a photo with embedded presets (e.g., camera settings or AI model parameters) and instantly populating your app’s UI with those values. With ImageMeta, you can extract and apply these presets effortlessly, streamlining workflows for photographers, data scientists, or creative professionals.
|
18 |
+
|
19 |
+
# Features and Key Characteristics
|
20 |
+
|
21 |
+
**ImageMeta** is a custom Gradio component designed to enhance image handling with robust metadata support. Key features include:
|
22 |
+
|
23 |
+
- **Interactive Image Upload**: Upload PNG/JPEG images via drag-and-drop or file selection, with real-time metadata extraction using `exifr` on the client side.
|
24 |
+
- **Metadata Extraction & Display**: View EXIF, IPTC, and XMP metadata in a customizable popup, with options to filter custom metadata (e.g., `Model`, `Schurn`) or include technical details (e.g., `ImageWidth`).
|
25 |
+
- **Preset Loading**: Load metadata directly into UI components (Textbox, Slider, PropertySheet) to apply saved presets, streamlining workflows like camera settings or AI model parameters.
|
26 |
+
- **Metadata Editing & Saving**: Add or update metadata and save images with embedded metadata for downstream use, powered by PIL on the server side.
|
27 |
+
- **Responsive Design**: Supports fullscreen mode, adjustable popup sizes, and a polished Svelte-based UI for seamless user experiences.
|
28 |
|
29 |
+
## Installation
|
30 |
```bash
|
31 |
pip install gradio_imagemeta
|
32 |
```
|
|
|
43 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
44 |
from pathlib import Path
|
45 |
|
46 |
+
|
47 |
output_dir = Path("outputs")
|
48 |
output_dir.mkdir(exist_ok=True)
|
49 |
|
|
|
64 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
65 |
description: str = field(default="", metadata={"label": "Description"})
|
66 |
|
67 |
+
def infer_type(s: str):
|
68 |
"""
|
69 |
+
Infers and converts a string to the most likely data type.
|
70 |
+
|
71 |
+
It attempts conversions in the following order:
|
72 |
+
1. Integer
|
73 |
+
2. Float
|
74 |
+
3. Boolean (case-insensitive 'true' or 'false')
|
75 |
+
If all conversions fail, it returns the original string.
|
76 |
|
77 |
Args:
|
78 |
+
s: The input string to be converted.
|
|
|
79 |
|
80 |
Returns:
|
81 |
+
The converted value (int, float, bool) or the original string.
|
82 |
"""
|
83 |
+
if not isinstance(s, str):
|
84 |
+
# If the input is not a string, return it as is.
|
85 |
+
return s
|
86 |
+
|
87 |
+
# 1. Try to convert to an integer
|
88 |
+
try:
|
89 |
+
return int(s)
|
90 |
+
except ValueError:
|
91 |
+
# Not an integer, continue...
|
92 |
+
pass
|
93 |
+
|
94 |
+
# 2. Try to convert to a float
|
95 |
+
try:
|
96 |
+
return float(s)
|
97 |
+
except ValueError:
|
98 |
+
# Not a float, continue...
|
99 |
+
pass
|
100 |
|
101 |
+
# 3. Check for a boolean value
|
102 |
+
# This explicit check is important because bool('False') evaluates to True.
|
103 |
+
s_lower = s.lower()
|
104 |
+
if s_lower == 'true':
|
105 |
+
return True
|
106 |
+
if s_lower == 'false':
|
107 |
+
return False
|
108 |
+
|
109 |
+
# 4. If nothing else worked, return the original string
|
110 |
+
return s
|
111 |
|
112 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
113 |
"""
|
|
|
127 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
128 |
|
129 |
output_values = [gr.skip()] * len(output_fields)
|
130 |
+
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
131 |
if hasattr(component, 'root_label'):
|
132 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
133 |
else:
|
134 |
+
output_values[i] = gr.update(value=infer_type(value))
|
135 |
|
136 |
return output_values
|
137 |
|
|
|
151 |
|
152 |
params = list(inputs)
|
153 |
image_params = dict(zip(input_fields.keys(), params))
|
154 |
+
#dataclass_fields = build_dataclass_fields(PropertyConfig)
|
155 |
+
metadata = {label: image_params.get(label, "") for label in image_params.keys()}
|
156 |
|
157 |
new_filepath = output_dir / "image_with_meta.png"
|
158 |
+
|
159 |
add_metadata(image_data, metadata, new_filepath)
|
160 |
|
161 |
return str(new_filepath)
|
|
|
180 |
label="Upload Image (Custom metadata only)",
|
181 |
type="filepath",
|
182 |
width=300,
|
183 |
+
height=400,
|
|
|
184 |
interactive=True
|
185 |
)
|
186 |
img_all = ImageMeta(
|
187 |
label="Upload Image (All metadata)",
|
188 |
only_custom_metadata=False,
|
189 |
+
type="filepath",
|
190 |
width=300,
|
191 |
+
height=400,
|
192 |
popup_metadata_height=400,
|
193 |
+
popup_metadata_width=500,
|
194 |
+
interactive=True
|
195 |
)
|
196 |
|
197 |
gr.Markdown("## Metadata Viewer")
|
|
|
217 |
with gr.Row():
|
218 |
save_button = gr.Button("Add Metadata and Save Image")
|
219 |
saved_file_output = gr.File(label="Download Image")
|
220 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
|
222 |
input_fields = {
|
223 |
"Model": model_box,
|
src/backend/gradio_imagemeta/helpers.py
CHANGED
@@ -107,6 +107,7 @@ def add_metadata(image_data: str | Path | Image.Image | np.ndarray, metadata: Di
|
|
107 |
if not bool(save_path):
|
108 |
return False
|
109 |
|
|
|
110 |
# Convert image_data to PIL.Image
|
111 |
if isinstance(image_data, (str, Path)):
|
112 |
image = Image.open(image_data)
|
|
|
107 |
if not bool(save_path):
|
108 |
return False
|
109 |
|
110 |
+
|
111 |
# Convert image_data to PIL.Image
|
112 |
if isinstance(image_data, (str, Path)):
|
113 |
image = Image.open(image_data)
|
src/backend/gradio_imagemeta/templates/component/index.js
CHANGED
The diff for this file is too large to render.
See raw diff
|
|
src/backend/gradio_imagemeta/templates/component/style.css
CHANGED
@@ -1 +1 @@
|
|
1 |
-
.block.svelte-239wnu{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.fullscreen.svelte-239wnu{border-radius:0}.auto-margin.svelte-239wnu{margin-left:auto;margin-right:auto}.block.border_focus.svelte-239wnu{border-color:var(--color-accent)}.block.border_contrast.svelte-239wnu{border-color:var(--body-text-color)}.padded.svelte-239wnu{padding:var(--block-padding)}.hidden.svelte-239wnu{display:none}.flex.svelte-239wnu{display:flex;flex-direction:column}.hide-container.svelte-239wnu:not(.fullscreen){margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}.resize-handle.svelte-239wnu{position:absolute;bottom:0;right:0;width:10px;height:10px;fill:var(--block-border-color);cursor:nwse-resize}.fullscreen.svelte-239wnu{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;overflow:auto}.animating.svelte-239wnu{animation:svelte-239wnu-pop-out .1s ease-out forwards}@keyframes svelte-239wnu-pop-out{0%{position:fixed;top:var(--start-top);left:var(--start-left);width:var(--start-width);height:var(--start-height);z-index:100}to{position:fixed;top:0vh;left:0vw;width:100vw;height:100vh;z-index:1000}}.placeholder.svelte-239wnu{border-radius:var(--block-radius);border-width:var(--block-border-width);border-color:var(--block-border-color);border-style:dashed}Tables */ table,tr,td,th{margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);padding:var(--spacing-xl)}.md code,.md pre{background:none;font-family:var(--font-mono);font-size:var(--text-sm);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:2;tab-size:2;-webkit-hyphens:none;hyphens:none}.md pre[class*=language-]::selection,.md pre[class*=language-] ::selection,.md code[class*=language-]::selection,.md code[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}.md pre{padding:1em;margin:.5em 0;overflow:auto;position:relative;margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);box-shadow:none;border:none;border-radius:var(--radius-md);background:var(--code-background-fill);padding:var(--spacing-xxl);font-family:var(--font-mono);text-shadow:none;border-radius:var(--radius-sm);white-space:nowrap;display:block;white-space:pre}.md :not(pre)>code{padding:.1em;border-radius:var(--radius-xs);white-space:normal;background:var(--code-background-fill);border:1px solid var(--panel-border-color);padding:var(--spacing-xxs) var(--spacing-xs)}.md .token.comment,.md .token.prolog,.md .token.doctype,.md .token.cdata{color:#708090}.md .token.punctuation{color:#999}.md .token.namespace{opacity:.7}.md .token.property,.md .token.tag,.md .token.boolean,.md .token.number,.md .token.constant,.md .token.symbol,.md .token.deleted{color:#905}.md .token.selector,.md .token.attr-name,.md .token.string,.md .token.char,.md .token.builtin,.md .token.inserted{color:#690}.md .token.atrule,.md .token.attr-value,.md .token.keyword{color:#07a}.md .token.function,.md .token.class-name{color:#dd4a68}.md .token.regex,.md .token.important,.md .token.variable{color:#e90}.md .token.important,.md .token.bold{font-weight:700}.md .token.italic{font-style:italic}.md .token.entity{cursor:help}.dark .md .token.comment,.dark .md .token.prolog,.dark .md .token.cdata{color:#5c6370}.dark .md .token.doctype,.dark .md .token.punctuation,.dark .md .token.entity{color:#abb2bf}.dark .md .token.attr-name,.dark .md .token.class-name,.dark .md .token.boolean,.dark .md .token.constant,.dark .md .token.number,.dark .md .token.atrule{color:#d19a66}.dark .md .token.keyword{color:#c678dd}.dark .md .token.property,.dark .md .token.tag,.dark .md .token.symbol,.dark .md .token.deleted,.dark .md .token.important{color:#e06c75}.dark .md .token.selector,.dark .md .token.string,.dark .md .token.char,.dark .md .token.builtin,.dark .md .token.inserted,.dark .md .token.regex,.dark .md .token.attr-value,.dark .md .token.attr-value>.token.punctuation{color:#98c379}.dark .md .token.variable,.dark .md .token.operator,.dark .md .token.function{color:#61afef}.dark .md .token.url{color:#56b6c2}span.svelte-1m32c2s div[class*=code_wrap]{position:relative}span.svelte-1m32c2s span.katex{font-size:var(--text-lg);direction:ltr}span.svelte-1m32c2s div[class*=code_wrap]>button{z-index:1;cursor:pointer;border-bottom-left-radius:var(--radius-sm);padding:var(--spacing-md);width:25px;height:25px;position:absolute;right:0}span.svelte-1m32c2s .check{opacity:0;z-index:var(--layer-top);transition:opacity .2s;background:var(--code-background-fill);color:var(--body-text-color);position:absolute;top:var(--size-1-5);left:var(--size-1-5)}span.svelte-1m32c2s p:not(:first-child){margin-top:var(--spacing-xxl)}span.svelte-1m32c2s .md-header-anchor{margin-left:-25px;padding-right:8px;line-height:1;color:var(--body-text-color-subdued);opacity:0}span.svelte-1m32c2s h1:hover .md-header-anchor,span.svelte-1m32c2s h2:hover .md-header-anchor,span.svelte-1m32c2s h3:hover .md-header-anchor,span.svelte-1m32c2s h4:hover .md-header-anchor,span.svelte-1m32c2s h5:hover .md-header-anchor,span.svelte-1m32c2s h6:hover .md-header-anchor{opacity:1}span.md.svelte-1m32c2s .md-header-anchor>svg{color:var(--body-text-color-subdued)}span.svelte-1m32c2s table{word-break:break-word}div.svelte-17qq50w>.md.prose{font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}div.svelte-17qq50w>.md.prose *{color:var(--block-info-text-color)}div.svelte-17qq50w{margin-bottom:var(--spacing-md)}span.has-info.svelte-zgrq3{margin-bottom:var(--spacing-xs)}span.svelte-zgrq3:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-zgrq3{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}span[dir=rtl].svelte-zgrq3{display:block}.hide.svelte-zgrq3{margin:0;height:0}label.svelte-13ao5pu.svelte-13ao5pu{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-13ao5pu.svelte-13ao5pu{border-top-left-radius:0}label.float.svelte-13ao5pu.svelte-13ao5pu{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-13ao5pu.svelte-13ao5pu:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-13ao5pu.svelte-13ao5pu{height:0}span.svelte-13ao5pu.svelte-13ao5pu{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-13ao5pu.svelte-13ao5pu{box-shadow:none;border-width:0;background:transparent;overflow:visible}label[dir=rtl].svelte-13ao5pu.svelte-13ao5pu{border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-right:none;border-bottom-left-radius:var(--block-radius);border-bottom-right-radius:var(--block-label-radius);border-top-left-radius:var(--block-label-radius)}label[dir=rtl].svelte-13ao5pu span.svelte-13ao5pu{margin-left:var(--size-2);margin-right:0}button.svelte-qgco6m{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-xs);color:var(--block-label-text-color);border:1px solid transparent;padding:var(--spacing-xxs)}button.svelte-qgco6m:hover{background-color:var(--background-fill-secondary)}button[disabled].svelte-qgco6m{opacity:.5;box-shadow:none}button[disabled].svelte-qgco6m:hover{cursor:not-allowed}.padded.svelte-qgco6m{background:var(--bg-color)}button.svelte-qgco6m:hover,button.highlight.svelte-qgco6m{cursor:pointer;color:var(--color-accent)}.padded.svelte-qgco6m:hover{color:var(--block-label-text-color)}span.svelte-qgco6m{padding:0 1px;font-size:10px}div.svelte-qgco6m{display:flex;align-items:center;justify-content:center;transition:filter .2s ease-in-out}.x-small.svelte-qgco6m{width:10px;height:10px}.small.svelte-qgco6m{width:14px;height:14px}.medium.svelte-qgco6m{width:20px;height:20px}.large.svelte-qgco6m{width:22px;height:22px}.pending.svelte-qgco6m{animation:svelte-qgco6m-flash .5s infinite}@keyframes svelte-qgco6m-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-qgco6m{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6,.dropdown-arrow.svelte-ihhdbf{fill:currentColor}.circle.svelte-ihhdbf{fill:currentColor;opacity:.1}svg.svelte-pb9pol{animation:svelte-pb9pol-spin 1.5s linear infinite}@keyframes svelte-pb9pol-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}h2.svelte-1xg7h5n{font-size:var(--text-xl)!important}p.svelte-1xg7h5n,h2.svelte-1xg7h5n{white-space:pre-line}.wrap.svelte-1xg7h5n{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3);text-align:center;margin:auto var(--spacing-lg)}.or.svelte-1xg7h5n{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-1xg7h5n{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-1xg7h5n{font-size:var(--text-lg)}}.hovered.svelte-1xg7h5n{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-15ls1gu{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:100%;margin-left:auto;margin-right:auto;height:var(--size-10)}.icon.svelte-15ls1gu{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-15ls1gu{color:var(--color-accent)}.icon.svelte-15ls1gu:hover,.icon.svelte-15ls1gu:focus{color:var(--color-accent)}.icon-button-wrapper.svelte-109se4{display:flex;flex-direction:row;align-items:center;justify-content:center;z-index:var(--layer-3);gap:var(--spacing-sm);box-shadow:var(--shadow-drop);border:1px solid var(--border-color-primary);background:var(--block-background-fill);padding:var(--spacing-xxs)}.icon-button-wrapper.hide-top-corner.svelte-109se4{border-top:none;border-right:none;border-radius:var(--block-label-right-radius)}.icon-button-wrapper.display-top-corner.svelte-109se4{border-radius:var(--radius-sm) 0 0 var(--radius-sm);top:var(--spacing-sm);right:-1px}.icon-button-wrapper.svelte-109se4:not(.top-panel){border:1px solid var(--border-color-primary);border-radius:var(--radius-sm)}.top-panel.svelte-109se4{position:absolute;top:var(--block-label-margin);right:var(--block-label-margin);margin:0}.icon-button-wrapper.svelte-109se4 button{margin:var(--spacing-xxs);border-radius:var(--radius-xs);position:relative}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child),.icon-button-wrapper.svelte-109se4 button:not(:last-child){margin-right:var(--spacing-xxs)}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child):not(.no-border *):after,.icon-button-wrapper.svelte-109se4 button:not(:last-child):not(.no-border *):after{content:"";position:absolute;right:-4.5px;top:15%;height:70%;width:1px;background-color:var(--border-color-primary)}.icon-button-wrapper.svelte-109se4>*{height:100%}.unstyled-link.svelte-151nsdd{all:unset;cursor:pointer}img.svelte-kxeri3{object-fit:cover}.image-container.svelte-157jyrf.svelte-157jyrf{height:100%;position:relative;min-width:var(--size-20)}.image-container.svelte-157jyrf button.svelte-157jyrf{width:var(--size-full);height:var(--size-full);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.image-frame.svelte-157jyrf img{width:var(--size-full);height:var(--size-full);object-fit:scale-down}.selectable.svelte-157jyrf.svelte-157jyrf{cursor:crosshair}.fullscreen-controls svg{position:relative;top:0}.image-container:fullscreen{background-color:#000;display:flex;justify-content:center;align-items:center}.image-container:fullscreen img{max-width:90vw;max-height:90vh;object-fit:scale-down}.image-frame.svelte-157jyrf.svelte-157jyrf{width:auto;height:100%;display:flex;align-items:center;justify-content:center}.metadata-popup.svelte-157jyrf.svelte-157jyrf{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--background-fill-primary, white);border:1px solid var(--border-color-primary);box-shadow:0 4px 8px #0003;z-index:1000;border-radius:8px;overflow:hidden}.popup-content.svelte-157jyrf.svelte-157jyrf{position:relative;padding:1rem;display:flex;flex-direction:column;height:100%}.popup-title.svelte-157jyrf.svelte-157jyrf{font-weight:700;margin:0 0 1rem}.close-button.svelte-157jyrf.svelte-157jyrf{position:absolute;top:.5rem;right:.5rem;background:none;border:none;font-size:1rem;cursor:pointer}.metadata-table-container.svelte-157jyrf.svelte-157jyrf{flex:1;overflow:auto}.metadata-table.svelte-157jyrf.svelte-157jyrf{width:100%;border-collapse:collapse}.metadata-label.svelte-157jyrf.svelte-157jyrf{background:var(--background-fill-secondary, #f5f5f5);padding:.5rem;font-weight:700;text-align:left;vertical-align:top;width:40%}.metadata-value.svelte-157jyrf.svelte-157jyrf{padding:.5rem;white-space:nowrap;vertical-align:top}.load-metadata-button.svelte-157jyrf.svelte-157jyrf{margin-top:1rem;padding:.5rem 1rem;background-color:var(--button-primary-background-fill);color:var(--button-primary-text-color);border:none;border-radius:4px;cursor:pointer;align-self:center}.load-metadata-button.svelte-157jyrf.svelte-157jyrf:hover{background-color:var(--button-primary-background-fill-hover)}.wrap.svelte-cr2edf.svelte-cr2edf{overflow-y:auto;transition:opacity .5s ease-in-out;background:var(--block-background-fill);position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:var(--size-40);width:var(--size-full)}.wrap.svelte-cr2edf.svelte-cr2edf:after{content:"";position:absolute;top:0;left:0;width:var(--upload-progress-width);height:100%;transition:all .5s ease-in-out;z-index:1}.uploading.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-lg);font-family:var(--font);z-index:2}.file-name.svelte-cr2edf.svelte-cr2edf{margin:var(--spacing-md);font-size:var(--text-lg);color:var(--body-text-color-subdued)}.file.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-md);z-index:2;display:flex;align-items:center}.file.svelte-cr2edf progress.svelte-cr2edf{display:inline;height:var(--size-1);width:100%;transition:all .5s ease-in-out;color:var(--color-accent);border:none}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-value{background-color:var(--color-accent);border-radius:20px}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-bar{background-color:var(--border-color-accent);border-radius:20px}.progress-bar.svelte-cr2edf.svelte-cr2edf{width:14px;height:14px;border-radius:50%;background:radial-gradient(closest-side,var(--block-background-fill) 64%,transparent 53% 100%),conic-gradient(var(--color-accent) var(--upload-progress-width),var(--border-color-accent) 0);transition:all .5s ease-in-out}button.svelte-1o7nwih{cursor:pointer;width:var(--size-full)}.center.svelte-1o7nwih{display:flex;justify-content:center}.flex.svelte-1o7nwih{display:flex;flex-direction:column;justify-content:center;align-items:center}.hidden.svelte-1o7nwih{display:none;position:absolute;flex-grow:0}.hidden.svelte-1o7nwih svg{display:none}.disable_click.svelte-1o7nwih{cursor:default}.icon-mode.svelte-1o7nwih{position:absolute!important;width:var(--size-4);height:var(--size-4);padding:0;min-height:0;border-radius:var(--radius-circle)}.icon-mode.svelte-1o7nwih svg{width:var(--size-4);height:var(--size-4)}.image-frame.svelte-16v1i9j img{width:var(--size-full);height:var(--size-full);object-fit:scale-down}.upload-container.svelte-16v1i9j{display:flex;align-items:center;justify-content:center;height:100%;flex-shrink:1;max-height:100%}.image-container.svelte-16v1i9j{display:flex;height:100%;flex-direction:column;justify-content:center;align-items:center;max-height:100%;position:relative}.selectable.svelte-16v1i9j{cursor:crosshair}.image-frame.svelte-16v1i9j{object-fit:cover;width:100%;height:100%}.metadata-popup.svelte-16v1i9j{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--background-fill-primary, white);border:1px solid var(--border-color-primary);box-shadow:0 4px 8px #0003;z-index:1000;border-radius:8px;overflow:hidden}.popup-content.svelte-16v1i9j{position:relative;padding:1rem;display:flex;flex-direction:column;height:100%}.popup-title.svelte-16v1i9j{font-weight:700;margin:0 0 1rem}.close-button.svelte-16v1i9j{position:absolute;top:.5rem;right:.5rem;background:none;border:none;font-size:1rem;cursor:pointer}.metadata-table-container.svelte-16v1i9j{flex:1;overflow:auto}.metadata-table.svelte-16v1i9j{width:100%;border-collapse:collapse}.metadata-label.svelte-16v1i9j{background:var(--background-fill-secondary, #f5f5f5);padding:.5rem;font-weight:700;text-align:left;vertical-align:top;width:40%}.metadata-value.svelte-16v1i9j{padding:.5rem;white-space:nowrap;vertical-align:top}.load-metadata-button.svelte-16v1i9j{margin-top:1rem;padding:.5rem 1rem;background-color:var(--button-primary-background-fill);color:var(--button-primary-text-color);border:none;border-radius:4px;cursor:pointer;align-self:center}.load-metadata-button.svelte-16v1i9j:hover{background-color:var(--button-primary-background-fill-hover)}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-17v219f.svelte-17v219f{top:0;right:0;left:0}.wrap.default.svelte-17v219f.svelte-17v219f{top:0;right:0;bottom:0;left:0}.hide.svelte-17v219f.svelte-17v219f{opacity:0;pointer-events:none}.generating.svelte-17v219f.svelte-17v219f{animation:svelte-17v219f-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-17v219f-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-17v219f.svelte-17v219f{background:none}@keyframes svelte-17v219f-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-17v219f-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-17v219f.svelte-17v219f{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-17v219f.svelte-17v219f{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-17v219f.svelte-17v219f{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-17v219f.svelte-17v219f{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-17v219f.svelte-17v219f{position:absolute;bottom:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-17v219f.svelte-17v219f{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-17v219f.svelte-17v219f{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-17v219f.svelte-17v219f{pointer-events:none}.minimal.svelte-17v219f .progress-text.svelte-17v219f{background:var(--block-background-fill)}.border.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary)}.clear-status.svelte-17v219f.svelte-17v219f{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.toast-body.svelte-syezpc{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-syezpc{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-syezpc{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-syezpc{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-syezpc{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-syezpc{border:1px solid var(--color-grey-700);background:var(--color-grey-50)}.dark .toast-body.info.svelte-syezpc{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-body.success.svelte-syezpc{border:1px solid var(--color-green-700);background:var(--color-green-50)}.dark .toast-body.success.svelte-syezpc{border:1px solid var(--color-green-500);background-color:var(--color-grey-950)}.toast-title.svelte-syezpc{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm)}.toast-title.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-title.error.svelte-syezpc{color:var(--color-red-50)}.toast-title.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-syezpc{color:var(--color-yellow-50)}.toast-title.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-title.info.svelte-syezpc{color:var(--color-grey-50)}.toast-title.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-title.success.svelte-syezpc{color:var(--color-green-50)}.toast-close.svelte-syezpc{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-close.error.svelte-syezpc{color:var(--color-red-500)}.toast-close.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-syezpc{color:var(--color-yellow-500)}.toast-close.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-close.info.svelte-syezpc{color:var(--color-grey-500)}.toast-close.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-close.success.svelte-syezpc{color:var(--color-green-500)}.toast-text.svelte-syezpc{font-size:var(--text-lg);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word}.toast-text.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-text.error.svelte-syezpc{color:var(--color-red-50)}.toast-text.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-syezpc{color:var(--color-yellow-50)}.toast-text.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-text.info.svelte-syezpc{color:var(--color-grey-50)}.toast-text.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-text.success.svelte-syezpc{color:var(--color-green-50)}.toast-details.svelte-syezpc{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-syezpc{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-icon.error.svelte-syezpc{color:var(--color-red-500)}.toast-icon.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-syezpc{color:var(--color-yellow-500)}.toast-icon.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-syezpc{color:var(--color-grey-500)}.toast-icon.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-icon.success.svelte-syezpc{color:var(--color-green-500)}@keyframes svelte-syezpc-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-syezpc{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-syezpc-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-syezpc{background:var(--color-red-700)}.dark .timer.error.svelte-syezpc{background:var(--color-red-500)}.timer.warning.svelte-syezpc{background:var(--color-yellow-700)}.dark .timer.warning.svelte-syezpc{background:var(--color-yellow-500)}.timer.info.svelte-syezpc{background:var(--color-grey-700)}.dark .timer.info.svelte-syezpc{background:var(--color-grey-500)}.timer.success.svelte-syezpc{background:var(--color-green-700)}.dark .timer.success.svelte-syezpc{background:var(--color-green-500)}.hidden.svelte-syezpc{display:none}.toast-text.svelte-syezpc a{text-decoration:underline}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}.streaming-bar.svelte-ga0jj6{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--primary-600);animation:svelte-ga0jj6-countdown linear forwards;z-index:1}@keyframes svelte-ga0jj6-countdown{0%{transform:translate(0)}to{transform:translate(-100%)}}.container.svelte-1sgcyba img{width:100%;height:100%}.container.selected.svelte-1sgcyba{border-color:var(--border-color-accent)}.border.table.svelte-1sgcyba{border:2px solid var(--border-color-primary)}.container.table.svelte-1sgcyba{margin:0 auto;border-radius:var(--radius-lg);overflow:hidden;width:var(--size-20);height:var(--size-20);object-fit:cover}.container.gallery.svelte-1sgcyba{width:var(--size-20);max-width:var(--size-20);object-fit:cover}
|
|
|
1 |
+
.block.svelte-239wnu{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.fullscreen.svelte-239wnu{border-radius:0}.auto-margin.svelte-239wnu{margin-left:auto;margin-right:auto}.block.border_focus.svelte-239wnu{border-color:var(--color-accent)}.block.border_contrast.svelte-239wnu{border-color:var(--body-text-color)}.padded.svelte-239wnu{padding:var(--block-padding)}.hidden.svelte-239wnu{display:none}.flex.svelte-239wnu{display:flex;flex-direction:column}.hide-container.svelte-239wnu:not(.fullscreen){margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}.resize-handle.svelte-239wnu{position:absolute;bottom:0;right:0;width:10px;height:10px;fill:var(--block-border-color);cursor:nwse-resize}.fullscreen.svelte-239wnu{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;overflow:auto}.animating.svelte-239wnu{animation:svelte-239wnu-pop-out .1s ease-out forwards}@keyframes svelte-239wnu-pop-out{0%{position:fixed;top:var(--start-top);left:var(--start-left);width:var(--start-width);height:var(--start-height);z-index:100}to{position:fixed;top:0vh;left:0vw;width:100vw;height:100vh;z-index:1000}}.placeholder.svelte-239wnu{border-radius:var(--block-radius);border-width:var(--block-border-width);border-color:var(--block-border-color);border-style:dashed}Tables */ table,tr,td,th{margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);padding:var(--spacing-xl)}.md code,.md pre{background:none;font-family:var(--font-mono);font-size:var(--text-sm);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:2;tab-size:2;-webkit-hyphens:none;hyphens:none}.md pre[class*=language-]::selection,.md pre[class*=language-] ::selection,.md code[class*=language-]::selection,.md code[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}.md pre{padding:1em;margin:.5em 0;overflow:auto;position:relative;margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);box-shadow:none;border:none;border-radius:var(--radius-md);background:var(--code-background-fill);padding:var(--spacing-xxl);font-family:var(--font-mono);text-shadow:none;border-radius:var(--radius-sm);white-space:nowrap;display:block;white-space:pre}.md :not(pre)>code{padding:.1em;border-radius:var(--radius-xs);white-space:normal;background:var(--code-background-fill);border:1px solid var(--panel-border-color);padding:var(--spacing-xxs) var(--spacing-xs)}.md .token.comment,.md .token.prolog,.md .token.doctype,.md .token.cdata{color:#708090}.md .token.punctuation{color:#999}.md .token.namespace{opacity:.7}.md .token.property,.md .token.tag,.md .token.boolean,.md .token.number,.md .token.constant,.md .token.symbol,.md .token.deleted{color:#905}.md .token.selector,.md .token.attr-name,.md .token.string,.md .token.char,.md .token.builtin,.md .token.inserted{color:#690}.md .token.atrule,.md .token.attr-value,.md .token.keyword{color:#07a}.md .token.function,.md .token.class-name{color:#dd4a68}.md .token.regex,.md .token.important,.md .token.variable{color:#e90}.md .token.important,.md .token.bold{font-weight:700}.md .token.italic{font-style:italic}.md .token.entity{cursor:help}.dark .md .token.comment,.dark .md .token.prolog,.dark .md .token.cdata{color:#5c6370}.dark .md .token.doctype,.dark .md .token.punctuation,.dark .md .token.entity{color:#abb2bf}.dark .md .token.attr-name,.dark .md .token.class-name,.dark .md .token.boolean,.dark .md .token.constant,.dark .md .token.number,.dark .md .token.atrule{color:#d19a66}.dark .md .token.keyword{color:#c678dd}.dark .md .token.property,.dark .md .token.tag,.dark .md .token.symbol,.dark .md .token.deleted,.dark .md .token.important{color:#e06c75}.dark .md .token.selector,.dark .md .token.string,.dark .md .token.char,.dark .md .token.builtin,.dark .md .token.inserted,.dark .md .token.regex,.dark .md .token.attr-value,.dark .md .token.attr-value>.token.punctuation{color:#98c379}.dark .md .token.variable,.dark .md .token.operator,.dark .md .token.function{color:#61afef}.dark .md .token.url{color:#56b6c2}span.svelte-1m32c2s div[class*=code_wrap]{position:relative}span.svelte-1m32c2s span.katex{font-size:var(--text-lg);direction:ltr}span.svelte-1m32c2s div[class*=code_wrap]>button{z-index:1;cursor:pointer;border-bottom-left-radius:var(--radius-sm);padding:var(--spacing-md);width:25px;height:25px;position:absolute;right:0}span.svelte-1m32c2s .check{opacity:0;z-index:var(--layer-top);transition:opacity .2s;background:var(--code-background-fill);color:var(--body-text-color);position:absolute;top:var(--size-1-5);left:var(--size-1-5)}span.svelte-1m32c2s p:not(:first-child){margin-top:var(--spacing-xxl)}span.svelte-1m32c2s .md-header-anchor{margin-left:-25px;padding-right:8px;line-height:1;color:var(--body-text-color-subdued);opacity:0}span.svelte-1m32c2s h1:hover .md-header-anchor,span.svelte-1m32c2s h2:hover .md-header-anchor,span.svelte-1m32c2s h3:hover .md-header-anchor,span.svelte-1m32c2s h4:hover .md-header-anchor,span.svelte-1m32c2s h5:hover .md-header-anchor,span.svelte-1m32c2s h6:hover .md-header-anchor{opacity:1}span.md.svelte-1m32c2s .md-header-anchor>svg{color:var(--body-text-color-subdued)}span.svelte-1m32c2s table{word-break:break-word}div.svelte-17qq50w>.md.prose{font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}div.svelte-17qq50w>.md.prose *{color:var(--block-info-text-color)}div.svelte-17qq50w{margin-bottom:var(--spacing-md)}span.has-info.svelte-zgrq3{margin-bottom:var(--spacing-xs)}span.svelte-zgrq3:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-zgrq3{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}span[dir=rtl].svelte-zgrq3{display:block}.hide.svelte-zgrq3{margin:0;height:0}label.svelte-13ao5pu.svelte-13ao5pu{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-13ao5pu.svelte-13ao5pu{border-top-left-radius:0}label.float.svelte-13ao5pu.svelte-13ao5pu{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-13ao5pu.svelte-13ao5pu:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-13ao5pu.svelte-13ao5pu{height:0}span.svelte-13ao5pu.svelte-13ao5pu{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-13ao5pu.svelte-13ao5pu{box-shadow:none;border-width:0;background:transparent;overflow:visible}label[dir=rtl].svelte-13ao5pu.svelte-13ao5pu{border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-right:none;border-bottom-left-radius:var(--block-radius);border-bottom-right-radius:var(--block-label-radius);border-top-left-radius:var(--block-label-radius)}label[dir=rtl].svelte-13ao5pu span.svelte-13ao5pu{margin-left:var(--size-2);margin-right:0}button.svelte-qgco6m{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-xs);color:var(--block-label-text-color);border:1px solid transparent;padding:var(--spacing-xxs)}button.svelte-qgco6m:hover{background-color:var(--background-fill-secondary)}button[disabled].svelte-qgco6m{opacity:.5;box-shadow:none}button[disabled].svelte-qgco6m:hover{cursor:not-allowed}.padded.svelte-qgco6m{background:var(--bg-color)}button.svelte-qgco6m:hover,button.highlight.svelte-qgco6m{cursor:pointer;color:var(--color-accent)}.padded.svelte-qgco6m:hover{color:var(--block-label-text-color)}span.svelte-qgco6m{padding:0 1px;font-size:10px}div.svelte-qgco6m{display:flex;align-items:center;justify-content:center;transition:filter .2s ease-in-out}.x-small.svelte-qgco6m{width:10px;height:10px}.small.svelte-qgco6m{width:14px;height:14px}.medium.svelte-qgco6m{width:20px;height:20px}.large.svelte-qgco6m{width:22px;height:22px}.pending.svelte-qgco6m{animation:svelte-qgco6m-flash .5s infinite}@keyframes svelte-qgco6m-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-qgco6m{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6,.dropdown-arrow.svelte-ihhdbf{fill:currentColor}.circle.svelte-ihhdbf{fill:currentColor;opacity:.1}svg.svelte-pb9pol{animation:svelte-pb9pol-spin 1.5s linear infinite}@keyframes svelte-pb9pol-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}h2.svelte-1xg7h5n{font-size:var(--text-xl)!important}p.svelte-1xg7h5n,h2.svelte-1xg7h5n{white-space:pre-line}.wrap.svelte-1xg7h5n{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3);text-align:center;margin:auto var(--spacing-lg)}.or.svelte-1xg7h5n{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-1xg7h5n{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-1xg7h5n{font-size:var(--text-lg)}}.hovered.svelte-1xg7h5n{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-15ls1gu{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:100%;margin-left:auto;margin-right:auto;height:var(--size-10)}.icon.svelte-15ls1gu{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-15ls1gu{color:var(--color-accent)}.icon.svelte-15ls1gu:hover,.icon.svelte-15ls1gu:focus{color:var(--color-accent)}.icon-button-wrapper.svelte-109se4{display:flex;flex-direction:row;align-items:center;justify-content:center;z-index:var(--layer-3);gap:var(--spacing-sm);box-shadow:var(--shadow-drop);border:1px solid var(--border-color-primary);background:var(--block-background-fill);padding:var(--spacing-xxs)}.icon-button-wrapper.hide-top-corner.svelte-109se4{border-top:none;border-right:none;border-radius:var(--block-label-right-radius)}.icon-button-wrapper.display-top-corner.svelte-109se4{border-radius:var(--radius-sm) 0 0 var(--radius-sm);top:var(--spacing-sm);right:-1px}.icon-button-wrapper.svelte-109se4:not(.top-panel){border:1px solid var(--border-color-primary);border-radius:var(--radius-sm)}.top-panel.svelte-109se4{position:absolute;top:var(--block-label-margin);right:var(--block-label-margin);margin:0}.icon-button-wrapper.svelte-109se4 button{margin:var(--spacing-xxs);border-radius:var(--radius-xs);position:relative}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child),.icon-button-wrapper.svelte-109se4 button:not(:last-child){margin-right:var(--spacing-xxs)}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child):not(.no-border *):after,.icon-button-wrapper.svelte-109se4 button:not(:last-child):not(.no-border *):after{content:"";position:absolute;right:-4.5px;top:15%;height:70%;width:1px;background-color:var(--border-color-primary)}.icon-button-wrapper.svelte-109se4>*{height:100%}.unstyled-link.svelte-151nsdd{all:unset;cursor:pointer}img.svelte-kxeri3{object-fit:cover}.image-container.svelte-ui4ajv.svelte-ui4ajv{height:100%;position:relative;min-width:var(--size-20)}.image-container.svelte-ui4ajv button.svelte-ui4ajv{width:var(--size-full);height:var(--size-full);border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center}.image-frame.svelte-ui4ajv img{width:var(--size-full);height:var(--size-full);object-fit:scale-down}.selectable.svelte-ui4ajv.svelte-ui4ajv{cursor:crosshair}.fullscreen-controls svg{position:relative;top:0}.image-container:fullscreen{background-color:#000;display:flex;justify-content:center;align-items:center}.image-container:fullscreen img{max-width:90vw;max-height:90vh;object-fit:scale-down}.image-frame.svelte-ui4ajv.svelte-ui4ajv{width:auto;height:100%;display:flex;align-items:center;justify-content:center}.metadata-popup.svelte-ui4ajv.svelte-ui4ajv{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--background-fill-primary, white);border:1px solid var(--border-color-primary);box-shadow:0 4px 8px #0003;z-index:1000;border-radius:8px;overflow:hidden}.popup-content.svelte-ui4ajv.svelte-ui4ajv{position:relative;padding:1rem;display:flex;flex-direction:column;height:100%}.popup-title.svelte-ui4ajv.svelte-ui4ajv{font-weight:700;margin:0 0 1rem}.close-button.svelte-ui4ajv.svelte-ui4ajv{position:absolute;top:.5rem;right:.5rem;background:none;border:none;font-size:1rem;cursor:pointer}.metadata-table-container.svelte-ui4ajv.svelte-ui4ajv{flex:1;overflow:auto}.metadata-table.svelte-ui4ajv.svelte-ui4ajv{width:100%;border-collapse:collapse}.metadata-label.svelte-ui4ajv.svelte-ui4ajv{background:var(--background-fill-secondary, #f5f5f5);padding:.5rem;font-weight:700;text-align:left;vertical-align:top;width:40%}.metadata-value.svelte-ui4ajv.svelte-ui4ajv{padding:.5rem;white-space:nowrap;vertical-align:top}.load-metadata-button.svelte-ui4ajv.svelte-ui4ajv{margin-top:1rem;padding:.5rem 1rem;background-color:var(--button-primary-border-color);color:var(--button-primary-text-color);border:none;border-radius:4px;cursor:pointer;align-self:center}.load-metadata-button.svelte-ui4ajv.svelte-ui4ajv:hover{background-color:var(--button-primary-background-fill-hover)}.wrap.svelte-cr2edf.svelte-cr2edf{overflow-y:auto;transition:opacity .5s ease-in-out;background:var(--block-background-fill);position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:var(--size-40);width:var(--size-full)}.wrap.svelte-cr2edf.svelte-cr2edf:after{content:"";position:absolute;top:0;left:0;width:var(--upload-progress-width);height:100%;transition:all .5s ease-in-out;z-index:1}.uploading.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-lg);font-family:var(--font);z-index:2}.file-name.svelte-cr2edf.svelte-cr2edf{margin:var(--spacing-md);font-size:var(--text-lg);color:var(--body-text-color-subdued)}.file.svelte-cr2edf.svelte-cr2edf{font-size:var(--text-md);z-index:2;display:flex;align-items:center}.file.svelte-cr2edf progress.svelte-cr2edf{display:inline;height:var(--size-1);width:100%;transition:all .5s ease-in-out;color:var(--color-accent);border:none}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-value{background-color:var(--color-accent);border-radius:20px}.file.svelte-cr2edf progress[value].svelte-cr2edf::-webkit-progress-bar{background-color:var(--border-color-accent);border-radius:20px}.progress-bar.svelte-cr2edf.svelte-cr2edf{width:14px;height:14px;border-radius:50%;background:radial-gradient(closest-side,var(--block-background-fill) 64%,transparent 53% 100%),conic-gradient(var(--color-accent) var(--upload-progress-width),var(--border-color-accent) 0);transition:all .5s ease-in-out}button.svelte-1o7nwih{cursor:pointer;width:var(--size-full)}.center.svelte-1o7nwih{display:flex;justify-content:center}.flex.svelte-1o7nwih{display:flex;flex-direction:column;justify-content:center;align-items:center}.hidden.svelte-1o7nwih{display:none;position:absolute;flex-grow:0}.hidden.svelte-1o7nwih svg{display:none}.disable_click.svelte-1o7nwih{cursor:default}.icon-mode.svelte-1o7nwih{position:absolute!important;width:var(--size-4);height:var(--size-4);padding:0;min-height:0;border-radius:var(--radius-circle)}.icon-mode.svelte-1o7nwih svg{width:var(--size-4);height:var(--size-4)}.image-frame.svelte-18daxnr img{width:var(--size-full);height:var(--size-full);object-fit:scale-down}.upload-container.svelte-18daxnr{display:flex;align-items:center;justify-content:center;height:100%;flex-shrink:1;max-height:100%}.image-container.svelte-18daxnr{display:flex;height:100%;flex-direction:column;justify-content:center;align-items:center;max-height:100%;position:relative}.selectable.svelte-18daxnr{cursor:crosshair}.image-frame.svelte-18daxnr{object-fit:cover;width:100%;height:100%}.metadata-popup.svelte-18daxnr{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--background-fill-primary, white);border:1px solid var(--border-color-primary);box-shadow:0 4px 8px #0003;z-index:1000;border-radius:8px;overflow:hidden}.popup-content.svelte-18daxnr{position:relative;padding:1rem;display:flex;flex-direction:column;height:100%}.popup-title.svelte-18daxnr{font-weight:700;margin:0 0 1rem}.close-button.svelte-18daxnr{position:absolute;top:.5rem;right:.5rem;background:none;border:none;font-size:1rem;cursor:pointer}.metadata-table-container.svelte-18daxnr{flex:1;overflow:auto}.metadata-table.svelte-18daxnr{width:100%;border-collapse:collapse}.metadata-label.svelte-18daxnr{background:var(--background-fill-secondary, #f5f5f5);padding:.5rem;font-weight:700;text-align:left;vertical-align:top;width:40%}.metadata-value.svelte-18daxnr{padding:.5rem;white-space:nowrap;vertical-align:top}.load-metadata-button.svelte-18daxnr{margin-top:1rem;padding:.5rem 1rem;background-color:var(--button-primary-border-color);color:var(--button-primary-text-color);border:none;border-radius:4px;cursor:pointer;align-self:center}.load-metadata-button.svelte-18daxnr:hover{background-color:var(--button-primary-background-fill-hover)}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-17v219f.svelte-17v219f{top:0;right:0;left:0}.wrap.default.svelte-17v219f.svelte-17v219f{top:0;right:0;bottom:0;left:0}.hide.svelte-17v219f.svelte-17v219f{opacity:0;pointer-events:none}.generating.svelte-17v219f.svelte-17v219f{animation:svelte-17v219f-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-17v219f-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-17v219f.svelte-17v219f{background:none}@keyframes svelte-17v219f-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-17v219f-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-17v219f.svelte-17v219f{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-17v219f.svelte-17v219f{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-17v219f.svelte-17v219f{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-17v219f.svelte-17v219f{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-17v219f.svelte-17v219f{position:absolute;bottom:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-17v219f.svelte-17v219f{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-17v219f.svelte-17v219f{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-17v219f.svelte-17v219f{pointer-events:none}.minimal.svelte-17v219f .progress-text.svelte-17v219f{background:var(--block-background-fill)}.border.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary)}.clear-status.svelte-17v219f.svelte-17v219f{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.toast-body.svelte-syezpc{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-syezpc{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-syezpc{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-syezpc{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-syezpc{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-syezpc{border:1px solid var(--color-grey-700);background:var(--color-grey-50)}.dark .toast-body.info.svelte-syezpc{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-body.success.svelte-syezpc{border:1px solid var(--color-green-700);background:var(--color-green-50)}.dark .toast-body.success.svelte-syezpc{border:1px solid var(--color-green-500);background-color:var(--color-grey-950)}.toast-title.svelte-syezpc{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm)}.toast-title.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-title.error.svelte-syezpc{color:var(--color-red-50)}.toast-title.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-syezpc{color:var(--color-yellow-50)}.toast-title.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-title.info.svelte-syezpc{color:var(--color-grey-50)}.toast-title.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-title.success.svelte-syezpc{color:var(--color-green-50)}.toast-close.svelte-syezpc{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-close.error.svelte-syezpc{color:var(--color-red-500)}.toast-close.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-syezpc{color:var(--color-yellow-500)}.toast-close.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-close.info.svelte-syezpc{color:var(--color-grey-500)}.toast-close.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-close.success.svelte-syezpc{color:var(--color-green-500)}.toast-text.svelte-syezpc{font-size:var(--text-lg);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word}.toast-text.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-text.error.svelte-syezpc{color:var(--color-red-50)}.toast-text.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-syezpc{color:var(--color-yellow-50)}.toast-text.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-text.info.svelte-syezpc{color:var(--color-grey-50)}.toast-text.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-text.success.svelte-syezpc{color:var(--color-green-50)}.toast-details.svelte-syezpc{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-syezpc{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-syezpc{color:var(--color-red-700)}.dark .toast-icon.error.svelte-syezpc{color:var(--color-red-500)}.toast-icon.warning.svelte-syezpc{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-syezpc{color:var(--color-yellow-500)}.toast-icon.info.svelte-syezpc{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-syezpc{color:var(--color-grey-500)}.toast-icon.success.svelte-syezpc{color:var(--color-green-700)}.dark .toast-icon.success.svelte-syezpc{color:var(--color-green-500)}@keyframes svelte-syezpc-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-syezpc{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-syezpc-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-syezpc{background:var(--color-red-700)}.dark .timer.error.svelte-syezpc{background:var(--color-red-500)}.timer.warning.svelte-syezpc{background:var(--color-yellow-700)}.dark .timer.warning.svelte-syezpc{background:var(--color-yellow-500)}.timer.info.svelte-syezpc{background:var(--color-grey-700)}.dark .timer.info.svelte-syezpc{background:var(--color-grey-500)}.timer.success.svelte-syezpc{background:var(--color-green-700)}.dark .timer.success.svelte-syezpc{background:var(--color-green-500)}.hidden.svelte-syezpc{display:none}.toast-text.svelte-syezpc a{text-decoration:underline}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}.streaming-bar.svelte-ga0jj6{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--primary-600);animation:svelte-ga0jj6-countdown linear forwards;z-index:1}@keyframes svelte-ga0jj6-countdown{0%{transform:translate(0)}to{transform:translate(-100%)}}.container.svelte-1sgcyba img{width:100%;height:100%}.container.selected.svelte-1sgcyba{border-color:var(--border-color-accent)}.border.table.svelte-1sgcyba{border:2px solid var(--border-color-primary)}.container.table.svelte-1sgcyba{margin:0 auto;border-radius:var(--radius-lg);overflow:hidden;width:var(--size-20);height:var(--size-20);object-fit:cover}.container.gallery.svelte-1sgcyba{width:var(--size-20);max-width:var(--size-20);object-fit:cover}
|
src/demo/app.py
CHANGED
@@ -7,6 +7,7 @@ from gradio_propertysheet import PropertySheet
|
|
7 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
8 |
from pathlib import Path
|
9 |
|
|
|
10 |
output_dir = Path("outputs")
|
11 |
output_dir.mkdir(exist_ok=True)
|
12 |
|
@@ -27,6 +28,51 @@ class PropertyConfig:
|
|
27 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
28 |
description: str = field(default="", metadata={"label": "Description"})
|
29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
31 |
"""
|
32 |
Processes image metadata and maps it to output components.
|
@@ -45,11 +91,11 @@ def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
|
45 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
46 |
|
47 |
output_values = [gr.skip()] * len(output_fields)
|
48 |
-
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
49 |
if hasattr(component, 'root_label'):
|
50 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
51 |
else:
|
52 |
-
output_values[i] = gr.
|
53 |
|
54 |
return output_values
|
55 |
|
@@ -68,11 +114,11 @@ def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
|
|
68 |
return None
|
69 |
|
70 |
params = list(inputs)
|
71 |
-
image_params = dict(zip(input_fields.keys(), params))
|
72 |
-
|
73 |
-
metadata = {label: image_params.get(label, "") for label in dataclass_fields.keys()}
|
74 |
|
75 |
new_filepath = output_dir / "image_with_meta.png"
|
|
|
76 |
add_metadata(image_data, metadata, new_filepath)
|
77 |
|
78 |
return str(new_filepath)
|
|
|
7 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
8 |
from pathlib import Path
|
9 |
|
10 |
+
|
11 |
output_dir = Path("outputs")
|
12 |
output_dir.mkdir(exist_ok=True)
|
13 |
|
|
|
28 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
29 |
description: str = field(default="", metadata={"label": "Description"})
|
30 |
|
31 |
+
def infer_type(s: str):
|
32 |
+
"""
|
33 |
+
Infers and converts a string to the most likely data type.
|
34 |
+
|
35 |
+
It attempts conversions in the following order:
|
36 |
+
1. Integer
|
37 |
+
2. Float
|
38 |
+
3. Boolean (case-insensitive 'true' or 'false')
|
39 |
+
If all conversions fail, it returns the original string.
|
40 |
+
|
41 |
+
Args:
|
42 |
+
s: The input string to be converted.
|
43 |
+
|
44 |
+
Returns:
|
45 |
+
The converted value (int, float, bool) or the original string.
|
46 |
+
"""
|
47 |
+
if not isinstance(s, str):
|
48 |
+
# If the input is not a string, return it as is.
|
49 |
+
return s
|
50 |
+
|
51 |
+
# 1. Try to convert to an integer
|
52 |
+
try:
|
53 |
+
return int(s)
|
54 |
+
except ValueError:
|
55 |
+
# Not an integer, continue...
|
56 |
+
pass
|
57 |
+
|
58 |
+
# 2. Try to convert to a float
|
59 |
+
try:
|
60 |
+
return float(s)
|
61 |
+
except ValueError:
|
62 |
+
# Not a float, continue...
|
63 |
+
pass
|
64 |
+
|
65 |
+
# 3. Check for a boolean value
|
66 |
+
# This explicit check is important because bool('False') evaluates to True.
|
67 |
+
s_lower = s.lower()
|
68 |
+
if s_lower == 'true':
|
69 |
+
return True
|
70 |
+
if s_lower == 'false':
|
71 |
+
return False
|
72 |
+
|
73 |
+
# 4. If nothing else worked, return the original string
|
74 |
+
return s
|
75 |
+
|
76 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
77 |
"""
|
78 |
Processes image metadata and maps it to output components.
|
|
|
91 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
92 |
|
93 |
output_values = [gr.skip()] * len(output_fields)
|
94 |
+
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
95 |
if hasattr(component, 'root_label'):
|
96 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
97 |
else:
|
98 |
+
output_values[i] = gr.update(value=infer_type(value))
|
99 |
|
100 |
return output_values
|
101 |
|
|
|
114 |
return None
|
115 |
|
116 |
params = list(inputs)
|
117 |
+
image_params = dict(zip(input_fields.keys(), params))
|
118 |
+
metadata = {label: image_params.get(label, "") for label in image_params.keys()}
|
|
|
119 |
|
120 |
new_filepath = output_dir / "image_with_meta.png"
|
121 |
+
|
122 |
add_metadata(image_data, metadata, new_filepath)
|
123 |
|
124 |
return str(new_filepath)
|
src/demo/space.py
CHANGED
@@ -21,7 +21,7 @@ with gr.Blocks(
|
|
21 |
# `gradio_imagemeta`
|
22 |
|
23 |
<div style="display: flex; gap: 7px;">
|
24 |
-
<img alt="
|
25 |
</div>
|
26 |
|
27 |
Image Preview with Metadata for Gradio Interface
|
@@ -47,6 +47,7 @@ from gradio_propertysheet import PropertySheet
|
|
47 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
48 |
from pathlib import Path
|
49 |
|
|
|
50 |
output_dir = Path("outputs")
|
51 |
output_dir.mkdir(exist_ok=True)
|
52 |
|
@@ -67,25 +68,50 @@ class PropertyConfig:
|
|
67 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
68 |
description: str = field(default="", metadata={"label": "Description"})
|
69 |
|
70 |
-
def
|
71 |
\"\"\"
|
72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
Args:
|
75 |
-
|
76 |
-
img_all_path: File path for the image to display in img_all.
|
77 |
|
78 |
Returns:
|
79 |
-
|
80 |
\"\"\"
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
-
#
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
|
90 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
91 |
\"\"\"
|
@@ -105,11 +131,11 @@ def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
|
105 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
106 |
|
107 |
output_values = [gr.skip()] * len(output_fields)
|
108 |
-
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
109 |
if hasattr(component, 'root_label'):
|
110 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
111 |
else:
|
112 |
-
output_values[i] = gr.
|
113 |
|
114 |
return output_values
|
115 |
|
@@ -129,10 +155,11 @@ def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
|
|
129 |
|
130 |
params = list(inputs)
|
131 |
image_params = dict(zip(input_fields.keys(), params))
|
132 |
-
dataclass_fields = build_dataclass_fields(PropertyConfig)
|
133 |
-
metadata = {label: image_params.get(label, "") for label in
|
134 |
|
135 |
new_filepath = output_dir / "image_with_meta.png"
|
|
|
136 |
add_metadata(image_data, metadata, new_filepath)
|
137 |
|
138 |
return str(new_filepath)
|
@@ -157,17 +184,18 @@ with gr.Blocks() as demo:
|
|
157 |
label="Upload Image (Custom metadata only)",
|
158 |
type="filepath",
|
159 |
width=300,
|
160 |
-
height=400,
|
161 |
-
disable_preprocess=False,
|
162 |
interactive=True
|
163 |
)
|
164 |
img_all = ImageMeta(
|
165 |
label="Upload Image (All metadata)",
|
166 |
only_custom_metadata=False,
|
|
|
167 |
width=300,
|
168 |
-
height=400,
|
169 |
popup_metadata_height=400,
|
170 |
-
popup_metadata_width=500
|
|
|
171 |
)
|
172 |
|
173 |
gr.Markdown("## Metadata Viewer")
|
@@ -193,17 +221,7 @@ with gr.Blocks() as demo:
|
|
193 |
with gr.Row():
|
194 |
save_button = gr.Button("Add Metadata and Save Image")
|
195 |
saved_file_output = gr.File(label="Download Image")
|
196 |
-
|
197 |
-
with gr.Row():
|
198 |
-
gr.Examples(
|
199 |
-
examples=[
|
200 |
-
["./examples/image_with_meta.png", "./examples/image_with_meta.png"]
|
201 |
-
],
|
202 |
-
fn=process_example_images,
|
203 |
-
inputs=[img_custom, img_all],
|
204 |
-
outputs=[img_custom, img_all],
|
205 |
-
cache_examples=True
|
206 |
-
)
|
207 |
|
208 |
input_fields = {
|
209 |
"Model": model_box,
|
|
|
21 |
# `gradio_imagemeta`
|
22 |
|
23 |
<div style="display: flex; gap: 7px;">
|
24 |
+
<a href="https://pypi.org/project/gradio_imagemeta/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_imagemeta"></a>
|
25 |
</div>
|
26 |
|
27 |
Image Preview with Metadata for Gradio Interface
|
|
|
47 |
from gradio_propertysheet.helpers import build_dataclass_fields, create_dataclass_instance
|
48 |
from pathlib import Path
|
49 |
|
50 |
+
|
51 |
output_dir = Path("outputs")
|
52 |
output_dir.mkdir(exist_ok=True)
|
53 |
|
|
|
68 |
image_settings: ImageSettings = field(default_factory=ImageSettings)
|
69 |
description: str = field(default="", metadata={"label": "Description"})
|
70 |
|
71 |
+
def infer_type(s: str):
|
72 |
\"\"\"
|
73 |
+
Infers and converts a string to the most likely data type.
|
74 |
+
|
75 |
+
It attempts conversions in the following order:
|
76 |
+
1. Integer
|
77 |
+
2. Float
|
78 |
+
3. Boolean (case-insensitive 'true' or 'false')
|
79 |
+
If all conversions fail, it returns the original string.
|
80 |
|
81 |
Args:
|
82 |
+
s: The input string to be converted.
|
|
|
83 |
|
84 |
Returns:
|
85 |
+
The converted value (int, float, bool) or the original string.
|
86 |
\"\"\"
|
87 |
+
if not isinstance(s, str):
|
88 |
+
# If the input is not a string, return it as is.
|
89 |
+
return s
|
90 |
+
|
91 |
+
# 1. Try to convert to an integer
|
92 |
+
try:
|
93 |
+
return int(s)
|
94 |
+
except ValueError:
|
95 |
+
# Not an integer, continue...
|
96 |
+
pass
|
97 |
+
|
98 |
+
# 2. Try to convert to a float
|
99 |
+
try:
|
100 |
+
return float(s)
|
101 |
+
except ValueError:
|
102 |
+
# Not a float, continue...
|
103 |
+
pass
|
104 |
|
105 |
+
# 3. Check for a boolean value
|
106 |
+
# This explicit check is important because bool('False') evaluates to True.
|
107 |
+
s_lower = s.lower()
|
108 |
+
if s_lower == 'true':
|
109 |
+
return True
|
110 |
+
if s_lower == 'false':
|
111 |
+
return False
|
112 |
+
|
113 |
+
# 4. If nothing else worked, return the original string
|
114 |
+
return s
|
115 |
|
116 |
def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
|
117 |
\"\"\"
|
|
|
131 |
raw_values = transfer_metadata(output_fields, metadata, dataclass_fields)
|
132 |
|
133 |
output_values = [gr.skip()] * len(output_fields)
|
134 |
+
for i, (component, value) in enumerate(zip(output_fields, raw_values)):
|
135 |
if hasattr(component, 'root_label'):
|
136 |
output_values[i] = create_dataclass_instance(PropertyConfig, value)
|
137 |
else:
|
138 |
+
output_values[i] = gr.update(value=infer_type(value))
|
139 |
|
140 |
return output_values
|
141 |
|
|
|
155 |
|
156 |
params = list(inputs)
|
157 |
image_params = dict(zip(input_fields.keys(), params))
|
158 |
+
#dataclass_fields = build_dataclass_fields(PropertyConfig)
|
159 |
+
metadata = {label: image_params.get(label, "") for label in image_params.keys()}
|
160 |
|
161 |
new_filepath = output_dir / "image_with_meta.png"
|
162 |
+
|
163 |
add_metadata(image_data, metadata, new_filepath)
|
164 |
|
165 |
return str(new_filepath)
|
|
|
184 |
label="Upload Image (Custom metadata only)",
|
185 |
type="filepath",
|
186 |
width=300,
|
187 |
+
height=400,
|
|
|
188 |
interactive=True
|
189 |
)
|
190 |
img_all = ImageMeta(
|
191 |
label="Upload Image (All metadata)",
|
192 |
only_custom_metadata=False,
|
193 |
+
type="filepath",
|
194 |
width=300,
|
195 |
+
height=400,
|
196 |
popup_metadata_height=400,
|
197 |
+
popup_metadata_width=500,
|
198 |
+
interactive=True
|
199 |
)
|
200 |
|
201 |
gr.Markdown("## Metadata Viewer")
|
|
|
221 |
with gr.Row():
|
222 |
save_button = gr.Button("Add Metadata and Save Image")
|
223 |
saved_file_output = gr.File(label="Download Image")
|
224 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
|
226 |
input_fields = {
|
227 |
"Model": model_box,
|
src/frontend/shared/ImagePreview.svelte
CHANGED
@@ -355,7 +355,7 @@
|
|
355 |
.load-metadata-button {
|
356 |
margin-top: 1rem;
|
357 |
padding: 0.5rem 1rem;
|
358 |
-
background-color: var(--button-primary-
|
359 |
color: var(--button-primary-text-color);
|
360 |
border: none;
|
361 |
border-radius: 4px;
|
|
|
355 |
.load-metadata-button {
|
356 |
margin-top: 1rem;
|
357 |
padding: 0.5rem 1rem;
|
358 |
+
background-color: var(--button-primary-border-color);
|
359 |
color: var(--button-primary-text-color);
|
360 |
border: none;
|
361 |
border-radius: 4px;
|
src/frontend/shared/ImageUploader.svelte
CHANGED
@@ -452,7 +452,7 @@
|
|
452 |
.load-metadata-button {
|
453 |
margin-top: 1rem;
|
454 |
padding: 0.5rem 1rem;
|
455 |
-
background-color: var(--button-primary-
|
456 |
color: var(--button-primary-text-color);
|
457 |
border: none;
|
458 |
border-radius: 4px;
|
|
|
452 |
.load-metadata-button {
|
453 |
margin-top: 1rem;
|
454 |
padding: 0.5rem 1rem;
|
455 |
+
background-color: var(--button-primary-border-color);
|
456 |
color: var(--button-primary-text-color);
|
457 |
border: none;
|
458 |
border-radius: 4px;
|
src/outputs/image_with_meta.png
ADDED
![]() |
Git LFS Details
|
src/pyproject.toml
CHANGED
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
|
|
8 |
|
9 |
[project]
|
10 |
name = "gradio_imagemeta"
|
11 |
-
version = "0.0.
|
12 |
description = "Image Preview with Metadata for Gradio Interface"
|
13 |
readme = "README.md"
|
14 |
license = "apache-2.0"
|
|
|
8 |
|
9 |
[project]
|
10 |
name = "gradio_imagemeta"
|
11 |
+
version = "0.0.2"
|
12 |
description = "Image Preview with Metadata for Gradio Interface"
|
13 |
readme = "README.md"
|
14 |
license = "apache-2.0"
|