- Unicode block progress bar (█▓░) - Cloud downloads show ☁ with average speed (MB/s, KB/s, B/s) - Truncated filenames with ellipsis for long names - Error indicator (✗) in progress bar - Simplified to serial export for clean cancel behavior - Added IsCancelled() to Bridge interface
photoscli
photoscli is a small macOS-only CLI written in Go that reads data from Apple Photos through a PhotoKit bridge.
It supports five tasks:
- listing albums
- listing asset IDs, filenames, and cloud status in an album
- showing the folder and album tree
- backing up all albums into the Photos folder tree
- exporting resized JPEG previews or original files from an album
What The Code Does
The executable lives in cmd/photoscli and calls into internal/photos, which wraps an Objective-C bridge in bridge/.
Current behavior:
albumsprints one line per album as<album-id>\t<title>photos --album-id <id-or-title>prints one asset per line as<id>\t<filename>\t<cloud>; accepts either a PhotoKit local identifier or an album titletreeprints the human-readable folder and album hierarchy from Apple Photosbackup-all --out <dir> [--size <px>] [--originals]exports every album into a matching folder tree, showing per-asset progress with filename, size, and cloud statusexport --album-id <id-or-title> --out <dir> [--size <px>] [--originals]exports either JPEG previews or original files with a progress bar showing filename, size, and cloud status;--album-idaccepts either a PhotoKit local identifier or an album title
The bridge uses PhotoKit to:
- request access to the user's Photos library
- fetch album collections by local identifier or album title
- fetch album assets sorted by
creationDateascending - render resized images with
PHImageManager - write JPEG files with compression
0.85 - support graceful cancellation via a cancel flag checked between file exports
Requirements
- macOS
- Go 1.22+
- Xcode command-line tools
The project builds with cgo and links against Photos, Foundation, and AppKit.
Build
make build
Output binary:
./bin/photoscli
Test
make test
Tests run against a stub bridge, so they do not require real Photos access.
Usage
./bin/photoscli albums
./bin/photoscli photos --album-id "<album-local-identifier>"
./bin/photoscli photos --album-id "Vacation"
./bin/photoscli tree
./bin/photoscli backup-all --out ./backup
./bin/photoscli backup-all --out ./backup --originals
./bin/photoscli export --album-id "<album-local-identifier>" --out ./export
./bin/photoscli export --album-id "Vacation" --out ./export
./bin/photoscli export --album-id "<album-local-identifier>" --out ./export --size 2048
./bin/photoscli export --album-id "<album-local-identifier>" --out ./export --originals
Commands
albums
- Requests Photos access
- Lists albums as tab-separated album ID and title
Example output:
5E9F.../L0/001 Vacation
8A1B.../L0/001 Work
photos --album-id <id-or-title>
- Requests Photos access
- If the value looks like a PhotoKit local identifier, uses it directly
- Otherwise searches album titles for a match and resolves the identifier
- Lists asset local identifiers and cloud status for the given album
Example output:
1F2A.../L0/001 IMG_0001.JPG local
9C4D.../L0/001 IMG_0002.JPG cloud
backup-all --out <dir> [--size <px>] [--originals]
- Requests Photos access
- Walks the Photos folder and album hierarchy
- Creates directories as
out/folder/album/files - Exports previews by default, originals when
--originalsis present - Shows per-asset progress bar with filename, file size, and cloud/local status
- Uses
--sizeonly for preview export
Example layout:
backup/
Trips/
Italy 2024/
Venice/
0000_....jpg
Favorites/
0000_....jpg
export --album-id <id-or-title> --out <dir> [--size <px>] [--originals]
- Requests Photos access
- Resolves
--album-idby local identifier first, then by album title if not found - Creates the output directory if needed
- Exports assets one at a time with a progress bar:
[=======---] 50% filename.jpg 1.2 MB cloud - Shows file size and cloud/local status for each exported asset
- Exports resized JPEG previews by default
- Exports original files when
--originalsis present - Writes a summary like
exported 10 photos to ./exportorexported 10 original files to ./exportto stderr
--size is the target bounding box passed to PhotoKit for preview export. Default: 1024.
--originals switches export mode to original-file export. In that mode, --size is ignored.
tree
- Requests Photos access
- Prints folders and albums as an indented tree
- Omits internal album IDs for human-readable output
Example output:
Trips
Italy 2024
Venice
Favorites
Permissions
On first use, macOS may prompt for Photos access.
If access is denied, the CLI returns an error telling you to grant access in:
System Settings > Privacy & Security
Export Details
Exported files currently:
- are JPEGs when exporting previews
- keep their original filenames when exporting originals when possible
- fall back to a sanitized asset identifier if an original filename is unavailable
- prefix duplicate original filenames with the asset index to avoid collisions
- name preview exports like
0000_<asset-local-identifier>.jpg - replace
/and\in asset IDs with_for generated preview filenames - replace
/and\in folder and album names with_when creating backup directory names - preserve ordering based on ascending asset creation date
If some assets fail but at least one succeeds, the command still succeeds and reports the number exported.
If all exports fail, the command returns an error.
Signal Handling
Sending SIGINT (Ctrl+C) or SIGTERM during export or backup triggers a graceful shutdown:
- The CLI prints
received signal, finishing current file...to stderr - The current file export is allowed to complete
- No further files are started
- The process exits after the in-progress file finishes
A second signal forces an immediate exit.
Architecture
cmd/photoscli: CLI entrypoint, argument parsing, and album name resolutioninternal/photos: Go bridge interface, JSON parsing, and error mappingbridge/: Objective-C PhotoKit implementation plus a C test stub
Data passed from Objective-C to Go is serialized as JSON and unmarshaled into Go structs.
Known Limitations
- The tree view only shows user collections exposed through PhotoKit's top-level user collections API
- Album title resolution matches the first album with that title; if multiple albums share a title, use the local identifier instead
photosonly prints asset IDs and filenames, not dates or metadata- Preview export uses PhotoKit preview rendering, not original file export
- Original export currently writes the first PhotoKit asset resource for each asset, which may not capture every related representation for complex assets
- iCloud-backed assets may require network download during export
- A second interrupt signal forces an immediate exit without waiting for the current file
- Partial export failures are not listed individually