Table of Contents
- PNG Metadata Editor - Technical Documentation v2.3.0
- Table of Contents
- Overview
- Build System
- Architecture
- Code Structure
- Class Reference
- Function Documentation
- Data Flow
- 1. File Opening with Image Browser
- 2. Editing Process with JSON Flattening
- 3. Saving Metadata
- 4. Theme Detection Flow
- Theme System
- Usage Examples
- Troubleshooting
- Development Notes
- Contributing
- Version History
PNG Metadata Editor - Technical Documentation v2.3.0
Table of Contents
- Overview
- Build System
- Architecture
- Code Structure
- Class Reference
- Function Documentation
- Data Flow
- Theme System
- Usage Examples
- 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.icofor WindowsAppIcon.icnsfor 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
-
MVC Pattern:
- Model:
metadata_dict - View: Tkinter UI components
- Controller: Event handlers and methods
- Model:
-
Observer Pattern: UI updates when metadata changes via
refresh_tree() -
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 pathcurrent_directory: Current directory for browsermetadata_dict: Dictionary storing metadata fieldshas_unsaved_changes: Track unsaved modificationsstatus_timer: Timer for status messagesthumbnail_images: PhotoImage references for thumbnailsthumbnail_frames: Frame references for highlightingtheme_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=2for 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
defaultsorgsettings
Development Notes
v2.3.0 Architecture Decisions
-
JSON Flattening Strategy:
- Display formatted JSON for readability
- Store flattened JSON to preserve original format
- Automatic conversion in save workflows
-
File Handle Management:
- Always close PIL Image objects after reading
- Prevents file locking issues
- Enables reliable multi-file loading
-
Scroll Region Updates:
- Triple update strategy (0ms, 100ms, 300ms)
- Handles timing issues with tkinter geometry
- Ensures scroll region encompasses all thumbnails
-
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:
- Code Style: Follow PEP 8, use type hints, document methods
- Testing: Test on all platforms, verify dark mode, test large directories
- Pull Requests: Fork, create feature branch, submit with clear description
- 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