Direct AFC access to /DCIM and PhotoData/Photos.sqlite. No backup, no MTP/WPD layer.
Includes hidden, trashed, and hidden-from-Explorer files -- confirmed against a real
device: 327 files in DCIM, 40 hidden, 16 trashed, 3 non-empty albums resolved correctly.
pip install -r requirements.txtusbmuxd is usually already running if GVFS can see the iPhone. Check with:
systemctl status usbmuxdIf missing: pacman -S usbmuxd (or libimobiledevice, which pulls it in as a dependency).
- Install Apple Mobile Device Support -- via iTunes (Microsoft Store or apple.com) or a standalone extract of the MSI.
- Confirm the
Apple Mobile Device Serviceis running (services.msc). - On first connect, tap "Trust This Computer" on the phone -- this step can't be skipped, it's enforced by the lockdown protocol itself.
No code changes needed after that. create_using_usbmux() finds the right transport on
any OS.
python main.py list
python main.py list --with-labels
python main.py list --with-labels --json result.json
python main.py export ./out # everything
python main.py export ./out --hidden # only hidden photos
python main.py export ./out --trashed # only Recently Deleted
python main.py export ./out --favorite # only favorites
python main.py export ./out --album Twitter # only photos in this album
python main.py download /DCIM/100APPLE/IMG_0001.HEIC ./IMG_0001.HEICexport downloads matching files into a local directory in one batch, printing progress
as it goes. Filters can't be combined (e.g. --hidden --album X isn't AND/OR-composable
in this version -- only one filter is applied at a time, whichever is set). If a filter
flag needs Photos.sqlite data, export pulls it automatically first. Files with the
same name from different 10xAPPLE folders get prefixed with the folder name to avoid
overwriting each other.
--with-labels pulls Photos.sqlite over AFC and joins hidden/trashed/favorite/album
data onto each file. Without it, list only does the DCIM walk.
python main.py export-all ./outDownloads every photo once, then sorts into:
./out/ -- plain photos: not hidden, not trashed, not in any album
./out/HIDDEN/ -- hidden photos
./out/TRASHED/ -- Recently Deleted
./out/<album name>/ -- one folder per non-empty album
./out/.cache/ -- actual downloaded files (hidden folder, don't touch)
A photo can appear in more than one folder -- iOS doesn't remove a photo from an album
when it's hidden or moved to Recently Deleted, so e.g. a hidden photo that's also in
"Twitter" shows up in both HIDDEN/ and Twitter/. Each file is downloaded from the
device exactly once into .cache/; every other location is a hardlink to that same file
(same inode, zero extra disk space, works without admin rights on Windows -- unlike
symlinks). Falls back to a real copy if hardlinking fails (e.g. output dir on a different
filesystem than expected).
- Path is
/PhotoData/Photos.sqlite, same AFC root as/DCIM.Mobilebackup2Servicewas tried first but throwsBackupFilterPasswordRequiredErroras soon as a domain filter is applied, even on an unencrypted backup -- that's a protocol-level restriction, not something fixable with different parameters. -wal/-shmside files are pulled too when present, since they can hold the latest writes not yet checkpointed into the main file.- The asset-album join table (e.g.
Z_30ASSETS) has a device-specific number in its name that does not necessarily match the number in its own column names -- observedZ_30ASSETSwith columnsZ_3ASSETS/Z_30ALBUMS. Resolved by column suffix, not by parsing the number. ZGENERICALBUM.ZKIND = 2is the only kind that maps to a real user-created album. Other kinds are Apple smart albums (ZTITLEusually NULL) or sync bookkeeping rows with a non-null title (e.g.progress-fs-importatZKIND~3570-3573) -- both excluded.ZASSETschema varies across iOS versions (ZADDITIONALASSETATTRIBUTESand similar tables appeared in later releases). Columns are resolved by name with fallback candidates rather than assumed fixed.
.MISCunderDCIMis skipped by default (afc_lister.py, folder names starting with.). Remove that check if it turns out to hold media too.