Dual-platform (macOS/iOS) media workflow automation app built with Swift/SwiftUI, featuring USB device detection, GPS metadata extraction, intelligent geocoding with persistent caching, automated file renaming, and cloud synchronization with CloudKit + MEGA integration.
Languages: Swift, XML, C (SQLite3 bindings)
Platforms: macOS 26.0+, iOS 17.0+
UI: SwiftUI (primary), AppKit/UIKit (platform-specific), MapKit (iOS)
Data: Core Data + CloudKit sync, SQLite3 (geocoding cache), UserDefaults
Networking: Foundation URLSession, Network framework (Bonjour TCP server), Mapbox API, MEGA API
System: IOKit (USB detection), AVFoundation/AVKit (video), CoreLocation (geocoding), CryptoKit/CommonCrypto (encryption), Security (Keychain)
Concurrency: async/await, Combine, GCD, OperationQueue
External Tools: ExifTool (metadata), Gyroflow (stabilization)
Architecture: MVVM + Coordinator, Protocol-oriented design, Dependency injection
Current Phase: Metadata Extraction & Smart Renaming (80% Complete)
- USB device detection for DJI Avata (detects "Untitled" volume, 45-50GB)
- File scanning from drone storage (
/DCIM/DJI_001/) - Preview tab with video thumbnails and playback
- Safe mode to prevent processing existing files
- Individual workflow step execution for debugging
- Permission handling for external volumes
- GPS extraction from SRT telemetry files
- Multi-provider geocoding (Mapbox β CoreLocation β ExifTool β Coordinates)
- Geocoding cache system (persistent, ~1km radius matching)
- Smart file renaming UI with suggestions
- Apply individual or batch rename operations
- Debug tab with file management
- ExifTool path detection: Currently fixing initialization error for detecting ExifTool location
- Error:
exifToolPathinitialization issue in MetadataExtractor.swift - ExifTool is installed at
/opt/homebrew/bin/exiftool - Need to complete the path detection logic properly
- Error:
- Video stabilization with Gyroflow
- MEGA cloud sync integration
- File cleanup after successful sync
- Settings persistence for workflow preferences
MVVM + Coordinator architecture for clear separation of concerns.
-
WorkflowManager.swift: Central orchestrator for all workflow steps
- Manages workflow state and progress
- Coordinates between different services
- Tracks processed files and their metadata
-
USBDetector.swift: Detects DJI Avata connection
- Uses IOKit and NSWorkspace
- Identifies "Untitled" volume with 45-50GB capacity
- Publishes connection status via Combine
-
MetadataExtractor.swift: GPS and geocoding functionality
- Extracts GPS from SRT telemetry files (multiple format support)
- Geocoding cascade: Mapbox β CoreLocation β ExifTool β Coordinates
- Persistent cache: Saves geocoded locations to UserDefaults
- Cache matching: ~1km radius for nearby coordinates
- Embeds metadata back into video files
-
FileManager.swift: Scans and organizes drone files
- Detects file types (MP4, JPG, SRT, LRV)
- Categorizes as video, photo, telemetry, or preview
- ContentView.swift: Main app container with tab navigation
- PreviewView.swift: File preview with thumbnails and video player
- WorkflowView.swift: Individual step execution with logs
- DebugView.swift: Smart renaming interface
- Shows suggested names after metadata extraction
- Individual "Apply" buttons per file
- "Apply All Suggestions" for batch operations
- Progress banners and visual feedback
- SettingsView.swift: Configuration interface
- Storage mode selection (Local/Cloud)
- Workflow options (safe mode, GPU acceleration)
- Mapbox token configuration
- Tool status checking
The app supports multiple DJI SRT telemetry formats:
// Format 1: GPS(lat,lon,alt)
GPS(37.7749, -122.4194, 15.5)
// Format 2: latitude: XX longitude: YY
latitude: 37.7749 longitude: -122.4194
// Format 3: DJI extended format
[GPS: (37.7749, -122.4194, 15.5)]Uses median filtering to handle GPS outliers from multiple telemetry points.
Multi-tier fallback ensures location resolution:
1. Check Cache (~1km radius)
β (cache miss)
2. Try Mapbox API (if token configured)
β (fails or no token)
3. Try Apple CoreLocation
β (fails)
4. Try ExifTool geolocation API
β (fails)
5. Use coordinates as filename (Lat37.7749_Lon-122.4194)
Cache Implementation:
- Key:
"latitude,longitude"string - Stored in UserDefaults under
"GeocodingCache" - Loaded on MetadataExtractor init
- Proximity matching prevents duplicate API calls for nearby locations
Format: City-State-Timestamp.ext
Examples:
San_Francisco-California-20241029_140523.MP4Brooklyn-New_York-20241028_163045.MP4Lat37.7749_Lon-122.4194-20241029_120000.MP4(coordinate fallback)
Sanitization: Replaces spaces, slashes, and backslashes with underscores to ensure filesystem compatibility.
Entitlements (Workflow.entitlements):
<!-- Sandbox disabled for development -->
<key>com.apple.security.app-sandbox</key>
<false/>
<!-- File access -->
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<!-- Removable volumes (USB drives) -->
<key>com.apple.security.files.removable-volume.read-write</key>
<true/>
<!-- Network for geocoding and MEGA -->
<key>com.apple.security.network.client</key>
<true/>
<!-- Run subprocesses (ExifTool, Gyroflow) -->
<key>com.apple.security.inherit</key>
<true/>Location: Workflow/Core/MetadataExtractor.swift:94
Error Messages:
Return from initializer without initializing all stored properties
Immutable value 'self.exifToolPath' may only be initialized once
Context:
- ExifTool is installed at
/opt/homebrew/bin/exiftool(symlink to/opt/homebrew/Cellar/exiftool/13.36/bin/exiftool) - The
init()method tries to find ExifTool in multiple locations - Changed
exifToolPathfromlettovarto allow reassignment - Need to initialize it once before the search logic
Current Code Structure:
class MetadataExtractor {
private var exifToolPath: String // Changed from 'let' to 'var'
init() {
// Initialize with default first
self.exifToolPath = "/usr/local/bin/exiftool"
// Then search for actual path
let possiblePaths = [
"/opt/homebrew/bin/exiftool",
"/opt/homebrew/Cellar/exiftool/13.36/bin/exiftool",
"/usr/local/bin/exiftool",
"/usr/bin/exiftool",
"/opt/local/bin/exiftool"
]
// Try to find and update exifToolPath...
}
}What Was Attempted:
- Added symlink resolution logic
- Added direct Cellar path checking
- Added 'which' command fallback
- Enhanced logging for debugging
Next Steps to Fix:
- The user modified line 7 to make
exifToolPathavarinstead oflet - The initialization at line 21 sets the default
- The error suggests there's still a code path where it's not initialized
- Check if all code paths properly initialize or update
exifToolPath
Status: β FIXED
Solution Implemented:
- Added persistent caching system
- Improved Mapbox API parsing (added
typesfilter) - Enhanced CoreLocation fallback with multiple field checks
- Added coordinate-based fallback naming
- Better error logging with emojis for debugging
-
ExifTool: Metadata extraction and embedding
- Install:
brew install exiftool - Expected locations:
/opt/homebrew/bin/or/usr/local/bin/
- Install:
-
Gyroflow (planned): Video stabilization
- Not yet integrated
- SwiftUI: UI framework
- Combine: Reactive programming for USB detection
- AVKit: Video playback and thumbnail generation
- CoreLocation: Geocoding fallback
- IOKit: USB device detection
- Mapbox Geocoding API (optional): Enhanced location resolution
- Token stored in UserDefaults:
"MapboxAccessToken" - Configure in Settings β Geocoding tab
- Format:
pk.xxxxx...(~80-100 chars)
- Token stored in UserDefaults:
- Install ExifTool:
brew install exiftool - (Optional) Get Mapbox token from https://www.mapbox.com/signup
- Build and run the app in Xcode
-
Connect Drone: Plug in DJI Avata USB drive
- App detects "Untitled" volume automatically
-
Preview Files: Switch to Preview tab
- See thumbnails of all videos and photos
- Double-click to open in Finder
- Click thumbnail to play video in modal
-
Extract Metadata: Go to Debug tab
- Click "Extract All Metadata" button
- Watch progress: "Scanning β Extracting GPS β Generating names"
- Blue banner appears with suggestions
-
Review Suggestions: Each file shows suggested name
- Format:
City-State-Timestamp.ext - Green "Apply" button per file
- Or use "Apply All Suggestions" for batch
- Format:
-
Apply Renames: Click Apply button(s)
- Individual confirmation for single files
- Batch rename without individual confirmations
- Success/failure logged to console
General Tab:
- Storage Mode: Local folder or MEGA cloud
- Auto-start workflow on USB connection
Workflow Tab:
- Safe Mode: Skip already-processed files
- Delete LRV preview files
- Delete after successful sync
- GPU acceleration for stabilization
- Parallel renders (1-8 simultaneous)
Geocoding Tab:
- Mapbox Access Token configuration
- Token validation and testing
- Network connection test (may fail in sandbox)
Tools Tab:
- Check installed tool status
- Quick install commands
Workflow/
βββ Workflow.xcodeproj
βββ Workflow/
β βββ WorkflowApp.swift # App entry point
β βββ Core/
β β βββ WorkflowManager.swift # Main coordinator
β β βββ USBDetector.swift # Device detection
β β βββ MetadataExtractor.swift # GPS & geocoding β οΈ
β β βββ FileManager.swift # File operations
β β βββ LogManager.swift # Logging utility
β β βββ Protocols/ # Protocol definitions
β βββ UI/
β β βββ ContentView.swift # Main container
β β βββ PreviewView.swift # File previews
β β βββ WorkflowView.swift # Step execution
β β βββ DebugView.swift # Smart renaming
β β βββ SettingsView.swift # Configuration
β βββ Models/
β β βββ (Data models)
β βββ Workflow.entitlements # App permissions
βββ README.md # This file
βββ DJIAvataWorkflow/ # Old folder (can be ignored)
All operations log to console with emojis for easy scanning:
- π Geocoding operations
- β Successful operations
β οΈ Warnings- β Errors
- πΎ Cache operations
- πΊοΈ Mapbox API calls
- π CoreLocation fallbacks
# Verify installation
which exiftool
# Should output: /opt/homebrew/bin/exiftool
# Check if it's working
exiftool -ver
# Should output: 13.36 (or similar)
# Test GPS extraction from SRT
exiftool path/to/file.SRT | grep GPSCheck Mapbox Token:
# In Settings β Geocoding, click "Test Connection"
# Watch logs for HTTP status and responseCheck Cache:
# Cache is stored in UserDefaults
defaults read com.yourorg.Workflow GeocodingCache# List connected volumes
diskutil list
# Should see "Untitled" volume around 49.5 GB
# Example: /dev/disk4 (external, physical)- Escaped backslashes: Changed
\\(to\(throughout codebase - WorkflowError conformance: Changed to
WorkflowErrorType.noDevice - CFHost type mismatch: Changed
BooltoDarwinBooleanfor sandbox checks - Force unwrap errors: Fixed CLPlacemark unwrapping in CoreLocation fallback
- ExifTool initialization: Current issue - see "Known Issues" above
The app requires actual DJI Avata hardware for full testing. For development:
- Mock the USB detector to return a test device
- Place sample MP4/SRT files in a test directory
- Update
FileManagerto scan test directory instead
Sandbox is currently disabled for development (app-sandbox = false). Before production:
- Enable sandbox
- Test all file operations with security-scoped bookmarks
- Verify network access for Mapbox
- Test ExifTool execution in sandboxed environment
When picking up this project in a new session:
-
First, fix the ExifTool initialization error:
- File:
Workflow/Core/MetadataExtractor.swift - Issue: Property initialization in
init() - Goal: Properly detect ExifTool at
/opt/homebrew/bin/exiftool
- File:
-
Test the full metadata extraction flow:
- Connect drone (or use test files)
- Click "Extract All Metadata"
- Verify GPS extraction from SRT files
- Verify geocoding (check logs for Mapbox/CoreLocation)
- Verify cache is working (second run should be faster)
- Verify suggested names appear correctly
-
Next features to implement (in priority order):
- Fix any remaining geocoding edge cases
- Implement video stabilization with Gyroflow
- Implement MEGA sync functionality
- Implement cleanup after successful sync
- Add progress indicators for long operations
-
Known limitations to address:
- Hardcoded ExifTool version in Cellar path (13.36)
- No retry logic for failed API calls
- No rate limiting for Mapbox API
- Cache never expires (consider TTL)
- No bulk metadata embedding (processes files one by one)
- Drone storage:
/Volumes/Untitled/DCIM/DJI_001/ - ExifTool:
/opt/homebrew/bin/exiftool - App logs: Check Xcode console
- Geocoding cache: UserDefaults key
"GeocodingCache"
"storageMode" // "Local" or "Cloud (MEGA)"
"localSyncFolder" // Path to local sync directory
"megaSyncFolder" // Path to MEGA sync directory
"autoStartWorkflow" // Boolean
"safeMode" // Boolean
"deleteLRVFiles" // Boolean
"deleteAfterSync" // Boolean
"useGPUAcceleration" // Boolean
"parallelRenders" // Int (1-8)
"MapboxAccessToken" // String (optional).MP4, .MOV β .video(codec: "h265")
.JPG, .JPEG β .photo
.SRT β .telemetry
.LRV β .preview (low-res preview files)GET https://api.mapbox.com/geocoding/v5/mapbox.places/{lon},{lat}.json
?access_token={token}
&types=place,locality,district
Built with SwiftUI for macOS 14.0+ Uses ExifTool by Phil Harvey Geocoding powered by Mapbox (optional)
Last Updated: 2024-10-29 Version: 0.8.0-alpha Status: Active Development