# applephotos

`applephotos` 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 and filenames 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/applephotos` and calls into `internal/photos`, which wraps an Objective-C bridge in `bridge/`.

Current behavior:

- `albums` prints one line per album as `<album-id>\t<title>`
- `photos --album-id <id-or-title>` prints one asset local identifier and filename per line; accepts either a PhotoKit local identifier or an album title
- `tree` prints the human-readable folder and album hierarchy from Apple Photos
- `backup-all --out <dir> [--size <px>] [--originals]` exports every album into a matching folder tree under the output directory
- `export --album-id <id-or-title> --out <dir> [--size <px>] [--originals]` exports either JPEG previews or original files and reports the number exported on stderr; `--album-id` accepts 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 `creationDate` ascending
- 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

```bash
make build
```

Output binary:

```bash
./bin/applephotos
```

## Test

```bash
make test
```

Tests run against a stub bridge, so they do not require real Photos access.

## Usage

```bash
./bin/applephotos albums
./bin/applephotos photos --album-id "<album-local-identifier>"
./bin/applephotos photos --album-id "Vacation"
./bin/applephotos tree
./bin/applephotos backup-all --out ./backup
./bin/applephotos backup-all --out ./backup --originals
./bin/applephotos export --album-id "<album-local-identifier>" --out ./export
./bin/applephotos export --album-id "Vacation" --out ./export
./bin/applephotos export --album-id "<album-local-identifier>" --out ./export --size 2048
./bin/applephotos 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:

```text
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 for the given album

Example output:

```text
1F2A.../L0/001	IMG_0001.JPG
9C4D.../L0/001	IMG_0002.JPG
```

`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
- Exports original files when `--originals` is present
- Uses `--size` only for preview export

Example layout:

```text
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-id` by local identifier first, then by album title if not found
- Creates the output directory if needed
- Exports resized JPEG previews by default
- Exports original files when `--originals` is present
- Writes a summary like `exported 10 photos to ./export` or `exported 10 original files to ./export` to 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:

```text
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:

1. The CLI prints `received signal, finishing current file...` to stderr
2. The current file export is allowed to complete
3. No further files are started
4. The process exits after the in-progress file finishes

A second signal forces an immediate exit.

- `cmd/applephotos`: CLI entrypoint, argument parsing, and album name resolution
- `internal/photos`: Go bridge interface, JSON parsing, and error mapping
- `bridge/`: 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
- `photos` only 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
- Export is synchronous and has no progress output
- A second interrupt signal forces an immediate exit without waiting for the current file
- Partial export failures are not listed individually
