4 Documentation
robert edited this page 2026-01-07 22:50:48 +01:00

PNG Metadata Editor - Technical Documentation v2.3.0

Table of Contents

  1. Overview
  2. Build System
  3. Architecture
  4. Code Structure
  5. Class Reference
  6. Function Documentation
  7. Data Flow
  8. Theme System
  9. Usage Examples
  10. Troubleshooting

Overview

PNG Metadata Editor v2.3.0 is a graphical application built with Python and Tkinter that allows users to view, edit, add, and delete metadata in PNG files. The application features an image browser with thumbnail previews, automatic dark mode detection, and intelligent JSON handling.

Key Technologies

  • Python 3.6+: Core language
  • Tkinter: GUI framework
  • Pillow (PIL): PNG image manipulation and metadata extraction
  • subprocess: System theme detection

Version 2.3.0 Features

  • Image browser with thumbnail previews
  • Automatic dark mode detection (macOS, Windows, Linux)
  • Smart JSON flattening (preserves original format)
  • Enhanced file handle management
  • Trackpad and mousewheel scrolling support
  • Resizable UI panes

Build System

build.py Functionality

The build.py script provides a unified way to build the application across platforms with automatic dependency management, PyInstaller handling, and platform-specific build options.

Build Requirements

  • Python 3.6+
  • PyInstaller (auto-installed if missing)
  • Pillow library
  • Platform-specific icon files:
    • AppIcon.ico for Windows
    • AppIcon.icns for macOS

Build Output

  • Executable is created in the dist/ directory
  • Temporary build files are stored in build/
  • Version information is embedded from version.txt

Architecture

Component Overview

+-----------------------------------------------+
|         PNG Metadata Editor v2.3.0            |
+-----------------------------------------------+
|  +-----------+  +------------------------+    |
|  |   Image   |  |   Metadata Editor      |    |
|  |   Browser |  |  +------------------+  |    |
|  |           |  |  |   Tree View      |  |    |
|  | Thumbnails|  |  +------------------+  |    |
|  |  + Scroll |  |  |  Detail Pane     |  |    |
|  +-----------+  |  +------------------+  |    |
|                 |   Edit/Add/Delete      |    |
|                 +------------------------+    |
+-----------------------------------------------+
|            Status Bar & Controls              |
+-----------------------------------------------+

Design Patterns

  1. MVC Pattern:

    • Model: metadata_dict
    • View: Tkinter UI components
    • Controller: Event handlers and methods
  2. Observer Pattern: UI updates when metadata changes via refresh_tree()

  3. Strategy Pattern: Platform-specific theme detection

Code Structure

png-meta-editor.py
|
+-- Constants
|   +-- APP_VERSION = "2.3.0"
|   +-- APP_NAME = "PNG Metadata Editor"
|
+-- PNGMetadataEditor (Main Class)
    +-- __init__() - Initialization
    +-- Theme System
    |   +-- detect_dark_mode()
    |   +-- get_theme_colors()
    +-- UI Construction
    |   +-- setup_ui()
    |   +-- Image Browser (left pane)
    |   +-- Metadata Editor (right pane)
    +-- Image Browser
    |   +-- browse_directory()
    |   +-- load_directory_thumbnails()
    |   +-- load_file_from_path()
    |   +-- scroll_to_thumbnail()
    +-- Metadata Operations
    |   +-- open_file()
    |   +-- save_changes()
    |   +-- add_field()
    |   +-- edit_entry()
    |   +-- delete_field()
    +-- JSON Handling
    |   +-- format_value_for_display()
    |   +-- flatten_json_if_valid()
    +-- Helper Methods
        +-- refresh_tree()
        +-- set_status()
        +-- mark_as_modified()

Class Reference

PNGMetadataEditor Class

Instance variables:

  • current_file: Currently loaded PNG file path
  • current_directory: Current directory for browser
  • metadata_dict: Dictionary storing metadata fields
  • has_unsaved_changes: Track unsaved modifications
  • status_timer: Timer for status messages
  • thumbnail_images: PhotoImage references for thumbnails
  • thumbnail_frames: Frame references for highlighting
  • theme_colors: Current theme color scheme

Public Methods

Method Description
detect_dark_mode() Detects OS dark mode setting
get_theme_colors() Returns color scheme based on theme
browse_directory() Opens directory browser for thumbnails
load_directory_thumbnails(directory) Loads PNG thumbnails from directory
load_file_from_path(filepath) Loads specific PNG file metadata
open_file() Opens file dialog and loads PNG
save_changes() Saves current metadata to file
add_field() Opens dialog to add new metadata field
edit_entry(event=None) Opens dialog to edit selected field
delete_field() Removes selected metadata field
copy_value() Copies selected value to clipboard
flatten_json_if_valid(value) Flattens JSON to preserve format

