elismasilva commited on
Commit
ff8cc66
·
verified ·
1 Parent(s): 9203f8d

Upload folder using huggingface_hub

Browse files
.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-%20orange">
14
 
15
  Image Preview with Metadata for Gradio Interface
16
 
17
- ## Installation
 
 
 
 
 
 
 
 
 
 
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 process_example_images(img_custom_path: str, img_all_path: str) -> tuple[str, str]:
56
  """
57
- Processes example image paths for display in ImageMeta components.
 
 
 
 
 
 
58
 
59
  Args:
60
- img_custom_path: File path for the image to display in img_custom.
61
- img_all_path: File path for the image to display in img_all.
62
 
63
  Returns:
64
- Tuple of file paths for img_custom and img_all outputs.
65
  """
66
- # Verify file existence
67
- if not Path(img_custom_path).is_file():
68
- raise FileNotFoundError(f"Image not found: {img_custom_path}")
69
- if not Path(img_all_path).is_file():
70
- raise FileNotFoundError(f"Image not found: {img_all_path}")
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- # Return file paths as strings (ImageMeta accepts file paths as input)
73
- return str(img_custom_path), str(img_all_path)
 
 
 
 
 
 
 
 
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.Textbox(value=value)
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 dataclass_fields.keys()}
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
- output_dir = Path("/tmp")
11
- output_dir.mkdir(exist_ok=True)
12
-
13
- @dataclass
14
- class ImageSettings:
15
- """Configuration for image metadata settings."""
16
- model: str = field(default="", metadata={"label": "Model"})
17
- f_number: str = field(default="", metadata={"label": "FNumber"})
18
- iso_speed_ratings: str = field(default="", metadata={"label": "ISOSpeedRatings"})
19
- s_churn: float = field(
20
- default=0.0,
21
- metadata={"component": "slider", "label": "Schurn", "minimum": 0.0, "maximum": 1.0, "step": 0.01},
22
- )
23
-
24
- @dataclass
25
- class PropertyConfig:
26
- """Root configuration for image properties, including nested image settings."""
27
- image_settings: ImageSettings = field(default_factory=ImageSettings)
28
- description: str = field(default="", metadata={"label": "Description"})
29
-
30
- def infer_type(s: str):
31
- """
32
- Infers and converts a string to the most likely data type.
33
-
34
- It attempts conversions in the following order:
35
- 1. Integer
36
- 2. Float
37
- 3. Boolean (case-insensitive 'true' or 'false')
38
- If all conversions fail, it returns the original string.
39
-
40
- Args:
41
- s: The input string to be converted.
42
-
43
- Returns:
44
- The converted value (int, float, bool) or the original string.
45
- """
46
- if not isinstance(s, str):
47
- # If the input is not a string, return it as is.
48
- return s
49
-
50
- # 1. Try to convert to an integer
51
- try:
52
- return int(s)
53
- except ValueError:
54
- # Not an integer, continue...
55
- pass
56
-
57
- # 2. Try to convert to a float
58
- try:
59
- return float(s)
60
- except ValueError:
61
- # Not a float, continue...
62
- pass
63
-
64
- # 3. Check for a boolean value
65
- # This explicit check is important because bool('False') evaluates to True.
66
- s_lower = s.lower()
67
- if s_lower == 'true':
68
- return True
69
- if s_lower == 'false':
70
- return False
71
-
72
- # 4. If nothing else worked, return the original string
73
- return s
74
-
75
- def handle_load_metadata(image_data: ImageMeta | None) -> List[Any]:
76
- """
77
- Processes image metadata and maps it to output components.
78
-
79
- Args:
80
- image_data: ImageMeta object containing image data and metadata, or None.
81
-
82
- Returns:
83
- A list of values for output components (Textbox, Slider, or PropertySheet instances).
84
- """
85
- if not image_data:
86
- return [gr.Textbox(value="") for _ in output_fields]
87
-
88
- metadata = extract_metadata(image_data, only_custom_metadata=True)
89
- dataclass_fields = build_dataclass_fields(PropertyConfig)
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.update(value=infer_type(value))
98
-
99
- return output_values
100
-
101
- def save_image_with_metadata(image_data: Any, *inputs: Any) -> str | None:
102
- """
103
- Saves an image with updated metadata to a file.
104
-
105
- Args:
106
- image_data: Input image data (e.g., file path or PIL Image).
107
- *inputs: Variable number of input values from UI components (Textbox, Slider).
108
-
109
- Returns:
110
- The file path of the saved image, or None if no image is provided.
111
- """
112
- if not image_data:
113
- return 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 image_params.keys()}
119
-
120
- new_filepath = output_dir / "image_with_meta.png"
121
- add_metadata(image_data, metadata, new_filepath)
122
-
123
- return str(new_filepath)
124
-
125
- initial_property_from_meta_config = PropertyConfig()
126
-
127
- with gr.Blocks() as demo:
128
- gr.Markdown("# ImageMeta Component Demo")
129
- gr.Markdown(
130
- """
131
- **To Test:**
132
- 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.
133
- 2. Click the 'Info' icon (ⓘ) in the top-left of the image component to view the metadata panel.
134
- 3. Click 'Load Metadata' in the popup to populate the fields below with metadata values (`Model`, `FNumber`, `ISOSpeedRatings`, `Schurn`, `Description`).
135
- 4. The section below displays how metadata is rendered in components and the `PropertySheet` custom component, showing the hierarchical structure of the image settings.
136
- 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.
137
- """
138
- )
139
- property_sheet_state = gr.State(value=initial_property_from_meta_config)
140
- with gr.Row():
141
- img_custom = ImageMeta(
142
- label="Upload Image (Custom metadata only)",
143
- type="filepath",
144
- width=300,
145
- height=400,
146
- interactive=True
147
- )
148
- img_all = ImageMeta(
149
- label="Upload Image (All metadata)",
150
- only_custom_metadata=False,
151
- type="filepath",
152
- width=300,
153
- height=400,
154
- popup_metadata_height=400,
155
- popup_metadata_width=500,
156
- interactive=True
157
- )
158
-
159
- gr.Markdown("## Metadata Viewer")
160
- gr.Markdown("### Individual Components")
161
- with gr.Row():
162
- model_box = gr.Textbox(label="Model")
163
- fnumber_box = gr.Textbox(label="FNumber")
164
- iso_box = gr.Textbox(label="ISOSpeedRatings")
165
- s_churn = gr.Slider(label="Schurn", value=1.0, minimum=0.0, maximum=1.0, step=0.1)
166
- description_box = gr.Textbox(label="Description")
167
-
168
- gr.Markdown("### PropertySheet Component")
169
- with gr.Row():
170
- property_sheet = PropertySheet(
171
- value=initial_property_from_meta_config,
172
- label="Image Settings",
173
- width=400,
174
- height=550,
175
- visible=True,
176
- root_label="General"
177
- )
178
- gr.Markdown("## Metadata Editor")
179
- with gr.Row():
180
- save_button = gr.Button("Add Metadata and Save Image")
181
- saved_file_output = gr.File(label="Download Image")
182
-
183
-
184
- input_fields = {
185
- "Model": model_box,
186
- "FNumber": fnumber_box,
187
- "ISOSpeedRatings": iso_box,
188
- "Schurn": s_churn,
189
- "Description": description_box
190
- }
191
-
192
- output_fields = [
193
- property_sheet,
194
- model_box,
195
- fnumber_box,
196
- iso_box,
197
- s_churn,
198
- description_box
199
- ]
200
-
201
- img_custom.load_metadata(handle_load_metadata, inputs=img_custom, outputs=output_fields)
202
- img_all.load_metadata(handle_load_metadata, inputs=img_all, outputs=output_fields)
203
-
204
- def handle_render_change(updated_config: PropertyConfig, current_state: PropertyConfig):
205
- """
206
- Updates the PropertySheet state when its configuration changes.
207
-
208
- Args:
209
- updated_config: The new PropertyConfig instance from the PropertySheet.
210
- current_state: The current PropertyConfig state.
211
-
212
- Returns:
213
- A tuple of (updated_config, updated_config) or (current_state, current_state) if updated_config is None.
214
- """
215
- if updated_config is None:
216
- return current_state, current_state
217
- return updated_config, updated_config
218
-
219
- property_sheet.change(
220
- fn=handle_render_change,
221
- inputs=[property_sheet, property_sheet_state],
222
- outputs=[property_sheet, property_sheet_state]
223
- )
224
- save_button.click(
225
- save_image_with_metadata,
226
- inputs=[img_custom, *input_fields.values()],
227
- outputs=[saved_file_output]
228
- )
229
-
230
- if __name__ == "__main__":
231
- demo.launch(debug=True)
 
 
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="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
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 process_example_images(img_custom_path: str, img_all_path: str) -> tuple[str, str]:
71
  \"\"\"
