import gradio as gr from typing import List, Any, Literal from gradio_imagemeta import ImageMeta from gradio_imagemeta.helpers import add_metadata, transfer_metadata from gradio_propertysheet import PropertySheet from gradio_propertysheet.helpers import flatten_dataclass_with_labels from pathlib import Path from ui_config import PropertyConfig output_dir = Path("outputs") output_dir.mkdir(exist_ok=True) def load_default_image(): return "src/examples/image_with_meta.png" def handle_load_metadata(image_data: gr.EventData) -> List[Any]: """ Processes image metadata by calling the agnostic `transfer_metadata` helper. """ if not image_data or not hasattr(image_data, "_data"): return [gr.skip()] * len(output_fields) metadata = image_data._data if not metadata: return [gr.skip()] * len(output_fields) # --- UI-Specific Configuration --- # Define the map that tells the helper how to process the PropertySheet. sheet_map = { id(property_sheet): { "type": property_sheet._dataclass_type, "prefixes": [] # No prefixes needed for this simple case } } gradio_map = { id(component): label for label, component in input_fields.items() } # Call the agnostic helper function to do the heavy lifting. return transfer_metadata( output_fields=output_fields, metadata=metadata, propertysheet_map=sheet_map, gradio_component_map=gradio_map, remove_prefix_from_keys=False ) def save_image_with_metadata( image_data: Any, format: Literal[".png", ".png"], sheet_state: PropertyConfig, model: str, f_number: str, iso: str, s_churn_val: float, description: str ) -> str | None: """ Saves an image with updated metadata, merging data from the PropertySheet and individual UI components. This example deals with the PropertySheet component and the individual gradio components. Since they have the same labels here, we'll simply replace the metadata with each other's values. """ if not image_data: return None metadata = flatten_dataclass_with_labels(sheet_state) individual_component_values = { "Model": model, "FNumber": f_number, "ISOSpeedRatings": iso, "Schurn": s_churn_val, "Description": description } metadata["Image Settings - Model"] = individual_component_values["Model"] metadata["Image Settings - FNumber"] = individual_component_values["FNumber"] metadata["Image Settings - ISOSpeedRatings"] = individual_component_values["ISOSpeedRatings"] metadata["Image Settings - Schurn"] = individual_component_values["Schurn"] metadata["Description"] = individual_component_values["Description"] final_metadata = {str(key): value for key, value in metadata.items()} new_filepath = output_dir / f"image_with_meta{format}" add_metadata(image_data, new_filepath, final_metadata) return str(new_filepath) initial_property_from_meta_config = PropertyConfig() with gr.Blocks(theme=gr.themes.Ocean()) as demo: gr.Markdown("# ImageMeta Component Demo") gr.Markdown(""" 2. Upload demo image or an image with EXIF or PNG metadata using either the "Upload Imagem (Custom metadata only)" component or the "Upload Imagem (all metadata)" component. 3. Click the 'Info' icon (ⓘ) in the top-left of the image component to view the metadata panel. 4. Click 'Load Metadata' in the popup to populate the fields below with metadata values (`Model`, `FNumber`, `ISOSpeedRatings`, `Schurn`, `Description`). 5. The section below displays how metadata is rendered in components and the `PropertySheet` custom component, showing the hierarchical structure of the image settings. 6. 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. """ ) property_sheet_state = gr.State(value=initial_property_from_meta_config) with gr.Row(): img_custom = ImageMeta( label="Upload Image (Custom metadata only)", type="filepath", width=600, height=400, popup_metadata_height=350, popup_metadata_width=550, interactive=True, only_custom_metadata=True ) img_all = ImageMeta( label="Upload Image (All metadata)", only_custom_metadata=False, type="filepath", width=600, height=400, popup_metadata_height=350, popup_metadata_width=550, interactive=True ) gr.Markdown("## Metadata Viewer") gr.Markdown("### Individual Components") with gr.Row(): model_box = gr.Textbox(label="Model") fnumber_box = gr.Textbox(label="FNumber") iso_box = gr.Textbox(label="ISOSpeedRatings") s_churn = gr.Slider(label="Schurn", value=1.0, minimum=0.0, maximum=1.0, step=0.1) description_box = gr.Textbox(label="Description", lines=2) gr.Markdown("### PropertySheet Component") with gr.Row(): property_sheet = PropertySheet( value=initial_property_from_meta_config, label="Image Settings", width=400, height=550, visible=True, root_label="General" ) gr.Markdown("## Metadata Editor") with gr.Row(): save_format = gr.Radio(label="Image Format", choices=[".png", ".jpg"], value=".png") save_button = gr.Button("Add Metadata and Save Image") saved_file_output = gr.File(label="Download Image") input_fields = { "Model": model_box, "FNumber": fnumber_box, "ISOSpeedRatings": iso_box, "Schurn": s_churn, "Description": description_box } output_fields = [ property_sheet, model_box, fnumber_box, iso_box, s_churn, description_box ] img_custom.load_metadata(handle_load_metadata, inputs=None, outputs=output_fields) img_all.load_metadata(handle_load_metadata, inputs=None, outputs=output_fields) def handle_render_change(updated_config: PropertyConfig, current_state: PropertyConfig): """ Updates the PropertySheet state when its configuration changes. Args: updated_config: The new PropertyConfig instance from the PropertySheet. current_state: The current PropertyConfig state. Returns: A tuple of (updated_config, updated_config) or (current_state, current_state) if updated_config is None. """ if updated_config is None: return current_state, current_state return updated_config, updated_config property_sheet.change( fn=handle_render_change, inputs=[property_sheet, property_sheet_state], outputs=[property_sheet, property_sheet_state] ) save_button.click( save_image_with_metadata, inputs=[img_custom, save_format, property_sheet, *input_fields.values()], outputs=[saved_file_output] ) demo.load( fn=load_default_image, inputs=None, outputs=img_custom ) if __name__ == "__main__": demo.launch()