juno.photos

Pick photos from the user’s library and save images to the camera roll.

Two surfaces:

  • pick() opens iOS’s system photo picker (PHPickerViewController) and returns the user’s selection as a list of Photo records. The picker runs out-of-process and does not require photo library authorization — the user’s selection is delivered to the app, not the library itself.

  • save() writes encoded image bytes to the camera roll. Needs add-only photo library authorization (NSPhotoLibraryAddUsageDescription).

Authorization helpers report write (add-only) permission status — pick() is unaffected by them. Authorization waits are polled from Python so Juno’s Stop button can interrupt the prompt via KeyboardInterrupt (same pattern as juno.location / juno.contacts / juno.reminders).

Typical usage:

from juno import photos

# Picker — no permission required
selection = photos.pick(limit=3)
for photo in selection:
    print(photo.filename, photo.mime_type, len(photo.data))

# Save — requires add-only authorization
if photos.request_authorization() == photos.AuthorizationStatus.AUTHORIZED:
    with open("/path/to/sunset.jpg", "rb") as f:
        photos.save(f.read(), format="jpeg")

Notes:

  • limited authorization (iOS 14+) reports via authorization_status() but is_authorized() returns False — every save under limited access would re-prompt the user to pick which assets the app may see, which is not what most scripts expect.

  • The picker runs as a separate process; large selections may take several seconds to deliver bytes back. Stop interrupts the wait cleanly.

class juno.photos.AuthorizationStatus

Bases: object

String constants returned by authorization_status().

class juno.photos.Photo(data, mime_type='application/octet-stream', filename='', width=0, height=0)

Bases: object

A single photo picked from the user’s library.

data is the raw encoded bytes — typically JPEG or HEIC, matching the on-disk asset. mime_type is a best-effort string derived from the picker’s registered type identifiers ("image/jpeg", "image/heic", "image/png", "application/octet-stream" if the type is unknown). filename is the suggested name from the picker (often empty for assets without one). width / height are pixel dimensions; 0 if decoding the data didn’t yield a valid image.

Parameters:
juno.photos.authorization_status()

Return the current photo-library add-only authorization status.

Returns:

One of the AuthorizationStatus string constants.

Return type:

str

Note

This reports write (add-only) permission. pick() does not depend on it — the system picker runs out-of-process and delivers selected assets without any library authorization.

juno.photos.is_authorized()

Return True if the app has full add-only photo-library authorization.

iOS 14 limited access returns False — under limited access every save re-prompts the user, which is not what scripts typically expect.

Return type:

bool

juno.photos.request_authorization()

Request photo-library add-only authorization.

Triggers the system permission prompt if status is notDetermined; returns the existing status otherwise. Polls authorization_status() from Python so the Stop button can interrupt the wait via KeyboardInterrupt.

Returns:

Resolved authorization status, one of the AuthorizationStatus constants.

Return type:

str

juno.photos.pick(*, limit=1)

Open the system photo picker and return the selection.

Parameters:

limit (int) – Maximum number of photos the user can pick. 0 means no limit (the system caps the actual cap). Must be a non-negative integer.

Returns:

A list of Photo records, possibly empty (the user dismissed the picker without selecting anything).

Raises:
  • TypeError – If limit is not an int.

  • ValueError – If limit is negative.

  • RuntimeError – If the picker presentation failed (no top-level view controller, system error, etc.).

Return type:

list[Photo]

Note

pick does not require photo-library authorization — the PHPickerViewController runs out-of-process and the user’s selection is delivered to the app directly. Stop cleanly unwinds the wait while the picker is on screen.

juno.photos.save(data, *, format='jpeg')

Save encoded image bytes to the camera roll.

Parameters:
  • data (bytes | bytearray | memoryview) – Encoded image bytes (JPEG / PNG / HEIC). Empty buffers are rejected before reaching the system.

  • format (str) – Informational hint about the byte format. One of "jpeg" / "png" / "heic".

Returns:

True on success; False on save failure (system rejected the bytes, write was cancelled, etc.).

Raises:
  • TypeError – If data is not a bytes-like object, or format is not a string.

  • ValueError – If data is empty, or format is not one of the accepted values.

  • PermissionError – If add-only photo authorization is denied, restricted, or limited.

Return type:

bool