72
- Processes example image paths for display in ImageMeta components.
 
 
 
 
 
 
73
 
74
  Args:
75
- img_custom_path: File path for the image to display in img_custom.
76
- img_all_path: File path for the image to display in img_all.
77
 
78
  Returns:
79
- Tuple of file paths for img_custom and img_all outputs.
80
  \"\"\"
81
- # Verify file existence
82
- if not Path(img_custom_path).is_file():
83
- raise FileNotFoundError(f"Image not found: {img_custom_path}")
84
- if not Path(img_all_path).is_file():
85
- raise FileNotFoundError(f"Image not found: {img_all_path}")
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- # Return file paths as strings (ImageMeta accepts file paths as input)
88
- return str(img_custom_path), str(img_all_path)
 
 
 
 
 
 
 
 
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.Textbox(value=value)
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 dataclass_fields.keys()}
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-%20orange">
14
 
15
  Image Preview with Metadata for Gradio Interface
16
 
17
- ## Installation
 
 
 
 
 
 
 
 
 
 
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 process_example_images(img_custom_path: str, img_all_path: str) -> tuple[str, str]:
56
  """
57
- Processes example image paths for display in ImageMeta components.
 
 
 
 
 
 
58
 
59
  Args:
60
- img_custom_path: File path for the image to display in img_custom.
61
- img_all_path: File path for the image to display in img_all.
62
 
63
  Returns:
64
- Tuple of file paths for img_custom and img_all outputs.
65
  """
