Update Documentation

robert 2026-01-07 22:50:48 +01:00
parent f153fba905
commit 405075c4eb

@ -1,52 +1,46 @@
# PNG Metadata Editor - Technical Documentation # PNG Metadata Editor - Technical Documentation v2.3.0
## Table of Contents ## Table of Contents
1. [Overview](#overview) 1. [Overview](#overview)
2. [Build System](#build-system) 2. [Build System](#build-system)
3. [Code Structure](#code-structure) 3. [Architecture](#architecture)
4. [Class Reference](#class-reference) 4. [Code Structure](#code-structure)
5. [Function Documentation](#function-documentation) 5. [Class Reference](#class-reference)
6. [Data Flow](#data-flow) 6. [Function Documentation](#function-documentation)
7. [Usage Examples](#usage-examples) 7. [Data Flow](#data-flow)
8. [Troubleshooting](#troubleshooting) 8. [Theme System](#theme-system)
9. [Usage Examples](#usage-examples)
10. [Troubleshooting](#troubleshooting)
## Overview ## Overview
PNG Metadata Editor is a graphical application built with Python and Tkinter that allows users to view, edit, add, and delete metadata in PNG files. The application leverages the Pillow library for PNG manipulation. 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 System
### build.py Functionality ### build.py Functionality
The `build.py` script provides a unified way to build the application across platforms: 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.
1. **Dependency Management**:
- Checks for `requirements.txt` and installs dependencies
- Falls back to default dependencies if file not found
2. **PyInstaller Handling**:
- Verifies PyInstaller is installed
- Automatically installs it if missing
- Provides clear error messages if installation fails
3. **Platform-Specific Builds**:
- Detects operating system (Windows, macOS, Linux)
- Applies appropriate build options for each platform
- Includes version information from `version.txt` when available
4. **Build Process**:
```python
# Main build workflow:
1. Check/install PyInstaller
2. Install dependencies
3. Determine platform options
4. Execute PyInstaller build
```
### Build Requirements ### Build Requirements
- Python 3.6+ - Python 3.6+
- PyInstaller (auto-installed if missing) - PyInstaller (auto-installed if missing)
- Pillow library
- Platform-specific icon files: - Platform-specific icon files:
- `AppIcon.ico` for Windows - `AppIcon.ico` for Windows
- `AppIcon.icns` for macOS - `AppIcon.icns` for macOS
@ -57,168 +51,287 @@ The `build.py` script provides a unified way to build the application across pla
- Temporary build files are stored in `build/` - Temporary build files are stored in `build/`
- Version information is embedded from `version.txt` - 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 ## Code Structure
``` ```
png-meta-editor.py png-meta-editor.py
├── Constants (APP_VERSION, APP_NAME) |
├── PNGMetadataEditor (Main Class) +-- Constants
│ ├── __init__() - Initialization | +-- APP_VERSION = "2.3.0"
│ ├── setup_ui() - UI Construction | +-- APP_NAME = "PNG Metadata Editor"
│ ├── open_file() - File Operations |
│ └── Various helper methods +-- PNGMetadataEditor (Main Class)
└── Supporting Functions +-- __init__() - Initialization
├── format_value_for_display() +-- Theme System
├── truncate_value() | +-- detect_dark_mode()
└── mark_as_modified() | +-- 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 ## Class Reference
### PNGMetadataEditor Class ### PNGMetadataEditor Class
```python Instance variables:
class PNGMetadataEditor: - `current_file`: Currently loaded PNG file path
def __init__(self, root): - `current_directory`: Current directory for browser
# Initialize application state - `metadata_dict`: Dictionary storing metadata fields
self.current_file = None # Currently loaded PNG file path - `has_unsaved_changes`: Track unsaved modifications
self.metadata_dict = {} # Dictionary storing all metadata fields - `status_timer`: Timer for status messages
self.has_unsaved_changes = False # Track unsaved modifications - `thumbnail_images`: PhotoImage references for thumbnails
self.status_timer = None # Timer for status messages - `thumbnail_frames`: Frame references for highlighting
``` - `theme_colors`: Current theme color scheme
### Public Methods ### Public Methods
| Method | Description | | Method | Description |
|--------|-------------| |--------|-------------|
| `open_file()` | Loads PNG file and extracts metadata | | `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 | | `save_changes()` | Saves current metadata to file |
| `add_field()` | Opens dialog to add new metadata field | | `add_field()` | Opens dialog to add new metadata field |
| `edit_entry(event=None)` | Opens dialog to edit selected field | | `edit_entry(event=None)` | Opens dialog to edit selected field |
| `delete_field()` | Removes selected metadata field | | `delete_field()` | Removes selected metadata field |
| `copy_value()` | Copies selected metadata value to clipboard | | `copy_value()` | Copies selected value to clipboard |
| `show_about()` | Displays application about dialog | | `flatten_json_if_valid(value)` | Flattens JSON to preserve format |
### Helper Methods
```python
def format_value_for_display(self, value):
"""Try to parse as JSON for pretty printing"""
try:
return json.dumps(json.loads(value), indent=2)
except:
return str(value)
def truncate_value(self, value, max_length=80):
"""Shorten long values for tree display"""
return str(value)[:max_length] + "..." if len(str(value)) > max_length else str(value)
def mark_as_modified(self):
"""Mark the document as having unsaved changes"""
if not self.has_unsaved_changes:
self.has_unsaved_changes = True
self.update_title()
self.changes_label.config(text="Unsaved changes")
```
## Function Documentation ## Function Documentation
### Core Functions ### Theme System
1. **`open_file()`** **detect_dark_mode()**
- Opens a file dialog to select PNG file - Detects if OS is in dark mode
- Extracts text chunks using PIL's `Image.text` attribute - Platform-specific commands with 1-second timeout
- Populates `metadata_dict` with key-value pairs - Returns: `bool` (True if dark mode, False otherwise)
- Refreshes UI to display new metadata
2. **`save_changes()`** **get_theme_colors()**
- Creates new `PngInfo` object with current metadata - Returns color scheme dictionary based on detected theme
- Saves image with updated metadata using PIL's `img.save()` - Dark mode: #2b2b2b backgrounds, #ffffff text
- Marks changes as saved and updates UI state - Light mode: #f0f0f0 backgrounds, #000000 text
3. **`refresh_tree()`** ### Image Browser Functions
- Clears existing tree view items
- Repopulates with current metadata from `metadata_dict`
- Truncates long values for display in tree
4. **`on_selection_change(event)`** **load_directory_thumbnails(directory)**
- Updates detail view when selection changes - Loads 150x150 thumbnails for all PNG files
- Formats selected value for display (JSON pretty-printing) - Shows loading indicator with progress
- Handles both JSON and plain text values - Updates scroll region 3 times (0ms, 100ms, 300ms)
### UI Management Functions **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
1. **`set_status(message, duration=3000, color="")`** **scroll_to_thumbnail(filepath)**
- Displays temporary message in status bar - Centers selected thumbnail in viewport
- Automatically clears after specified duration (ms) - Uses `yview_moveto()` for smooth scrolling
- Supports colored messages for different status types
2. **`update_title()`** ### JSON Handling Functions
- Updates window title based on current state
- Shows filename when file is loaded
- Adds asterisk (*) for unsaved changes
3. **`show_about()`** **format_value_for_display(value)**
- Displays application information dialog - Pretty-prints JSON with `indent=2` for editor display
- Shows version, description, author, and license - Falls back to original string if not valid JSON
- Uses `messagebox.showinfo()` for standard dialog
### Data Handling Functions **flatten_json_if_valid(value)**
- Flattens JSON to single line: `separators=(',', ':')`
1. **`format_value_for_display(value)`** - Preserves original compact format before saving
- Attempts to parse value as JSON - Falls back to original string if not valid JSON
- Returns pretty-printed JSON if successful
- Falls back to string representation otherwise
2. **`truncate_value(value, max_length=80)`**
- Shortens long values for tree display
- Adds ellipsis (...) to truncated values
- Preserves full value in detail view
3. **`mark_as_modified()`**
- Sets unsaved changes flag
- Updates UI indicators (title, label)
- Prevents duplicate change notifications
## Data Flow ## Data Flow
1. **File Opening**: ### 1. File Opening with Image Browser
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
User->>App: Click "Open PNG File" participant User
App->>FileDialog: Show open dialog participant App
User->>App: Select PNG file 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) App->>PIL: Image.open(filepath)
PIL->>App: Extract text chunks PIL->>App: Extract text chunks
App->>metadata_dict: Store key-value pairs App->>PIL: img.close() <- IMPORTANT
App->>App: Store in metadata_dict
App->>UI: Refresh tree and detail view App->>UI: Refresh tree and detail view
App->>UI: Scroll to selected thumbnail
``` ```
2. **Editing Process**: ### 2. Editing Process with JSON Flattening
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant User
participant App
participant Dialog
participant metadata_dict
participant UI
User->>App: Select field in tree User->>App: Select field in tree
App->>on_selection_change: Update detail view 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" User->>App: Click "Edit Field"
App->>Dialog: Show edit dialog App->>Dialog: Show edit dialog with formatted JSON
User->>App: Modify value User->>Dialog: Modify value
User->>App: Click "Save" User->>Dialog: Click "Save"
App->>metadata_dict: Update value Dialog->>App: Return edited value
App->>mark_as_modified: Set unsaved flag 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 App->>UI: Refresh tree view
``` ```
3. **Saving**: ### 3. Saving Metadata
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant User
participant App
participant PIL
participant metadata_dict
participant File
User->>App: Click "Save Changes" User->>App: Click "Save Changes"
App->>PIL: Image.open(current_file) App->>PIL: Image.open(current_file)
App->>PngInfo: Create new metadata object 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) App->>PIL: img.save(pnginfo=metadata)
App->>mark_as_saved: Clear unsaved flag PIL->>File: Write PNG with metadata
App->>UI: Update status and title App->>App: mark_as_saved()
App->>App: Update UI (remove asterisk)
App->>App: Show success status
``` ```
### 4. Theme Detection Flow
```mermaid
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 ## Usage Examples
### Basic Workflow ### Basic Workflow
@ -227,28 +340,31 @@ def mark_as_modified(self):
root = tk.Tk() root = tk.Tk()
app = PNGMetadataEditor(root) app = PNGMetadataEditor(root)
# Open a file programmatically (simulated) # Browse directory (simulated)
app.current_file = "example.png" app.current_directory = "/path/to/images"
img = Image.open("example.png") app.load_directory_thumbnails("/path/to/images")
app.metadata_dict = dict(img.text)
app.refresh_tree()
# Edit a field programmatically # Load specific file
app.edit_entry() # Would normally be triggered by UI event 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 # Save changes
app.save_changes() app.save_changes()
``` ```
### Programmatic Access ### Programmatic Metadata Access
```python ```python
# Get all metadata as dictionary # Get all metadata
metadata = app.metadata_dict metadata = app.metadata_dict
# Add new field programmatically # Add field with JSON (will be flattened on save)
app.metadata_dict["NewField"] = "Value" json_value = json.dumps({"key": "value"}, indent=2)
app.metadata_dict["NewField"] = json_value
app.mark_as_modified() app.mark_as_modified()
app.refresh_tree()
# Check for unsaved changes # Check for unsaved changes
if app.has_unsaved_changes: if app.has_unsaved_changes:
@ -257,50 +373,102 @@ if app.has_unsaved_changes:
## Troubleshooting ## 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 ### Common Issues
1. **File Not Opening**:
**File Not Opening**
- Ensure file is a valid PNG - Ensure file is a valid PNG
- Check file permissions - Check file permissions
- Verify file isn't locked by another process
2. **Metadata Not Displaying**: **Metadata Not Displaying**
- Verify PNG contains text chunks - Verify PNG contains text chunks (tEXt, zTXt, iTXt)
- Check for special characters in keys/values - Check for special characters in keys/values
3. **Save Failures**: **Save Failures**
- Ensure file isn't read-only - Ensure file isn't read-only
- Verify write permissions to directory - Verify write permissions to directory
- Check available disk space
### Debugging Tips **Dark Mode Not Working**
- Check status bar for error messages - Verify theme detection command access
- Review console output for exceptions - Check terminal for subprocess errors
- Verify metadata_dict contents in debugger - Ensure permissions for `defaults` or `gsettings`
## Development Notes ## Development Notes
### Build System Improvements ### v2.3.0 Architecture Decisions
1. **Automatic Dependency Installation**:
- Script checks for `requirements.txt`
- Installs dependencies automatically
2. **PyInstaller Management**: 1. **JSON Flattening Strategy**:
- Verifies PyInstaller installation - Display formatted JSON for readability
- Auto-installs if missing - Store flattened JSON to preserve original format
- Automatic conversion in save workflows
3. **Platform Detection**: 2. **File Handle Management**:
- Automatically applies correct build options - Always close PIL Image objects after reading
- Handles version information consistently - 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 ### Future Improvements
- Batch processing for multiple files - Batch processing for multiple files
- Metadata validation rules - Metadata validation rules
- Export/import to JSON - Export/import to JSON
- Advanced search functionality - Advanced search functionality
- Undo/redo support
- Reactive theme changes (watch for OS theme changes)
- Async thumbnail loading
- Virtual scrolling for large directories
## Contributing ## Contributing
When contributing to this project: When contributing to this project:
1. Fork the repository
2. Create a feature branch
3. Submit a pull request with tests
For major changes, please open an issue first to discuss what you would like to change. 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