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
1. [Overview](#overview)
2. [Build System](#build-system)
3. [Code Structure](#code-structure)
4. [Class Reference](#class-reference)
5. [Function Documentation](#function-documentation)
6. [Data Flow](#data-flow)
7. [Usage Examples](#usage-examples)
8. [Troubleshooting](#troubleshooting)
3. [Architecture](#architecture)
4. [Code Structure](#code-structure)
5. [Class Reference](#class-reference)
6. [Function Documentation](#function-documentation)
7. [Data Flow](#data-flow)
8. [Theme System](#theme-system)
9. [Usage Examples](#usage-examples)
10. [Troubleshooting](#troubleshooting)
## 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.py Functionality
The `build.py` script provides a unified way to build the application across platforms:
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
```
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
@ -57,167 +51,286 @@ The `build.py` script provides a unified way to build the application across pla
- 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, APP_NAME)
├── PNGMetadataEditor (Main Class)
│ ├── __init__() - Initialization
│ ├── setup_ui() - UI Construction
│ ├── open_file() - File Operations
│ └── Various helper methods
└── Supporting Functions
├── format_value_for_display()
├── truncate_value()
└── mark_as_modified()
|
+-- 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
```python
class PNGMetadataEditor:
def __init__(self, root):
# Initialize application state
self.current_file = None # Currently loaded PNG file path
self.metadata_dict = {} # Dictionary storing all metadata fields
self.has_unsaved_changes = False # Track unsaved modifications
self.status_timer = None # Timer for status messages
```
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 |
|--------|-------------|
| `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 |
| `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 metadata value to clipboard |
| `show_about()` | Displays application about dialog |
### 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")
```
| `copy_value()` | Copies selected value to clipboard |
| `flatten_json_if_valid(value)` | Flattens JSON to preserve format |
## Function Documentation
### Core Functions
### Theme System
1. **`open_file()`**
- Opens a file dialog to select PNG file
- Extracts text chunks using PIL's `Image.text` attribute
- Populates `metadata_dict` with key-value pairs
- Refreshes UI to display new metadata
**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)
2. **`save_changes()`**
- Creates new `PngInfo` object with current metadata
- Saves image with updated metadata using PIL's `img.save()`
- Marks changes as saved and updates UI state
**get_theme_colors()**
- Returns color scheme dictionary based on detected theme
- Dark mode: #2b2b2b backgrounds, #ffffff text
- Light mode: #f0f0f0 backgrounds, #000000 text
3. **`refresh_tree()`**
- Clears existing tree view items
- Repopulates with current metadata from `metadata_dict`
- Truncates long values for display in tree
### Image Browser Functions
4. **`on_selection_change(event)`**
- Updates detail view when selection changes
- Formats selected value for display (JSON pretty-printing)
- Handles both JSON and plain text values
**load_directory_thumbnails(directory)**
- Loads 150x150 thumbnails for all PNG files
- Shows loading indicator with progress
- 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="")`**
- Displays temporary message in status bar
- Automatically clears after specified duration (ms)
- Supports colored messages for different status types
**scroll_to_thumbnail(filepath)**
- Centers selected thumbnail in viewport
- Uses `yview_moveto()` for smooth scrolling
2. **`update_title()`**
- Updates window title based on current state
- Shows filename when file is loaded
- Adds asterisk (*) for unsaved changes
### JSON Handling Functions
3. **`show_about()`**
- Displays application information dialog
- Shows version, description, author, and license
- Uses `messagebox.showinfo()` for standard dialog
**format_value_for_display(value)**
- Pretty-prints JSON with `indent=2` for editor display
- Falls back to original string if not valid JSON
### Data Handling Functions
1. **`format_value_for_display(value)`**
- Attempts to parse value as 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
**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**:
```mermaid
sequenceDiagram
User->>App: Click "Open PNG File"
App->>FileDialog: Show open dialog
User->>App: Select PNG file
### 1. File Opening with Image Browser
```mermaid
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->>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: Scroll to selected thumbnail
```
### 2. Editing Process with JSON Flattening
```mermaid
sequenceDiagram
participant User
participant App
participant Dialog
participant metadata_dict
participant UI
2. **Editing Process**:
```mermaid
sequenceDiagram
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"
App->>Dialog: Show edit dialog
User->>App: Modify value
User->>App: Click "Save"
App->>metadata_dict: Update value
App->>mark_as_modified: Set unsaved flag
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
```mermaid
sequenceDiagram
participant User
participant App
participant PIL
participant metadata_dict
participant File
3. **Saving**:
```mermaid
sequenceDiagram
User->>App: Click "Save Changes"
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->>mark_as_saved: Clear unsaved flag
App->>UI: Update status and title
```
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
```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
@ -227,28 +340,31 @@ def mark_as_modified(self):
root = tk.Tk()
app = PNGMetadataEditor(root)
# Open a file programmatically (simulated)
app.current_file = "example.png"
img = Image.open("example.png")
app.metadata_dict = dict(img.text)
app.refresh_tree()
# Browse directory (simulated)
app.current_directory = "/path/to/images"
app.load_directory_thumbnails("/path/to/images")
# Edit a field programmatically
app.edit_entry() # Would normally be triggered by UI event
# 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 Access
### Programmatic Metadata Access
```python
# Get all metadata as dictionary
# Get all metadata
metadata = app.metadata_dict
# Add new field programmatically
app.metadata_dict["NewField"] = "Value"
# 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()
app.refresh_tree()
# Check for unsaved changes
if app.has_unsaved_changes:
@ -257,50 +373,102 @@ if app.has_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
1. **File Not Opening**:
- Ensure file is a valid PNG
- Check file permissions
2. **Metadata Not Displaying**:
- Verify PNG contains text chunks
- Check for special characters in keys/values
**File Not Opening**
- Ensure file is a valid PNG
- Check file permissions
- Verify file isn't locked by another process
3. **Save Failures**:
- Ensure file isn't read-only
- Verify write permissions to directory
**Metadata Not Displaying**
- Verify PNG contains text chunks (tEXt, zTXt, iTXt)
- Check for special characters in keys/values
### Debugging Tips
- Check status bar for error messages
- Review console output for exceptions
- Verify metadata_dict contents in debugger
**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
### Build System Improvements
1. **Automatic Dependency Installation**:
- Script checks for `requirements.txt`
- Installs dependencies automatically
### v2.3.0 Architecture Decisions
2. **PyInstaller Management**:
- Verifies PyInstaller installation
- Auto-installs if missing
1. **JSON Flattening Strategy**:
- Display formatted JSON for readability
- Store flattened JSON to preserve original format
- Automatic conversion in save workflows
3. **Platform Detection**:
- Automatically applies correct build options
- Handles version information consistently
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. 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