Function Documentation

Theme System

detect_dark_mode()

  • Detects if OS is in dark mode
  • Platform-specific commands with 1-second timeout
  • Returns: bool (True if dark mode, False otherwise)

get_theme_colors()

  • Returns color scheme dictionary based on detected theme
  • Dark mode: #2b2b2b backgrounds, #ffffff text
  • Light mode: #f0f0f0 backgrounds, #000000 text

Image Browser Functions

load_directory_thumbnails(directory)

  • Loads 150x150 thumbnails for all PNG files
  • Shows loading indicator with progress
  • Updates scroll region 3 times (0ms, 100ms, 300ms)

load_file_from_path(filepath, auto_scroll=True)

  • Opens image with PIL
  • Extracts text chunks to metadata_dict
  • Critical: Closes image file handle with img.close()
  • Refreshes UI and scrolls to thumbnail

scroll_to_thumbnail(filepath)

  • Centers selected thumbnail in viewport
  • Uses yview_moveto() for smooth scrolling

JSON Handling Functions

format_value_for_display(value)

  • Pretty-prints JSON with indent=2 for editor display
  • Falls back to original string if not valid JSON

flatten_json_if_valid(value)

  • Flattens JSON to single line: separators=(',', ':')
  • Preserves original compact format before saving
  • Falls back to original string if not valid JSON

Data Flow

1. File Opening with Image Browser

sequenceDiagram
    participant User
    participant App
    participant FileDialog
    participant PIL
    participant UI

    User->>App: Click "Browse Directory"
    App->>FileDialog: Show directory dialog
    User->>App: Select directory
    App->>App: load_directory_thumbnails()
    App->>UI: Show loading indicator
    loop For each PNG file
        App->>PIL: Open and create thumbnail
        App->>UI: Create thumbnail frame
    end
    App->>UI: Update scroll region (3x)
    User->>App: Click thumbnail
    App->>App: load_file_from_path()
    App->>PIL: Image.open(filepath)
    PIL->>App: Extract text chunks
    App->>PIL: img.close() <- IMPORTANT
    App->>App: Store in metadata_dict
    App->>UI: Refresh tree and detail view
    App->>UI: Scroll to selected thumbnail

2. Editing Process with JSON Flattening

sequenceDiagram
    participant User
    participant App
    participant Dialog
    participant metadata_dict
    participant UI

    User->>App: Select field in tree
    App->>App: on_selection_change()
    App->>App: format_value_for_display()
    Note over App: Pretty-print JSON with indent=2
    App->>UI: Show formatted value
    User->>App: Click "Edit Field"
    App->>Dialog: Show edit dialog with formatted JSON
    User->>Dialog: Modify value
    User->>Dialog: Click "Save"
    Dialog->>App: Return edited value
    App->>App: flatten_json_if_valid()
    Note over App: Flatten to single line
    App->>metadata_dict: Update with flattened value
    App->>App: mark_as_modified()
    App->>UI: Refresh tree view

3. Saving Metadata

sequenceDiagram
    participant User
    participant App
    participant PIL
    participant metadata_dict
    participant File

    User->>App: Click "Save Changes"
    App->>PIL: Image.open(current_file)
    App->>PIL: Create PngInfo object
    loop For each metadata field
        App->>metadata_dict: Get key-value pair
        Note over App: Values already flattened
        App->>PIL: metadata.add_text(key, value)
    end
    App->>PIL: img.save(pnginfo=metadata)
    PIL->>File: Write PNG with metadata
    App->>App: mark_as_saved()
    App->>App: Update UI (remove asterisk)
    App->>App: Show success status

4. Theme Detection Flow

sequenceDiagram
    participant App
    participant detect_dark_mode
    participant subprocess
    participant winreg
    participant get_theme_colors
    participant UI

    App->>detect_dark_mode: Check OS theme
    alt macOS
        detect_dark_mode->>subprocess: defaults read AppleInterfaceStyle
        subprocess-->>detect_dark_mode: "Dark" or error
    else Windows
        detect_dark_mode->>winreg: Read AppsUseLightTheme
        winreg-->>detect_dark_mode: 0=Dark, 1=Light
    else Linux
        detect_dark_mode->>subprocess: gsettings get gtk-theme
        subprocess-->>detect_dark_mode: theme name
    end
    detect_dark_mode-->>App: Return boolean (True=Dark)
    App->>get_theme_colors: Get color scheme
    get_theme_colors-->>App: Return color dict
    App->>UI: Apply colors to canvas/frames