66
- # Verify file existence
67
- if not Path(img_custom_path).is_file():
68
- raise FileNotFoundError(f"Image not found: {img_custom_path}")
69
- if not Path(img_all_path).is_file():
70
- raise FileNotFoundError(f"Image not found: {img_all_path}")
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- # Return file paths as strings (ImageMeta accepts file paths as input)
73
- return str(img_custom_path), str(img_all_path)
 
 
 
 
 
 
 
 
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.Textbox(value=value)
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 dataclass_fields.keys()}
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.Textbox(value=value)
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
- dataclass_fields = build_dataclass_fields(PropertyConfig)
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="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.1%20-%20orange">
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 process_example_images(img_custom_path: str, img_all_path: str) -> tuple[str, str]:
71
  \"\"\"
72
- Processes example image paths for display in ImageMeta components.
 
 
 
 
 
 
73
 
74
  Args:
75
- img_custom_path: File path for the image to display in img_custom.
76
- img_all_path: File path for the image to display in img_all.
77
 
78
  Returns:
79
- Tuple of file paths for img_custom and img_all outputs.
80
  \"\"\"
81
- # Verify file existence
82
- if not Path(img_custom_path).is_file():
83
- raise FileNotFoundError(f"Image not found: {img_custom_path}")
84
- if not Path(img_all_path).is_file():
85
- raise FileNotFoundError(f"Image not found: {img_all_path}")
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- # Return file paths as strings (ImageMeta accepts file paths as input)
88
- return str(img_custom_path), str(img_all_path)
 
 
 
 
 
 
 
 
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.Textbox(value=value)
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 dataclass_fields.keys()}
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-background-fill);
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-background-fill);
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

  • SHA256: 21d3f57b5657cbdbfcc65be66db9793ec5592cf7d375468c1dd4677147dad769
  • Pointer size: 132 Bytes
  • Size of remote file: 5.79 MB
src/pyproject.toml CHANGED
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
 
9
  [project]
10
  name = "gradio_imagemeta"
11
- version = "0.0.1"
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"