AndroidConnector: scan both /storage/emulated/0/DCIM and /storage/emulated/0/Pictures — all subdirs of each. ExifFilter: add Slack, Teams, Zoom, Discord, sticker, gif, avatar, profile, thumbnail, cache to excluded path fragments. These land in the Other section of the folder panel.
PhotoPhetch
A macOS desktop app for importing photos from phones into PhotoPhile's staging directory.
Solves the "messy phone import" problem: only grabs camera-roll originals, filters out screenshots and received images, optionally converts HEIC → JPEG, and SHA-256 verifies every file before optionally deleting it from the phone.
Features
- Android support via ADB (
adb pull) - iPhone support via gphoto2 (PTP over USB)
- Smart filtering: excludes screenshots, WhatsApp/Telegram received images, and Live Photo motion clips
- HEIC → JPEG conversion via macOS
sips(built-in, no install needed) - SHA-256 verification of every copied file
- Safe delete: explicit confirmation screen before removing files from the phone
- Staging path convention: writes to
_staging/<YYYY-MM-DD>_<DeviceName>/ - Persists settings to
~/.photophetch/config.json
Requirements
- macOS (primary target)
- Java 21+
- Homebrew for dependencies
Install dependencies
# For iPhone support
brew install gphoto2
# For Android support
brew install android-platform-tools
Running
From source (development)
cd photophetch
./gradlew run
Build a fat JAR
./gradlew packageUberJarForCurrentOS
# Output: build/compose/jars/photophetch-macos-*.jar
java -jar build/compose/jars/photophetch-macos-*.jar
Build a macOS .app bundle
./gradlew packageDmg
# Output: build/compose/binaries/main/dmg/PhotoPhetch-1.0.0.dmg
First-time setup
- Launch the app
- Go to Settings and set your staging root (e.g.
/Volumes/delphi/_staging) - Make sure your NAS is mounted at that path
- Connect your phone via USB
- Click Scan for Devices on the Connect screen
iPhone notes
When you plug in an iPhone, macOS's built-in PTPCamera and AMPDeviceDiscoveryAgent daemons grab the device before gphoto2 or ADB can. Symptoms: "Could not connect to device" errors, Image Capture showing the phone under DEVICES with nothing listed, or LOC:something entries cycling in Image Capture.
Kill them before scanning:
killall PTPCamera
killall AMPDeviceDiscoveryAgent
Close Image Capture first if it's open. Then rescan in PhotoPhetch.
To stop them from re-grabbing the device on every USB connect (recommended if you use PhotoPhetch regularly):
launchctl unload -w /System/Library/LaunchAgents/com.apple.ptpcamera.plist
To re-enable Image Capture later:
launchctl load -w /System/Library/LaunchAgents/com.apple.ptpcamera.plist
HEIC settings
If your iPhone is shooting in HEIC (the default), PhotoPhetch will warn you and convert to JPEG during import. To stop shooting HEIC:
Settings → Camera → Formats → Most Compatible
Android notes
Enable USB debugging:
Settings → About Phone → tap Build Number 7 times → Developer Options → USB Debugging
When you plug in the USB cable, a prompt will appear on the phone asking for the connection mode. Choose "Transferring files / Android Auto" (also called "File Transfer" on some phones). Do not choose "Transferring images" (PTP) — it restricts file access and breaks ADB browsing.
Accept the "Trust this computer?" prompt on the phone when connecting.
Project structure
src/main/kotlin/com/bolenpad/photophetch/
├── Main.kt # Entry point
├── device/
│ ├── DeviceConnector.kt # Interface
│ ├── AndroidConnector.kt # ADB implementation
│ └── IosConnector.kt # gphoto2 implementation
├── model/
│ ├── PhonePhoto.kt # Photo metadata
│ ├── ImportJob.kt # Import batch + results
│ ├── DeviceInfo.kt # Connected device info
│ └── AppConfig.kt # Persisted preferences
├── transfer/
│ └── Copier.kt # Copy + SHA-256 verify + HEIC conversion
├── ui/
│ ├── App.kt # Root composable + navigation
│ ├── AppState.kt # StateFlow ViewModel
│ ├── ConnectScreen.kt # Device detection
│ ├── BrowseScreen.kt # Photo grid + filters
│ ├── ImportScreen.kt # Import configuration
│ ├── VerifyScreen.kt # Progress + results + delete confirmation
│ └── SettingsScreen.kt # Staging path + preferences
└── util/
├── ConfigStore.kt # Config persistence
├── ExifFilter.kt # Screenshot/noise filtering
└── StagingPath.kt # Staging directory path resolution
PhotoPhile integration
PhotoPhetch writes directly to the mounted NAS path — no API calls. After import, PhotoPhile picks up the new folder in _staging/ and runs it through its normal pipeline: configure → normalize → publish.