Theme System

Color Schemes

Dark Mode (when OS theme is dark):

  • canvas_bg: #2b2b2b (dark gray)
  • frame_bg: #2b2b2b
  • text_fg: #ffffff (white)
  • loading_fg: #6bb6ff (light blue)

Light Mode (when OS theme is light):

  • canvas_bg: #f0f0f0 (light gray)
  • frame_bg: #f0f0f0
  • text_fg: #000000 (black)
  • loading_fg: #4a90d9 (blue)

Platform-Specific Detection

Platform Method Command/Registry
macOS defaults defaults read -g AppleInterfaceStyle
Windows Registry HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme
Linux gsettings gsettings get org.gnome.desktop.interface gtk-theme

Usage Examples

Basic Workflow

# Initialize application
root = tk.Tk()
app = PNGMetadataEditor(root)

# Browse directory (simulated)
app.current_directory = "/path/to/images"
app.load_directory_thumbnails("/path/to/images")

# Load specific file
app.load_file_from_path(Path("/path/to/image.png"))

# Edit metadata
app.metadata_dict["prompt"] = '{"text":"a cat","steps":20}'
app.mark_as_modified()
app.refresh_tree()

# Save changes
app.save_changes()

Programmatic Metadata Access

# Get all metadata
metadata = app.metadata_dict

# Add field with JSON (will be flattened on save)
json_value = json.dumps({"key": "value"}, indent=2)
app.metadata_dict["NewField"] = json_value
app.mark_as_modified()

# Check for unsaved changes
if app.has_unsaved_changes:
    print("There are unsaved changes!")

Troubleshooting

Fixed in v2.3.0

Can't Load Second File

  • Status: FIXED
  • Cause: Image file handle not closed
  • Solution: Added img.close() after reading metadata

JSON Format Changes After Edit

  • Status: FIXED
  • Cause: Pretty-printed JSON was saved as-is
  • Solution: flatten_json_if_valid() restores compact format

Scroll Stops Before End of Thumbnails

  • Status: FIXED
  • Cause: Scroll region not updated after thumbnail loading
  • Solution: Triple scroll region update (0ms, 100ms, 300ms)

Common Issues

File Not Opening

  • Ensure file is a valid PNG
  • Check file permissions
  • Verify file isn't locked by another process

Metadata Not Displaying

  • Verify PNG contains text chunks (tEXt, zTXt, iTXt)
  • Check for special characters in keys/values

Save Failures

  • Ensure file isn't read-only
  • Verify write permissions to directory
  • Check available disk space

Dark Mode Not Working

  • Verify theme detection command access
  • Check terminal for subprocess errors
  • Ensure permissions for defaults or gsettings

Development Notes

v2.3.0 Architecture Decisions

  1. JSON Flattening Strategy:

    • Display formatted JSON for readability
    • Store flattened JSON to preserve original format
    • Automatic conversion in save workflows
  2. File Handle Management:

    • Always close PIL Image objects after reading
    • Prevents file locking issues
    • Enables reliable multi-file loading
  3. Scroll Region Updates:

    • Triple update strategy (0ms, 100ms, 300ms)
    • Handles timing issues with tkinter geometry
    • Ensures scroll region encompasses all thumbnails
  4. Theme Detection:

    • Platform-specific detection methods
    • Fallback to light mode if detection fails
    • Applied once at startup (not reactive)

Future Improvements

  • Batch processing for multiple files
  • Metadata validation rules
  • Export/import to JSON
  • Advanced search functionality
  • Undo/redo support
  • Reactive theme changes (watch for OS theme changes)
  • Async thumbnail loading
  • Virtual scrolling for large directories

Contributing

When contributing to this project:

  1. Code Style: Follow PEP 8, use type hints, document methods
  2. Testing: Test on all platforms, verify dark mode, test large directories
  3. Pull Requests: Fork, create feature branch, submit with clear description
  4. Issues: Include version, reproduction steps, sample PNG if relevant

For major changes, please open an issue first to discuss.

Version History

  • v2.3.0 (2026-01-07): Major UI update

    • Added image browser with thumbnail previews
    • Implemented dark mode detection for all platforms
    • Smart JSON flattening (preserves original format)
    • Fixed file loading with proper handle management
    • Enhanced scroll region updates
    • Unicode support in UI
  • v1.0.0 (2026-01-05): Initial release

    • Basic metadata viewing and editing
    • Tree view with detail pane
    • Add, edit, delete operations
    • JSON pretty-printing