# PhotoSync

> [!NOTIFY]  
> This mod is mostly generated by Claude. Bug feedbacks are welcomed and they will get
> fixed and replied by human.

A Fabric client mod that sends your Minecraft screenshots to [Immich](https://immich.app)
and lets you browse the album back inside the game. This mod also supports periodic screenshooting.

Take a screenshot with F2 and it uploads in the background. Press F6 to see what
is uploading, scroll through the album's timeline, or change how any of it
behaves. Nothing about the vanilla screenshot flow changes — F2 still saves a PNG
to `screenshots/` and still works with the mod misconfigured or offline.

Supports **Minecraft 1.20 through 26.2**, client side only. Requires
[Fabric API](https://modrinth.com/mod/fabric-api). Sinytra connector should work.

| Photo Browser | Settings |
| - | - |
| ![preview](./images/photo_preview.png) | ![settings](./images/settings.png) |

---

## Install

1. Install Fabric Loader 0.19.3 or newer and Fabric API for your Minecraft
   version.
2. Drop the jar matching your version into `mods/`:

   | Your Minecraft version | Jar |
   | --- | --- |
   | 1.20 – 1.20.1 | `photosync-1.20.1-x.y.z.jar` |
   | 1.20.2 – 1.20.4 | `photosync-1.20.4-x.y.z.jar` |
   | 1.20.5 – 1.20.6 | `photosync-1.20.6-x.y.z.jar` |
   | 1.21 – 1.21.1 | `photosync-1.21.1-x.y.z.jar` |
   | 1.21.2 – 1.21.4 | `photosync-1.21.4-x.y.z.jar` |
   | 1.21.5 | `photosync-1.21.5-x.y.z.jar` |
   | 1.21.6 – 1.21.10 | `photosync-1.21.8-x.y.z.jar` |
   | 1.21.11 – 1.21.x | `photosync-1.21.11-x.y.z.jar` |
   | 26.1 – 26.2 | `photosync-26.2-x.y.z.jar` |

   Each jar declares its own supported range, so Fabric will refuse to load the
   wrong one rather than crashing later.

3. Launch the game. PhotoSync does nothing until you connect it to a server.

The mod is client-side. Servers neither know nor care that you are running it,
and it works in single player.

## Connect it to Immich

Press **F6** (rebindable in Options → Controls → PhotoSync), open **Settings**,
and fill in two fields:

- **Server URL** — `https://photos.example.com`. Typing just `photos.example.com`
  works too; so does pasting a URL that already ends in `/api`.
- **API key** — Immich's web UI, under Account Settings → API Keys. The field is
  masked, and the key is never written to the log or copied to the clipboard.

Press **Test**. It reports the account name and server version on success, and
the actual failure on the way there — a wrong key and an unreachable host say
different things.

Then pick an **Album**. "Whole library" uploads without adding to any album; the
picker also creates one if you type a name and press Create. The chosen album is
both where uploads go and what the Browse tab shows.

Every upload carries a one-line description: the window title (which a modpack
replaces with its own name) and the server you were on, or "Singleplayer". It is
captured the moment the screenshot is taken, so an album mixed from several
clients or worlds stays tellable apart.

Settings save when you close the screen. **Revert** undoes everything since you
opened it.

## The screens

**Uploads** is the queue: everything still in flight plus the last hundred that
finished, newest first, each with its state — queued, uploading, retrying, done,
failed — and a preview of the selected one. Failed uploads get a **Retry**;
anything at all gets **Show in folder**, which opens `screenshots/` in your file
manager. **Remove** takes an entry out of the list without touching the file, and
**Clear finished** empties the completed ones in one go.

Uploads that fail retry on their own — five attempts by default, with the wait
doubling each time, so the last one comes 80 seconds after the fourth. Only the
final failure raises a corner message; the retries in between are visible here
for anyone who wants to watch them.

**Browse** is the album, newest first, with a heading per day. It loads the way a
photo website does: the per-day counts arrive before any image does, so the
scrollbar is honest about the album's real size from the first frame, and the
thumbnails for a day are fetched only when you scroll near them. Click a photo to
open it, Escape to go back. It is read-only — PhotoSync will not delete, move or
favourite anything on your server.

An opened photo is fetched at the largest size your server will serve — the
original if it has full-size renditions enabled, its 1440px preview otherwise.
The blur you see for the moment before it arrives is the thumbhash the timeline
already carried, and it says so while it is standing in, so a blur is never
mistaken for the photo.

Videos in the album show their preview frame with a video badge in the corner.
There is no playback; this is a screenshot mod.

**Settings** covers everything below.

## Automatic screenshots

Off by default, and it stays off until you turn it on. Once enabled, PhotoSync
takes a screenshot every five minutes (adjustable from 5 seconds to an hour) and
puts it through the same queue as a manual one.

- **Name suffix** — appended before `.png`, `_auto` by default, so an automatic
  capture lands as `2026-08-07_14.31.02_auto.png` and is easy to spot or filter.
  Leave it blank for none.
- **Only while in a world** — on by default. No point photographing the main menu.
- **Skip while a screen is open** — on by default. Your inventory is not a
  landscape.
- **Skip if you have not moved** — on by default. A cycle is skipped when the
  camera has been in the same place, looking the same way, for a whole interval:
  that shot would be the previous one again. It resumes on its own the moment you
  move or look somewhere else, so an afk stretch costs you one photo rather than
  a hundred identical ones. The view is judged from the camera, so following
  someone in spectator mode counts as moving.

Automatic captures are recorded separately from manual ones, so the two are
distinguishable everywhere they appear.

## Messages in the corner

One line, bottom left, gone in three seconds. Separately switchable for
screenshot taken, upload finished, and upload failed, with an adjustable
duration — the setting that suits an automatic capture every thirty seconds is
not the one that suits two screenshots an hour.

These draw on the in-world HUD, so they do not appear on the title screen or on
top of an open screen. An upload that finishes while you are looking at the
Uploads tab updates the list instead, which is better feedback than a toast.

## Quitting with uploads still running

If you quit while something is uploading, PhotoSync stops the shutdown and shows
a progress dialog: how many are left, and two buttons. **Keep playing** returns
you to the game. **Quit anyway** quits immediately.

Either way nothing is lost. The queue is on disk and is written through on every
state change, so unfinished uploads resume the next time you play — the dialog
exists to save you the wait, not to prevent a loss. An upload that was mid-flight
when the game died is simply tried again, and Immich recognises the retry as a
duplicate rather than storing a second copy.

You can turn the dialog off under Settings → Uploads → *Ask before quitting
mid-upload*, in which case the game gets a three-second grace period to finish
whatever is nearly done and then quits regardless.

## Everything else in Settings

| Setting | Default | |
| --- | --- | --- |
| Upload screenshots automatically | on | Off makes the mod a browser and a manual queue |
| Uploads at once | 2 | Up to 4 |
| Attempts before giving up | 5 | Up to 20 |
| Wait between attempts | 5s | Doubles each attempt: 5s, 10s, 20s, 40s |
| Delete the local file after upload | off | Only ever after the server confirms it |
| Thumbnail size | 96px | 48–192 |
| Thumbnails kept in memory | 256 | The mod's largest memory knob |
| Mark videos in the grid | on | |

## Files it writes

By default everything lives in `config/photosync/` in your game directory:

- `photosync.json` — settings, including your API key. Back it up accordingly.
- `uploads.json` — the upload queue, so it survives a crash or a quit.

Turn on *Store settings outside the game folder* in Settings → Storage and
`photosync.json` moves to `~/.config/photosyncmod/` on Linux and macOS, or
`%APPDATA%\photosyncmod\` on Windows, shared across your Minecraft installs.
The game folder keeps `uploads.json` and a small `location.json` pointer that
says where the settings file went.

Screenshots themselves go where they always did, in `screenshots/`. PhotoSync
never moves them, and only deletes one if you asked it to and the upload was
confirmed.

## Troubleshooting

**"Not connected" on the Browse tab** — the server URL or API key is missing or
wrong. Settings → Test says which.

**Screenshots are taken but nothing queues** — either *Upload screenshots
automatically* is off, or there are no credentials yet. PhotoSync deliberately
does not queue against a server it cannot reach, because the result is a list of
failures you cannot act on.

**Thumbnails are blank in Browse** — Minecraft's image decoder reads PNG and JPEG
only, and Immich's default thumbnail format is WebP. PhotoSync notices on the
first tile and asks for JPEG previews instead for the rest of the session, so
this self-corrects on its own.

**An opened photo says it could not load** — close it and open it again, which
retries. If it fails every time, the log line says why; the usual cause is
Administration → Settings → Image → Preview format set to WebP on the server,
which nothing on this side can decode. Set it to JPEG.

**A jar refuses to load** — check the table above. Fabric enforces each jar's
declared version range, so this means the jar is for a different bucket.

## Building from source

```sh
./gradlew buildAllPlatforms
```

Jars land in `platform/<version>/build/libs/`. Nothing is shaded or bundled —
each jar is around 270 KB and contains only this mod's classes. To run one
version in a dev client:

```sh
./gradlew :platform:1.21.11:runClient
```

Releases are built and published by `.github/workflows/release.yml`: push a
`v<version>` tag (which must match `mod_version` in `gradle.properties`), or run
the workflow by hand and it builds the current commit and releases it under the
`mod_version` tag.

The source tree is one shared implementation plus nine thin per-version
adapters. If you are adding a Minecraft version or wondering why the layout is
the way it is, [`docs/PORTING.md`](docs/PORTING.md) is the document for that: it
covers the bucket scheme, the measured API breakpoints between every supported
version, and the procedure for a new release.

Adding a photo service other than Immich means implementing `PhotoProvider` and
`ProviderFactory` in `shared/core` and registering it with `ProviderCatalog`. No
UI or platform code needs to change — the settings screen builds its connection
fields from what the provider declares.

## Licence

MIT.
