Kyle Bolen fecdef3acf Fix date parsing: handle both nanosecond and second AFC timestamps
Threshold: >1e15 = nanoseconds, >1e9 = seconds. Previous threshold of
1e18 was too high for nanosecond timestamps in the year 2025 range
(which are ~1.75e18, so actually fine). Also revert pull to use
afc.pull() since it exists in this pymobiledevice3 version.
2026-07-28 08:26:30 +00:00
2026-07-28 03:04:05 +00:00
2026-07-28 03:09:28 +00:00

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

  1. Launch the app
  2. Go to Settings and set your staging root (e.g. /Volumes/delphi/_staging)
  3. Make sure your NAS is mounted at that path
  4. Connect your phone via USB
  5. 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

Using iPhone and Android at the same time

If you have both phones plugged in, the Android phone must be in Transferring files mode (not Charging only). An Android phone in Charging mode can block macOS USB enumeration and cause gphoto2 to see 0 folders on the iPhone. Either:

  • Switch the Android to Transferring files mode from its notification shade, or
  • Unplug the Android while working with the iPhone

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.

Description
Desktop app for importing photos from phones into PhotoPhile staging
Readme 796 KiB
2026-08-02 11:32:00 -07:00
Languages
Kotlin 88.1%
Python 7%
Shell 4.9%