Build Pixieset

KINDAreplaces $8/mosaves $96/yrback to the verdict

0%0 of 21 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A catalogue of your local photographs that publishes a password-protected gallery to storage you own: generated previews at sensible sizes, client favouriting for proofing, download permissions per gallery, expiry dates, and originals that never leave your disk unless you decide they do.

estimated effort multi-daythe files for this build are in the project pack

CataloguePython 3.12 with SQLite, reading a local libraryPreviewslibvips through pyvipsGalleryA static site generated per gallery, uploaded to object storageAccessSigned URLs plus a gallery password, checked before any image URL existsStorageS3-compatible object storage behind a content delivery network

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the catalogue, derivative generation and publishing.

    Get it python.org or your package manager, then a virtual environment.

    Verify python3 --version prints 3.12 or higher

  2. installfree

    Why Generating thousands of derivatives with a memory-hungry library will exhaust a laptop. libvips is substantially faster and lighter for exactly this workload.

    Get it brew install vips, or your package manager, then pip install pyvips. open ↗

    Verify python -c "import pyvips; print(pyvips.version(0))" runs

  3. API keystorage is cheap; egress varies enormously by provider

    Why Galleries are opened from phones on poor connections, and photographs are large. Egress rate is the cost that matters.

    Get it Create a private bucket and credentials scoped to it, and put a delivery network in front. Compare egress pricing before choosing.

    Verify A test object uploads, is refused when requested directly, and is served through the network with a signature

  4. have readyfree

    Why Indexing time, derivative generation time and gallery weight are all meaningless at a hundred images and decisive at ten thousand.

    Get it Use your own library. If it is smaller, duplicate it to test scale before trusting the performance numbers.

    Verify The library is available and its size is recorded

  5. installfree

    Why Phase 2's claim that location metadata is stripped must be verified with a tool rather than assumed, because publishing a client's location is a real harm.

    Get it brew install exiftool, or your package manager.

    Verify It reports metadata for a source image and finds none in a derivative

  6. decidevaries with library size; this is usually a photographer's largest storage cost

    Why Originals are the irreplaceable asset and the catalogue does not contain them.

    Get it An external drive plus an offsite copy, or object storage. Decide before Phase 6, and prefer two locations to one.

    Verify A restore of one original has been performed

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- photos(id, path, taken_at, width, height, orientation, camera, content_hash) · the catalogue, pointing at originals in place
- galleries(id, name, client_name, slug, password_hash, expires_at, download_policy, published_at)
- gallery_photos(gallery_id, photo_id, position, favourited_at, comment)
- derivatives(photo_id, size, key, bytes, generated_at) · thumbnail, preview and optional full size
- downloads(gallery_id, photo_id, at, ip_hash) · so you can answer what a client actually took
- Originals are never uploaded unless the download policy explicitly allows them, and that is a per-gallery decision.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
LIBRARY_PATHrequired~/Pictures/LibraryWhere your originals live. Read only; nothing is written here.
CATALOGUE_DBrequired./data/catalogue.dbThe index over your library. Rebuildable from the library, so not the important backup.
BUCKET_NAMErequiredclient-galleriesPrivate bucket holding derivatives. Originals go here only when a gallery policy allows downloads.
BUCKET_SECRET_KEYsecretrequiredreplace-with-secretScoped to the gallery bucket only.
CDN_BASE_URLrequiredhttps://galleries.example.comWhat clients fetch from. The bucket hostname never appears in a gallery.
SIGNED_URL_TTL_SECONDSrequired3600How long an image URL lasts after the password is entered.
PREVIEW_LONG_EDGErequired2048Preview size. Large enough to look sharp, small enough to open on mobile data.

The build, in order

  1. Cataloguing what you already have

    Your photographs are indexed in place, with correct orientation and dates.

    1. Read only. The catalogue points at files in place; it never moves or rewrites them.

      Files src/catalogue.py

      terminal
      pip install pyvips
    2. From metadata, with an explicit fallback that is recorded rather than silent.

      Files src/metadata.py

    3. Content hash, so the same image in two folders is one photograph with two paths.

      Files src/catalogue.py

    done when · tick each as it passes
    watch out
    • A photograph's stored orientation is metadata, not pixels, and ignoring it means every portrait shot appears sideways in your previews while looking correct in every viewer you check it in. Apply the orientation when generating derivatives and record that you did.
  2. Derivatives that look right and are not enormous

    Previews are sharp on a retina screen and small enough to load on mobile data.

    1. Convert, resize, convert back, then sharpen. Sharpening before the resize does the wrong thing.

      Files src/derivatives.py

    2. Remove location and personal fields, keep copyright if you set it. Verify with a metadata tool rather than assuming.

      terminal
      exiftool -G -a preview.jpg | grep -i gps
    3. Total bytes for a typical gallery. This is what a client on a phone will download.

      Files src/derivatives.py

    done when · tick each as it passes
    watch out
    • Resizing in gamma-encoded space darkens images and loses fine detail, which is visible on anything with texture. Resize in linear light and convert back.
    • Metadata travels with a photograph unless you strip it, and that includes location. Publishing a client gallery with location metadata intact tells anyone who downloads an image where it was taken, which for a wedding or a family shoot is information they did not agree to share.
  3. Galleries and a gate that is real

    A client can open their gallery, and nobody else can open it or its images.

    1. A static page with responsive images and lazy loading. It should open quickly on a phone.

      Files src/gallery.pytemplates/gallery.html

    2. The password check issues short-lived signed URLs. No image URL exists before authentication.

      Files src/access.py

    3. Request an object directly, reuse an expired signature, and open an expired gallery. All three must fail.

    done when · tick each as it passes
    watch out
    • A password on the page with images at predictable public URLs is not access control, because anyone with an image URL skips the page entirely and image URLs get shared. Issue signed URLs after authentication and keep the objects private, or the password is decoration.
  4. Proofing

    A client can mark favourites and leave a note, and you get a clean list.

    1. Against the gallery, keyed by a session established at password entry.

      Files src/proofing.py

    2. Filenames and comments in a form you can hand to your editing workflow.

      Files src/export.py

    3. Either separate sessions or a shared selection, but say which in the interface rather than leaving it ambiguous.

      Files templates/gallery.html

    done when · tick each as it passes
    watch out
    • Storing selections in browser storage means a client who starts on their phone and finishes on a laptop loses half their choices and does not realise. Store selections server side against a gallery session, and let the client resume with the same password.
  5. Downloads on your terms

    Clients get exactly what you agreed to give them, and you know what they took.

    1. The policy decides which signatures are issued at all. An original URL should not exist for a previews-only gallery.

      Files src/downloads.py

    2. Build and send simultaneously so memory stays flat. Test with a large gallery and watch memory.

      Files src/zip.py

    3. Per image and per gallery, with a hashed address rather than a stored one.

      Files src/downloads.py

    done when · tick each as it passes
    watch out
    • Generating a zip of two hundred originals in memory will exhaust it and take the process down. Stream the archive as it is built, and cap the request so an unattended retry loop cannot generate the same archive repeatedly.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more photo editing.