feat: organizer Android app and token-gated upload API
CI / lint-and-test (push) Failing after 1m18s
CI / lint-and-test (push) Failing after 1m18s
Replace Syncthing as the product upload path with a share-target app and POST /api/jobs. Web UI live-polls job progress. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,29 +1,31 @@
|
||||
# live.f12.rocks
|
||||
|
||||
Event photo pep pipeline: guests drop a photo into a Syncthing folder, a
|
||||
worker runs it through `rembg` (background removal) + `gmic` (random
|
||||
filter/blend compositing, ported from `make_random.py`), and a small web
|
||||
UI shows the results with a "remix" option to try different filters on
|
||||
the same photo. Finished jobs sync back via the same share.
|
||||
Event photo pep pipeline: the organizer shares a photo into the Android
|
||||
app, a worker runs it through `rembg` (background removal) + `gmic`
|
||||
(random filter/blend compositing, ported from `make_random.py`), and a
|
||||
small web UI shows the results with a "remix" option. Finished jobs
|
||||
land on the phone (gallery + local notification) and on the public page.
|
||||
|
||||
No web auth. This is an event tool, not a DAM.
|
||||
No web login. Upload uses a shared token in the organizer app. This is
|
||||
an event tool, not a DAM.
|
||||
|
||||
## Services (`compose.yml`)
|
||||
|
||||
| Service | What | Port |
|
||||
|----------|--------------------------------------------------|--------------------------|
|
||||
| `worker` | Watches `incoming/`, runs the compose pipeline | none published |
|
||||
| `web` | FastAPI + Jinja UI, browse jobs, remix | via Traefik only |
|
||||
| `web` | FastAPI HTML + JSON API, remix | via Traefik only |
|
||||
|
||||
Both share one host directory (`DATA_HOST_DIR`) mounted at `/data`.
|
||||
Syncthing runs on the host — not in this compose.
|
||||
The organizer app lives in `android/`. Syncthing can still write
|
||||
`incoming/` as a fallback — it is not in this compose.
|
||||
|
||||
## Data layout (Syncthing share)
|
||||
## Data layout
|
||||
|
||||
Prod path: `/home/frank/sync.schwenk.online/data/livef12`
|
||||
|
||||
```
|
||||
incoming/ # phone drop — worker only ever reads/copies from here
|
||||
incoming/ # HTTP upload (and optional Syncthing drop)
|
||||
variants/
|
||||
{stem}_original.jpg # preprocessed private copy
|
||||
{stem}_rembg.png
|
||||
@@ -32,28 +34,38 @@ intermediates/
|
||||
{stem}_v1_bg_filtered.png
|
||||
{stem}_v1_post_0.png # …
|
||||
meta/
|
||||
{stem}.json # status + manifest for the web UI
|
||||
processed.json # worker bookkeeping: which incoming files were handled
|
||||
{stem}.json # status + stage + manifest
|
||||
processed.json # worker bookkeeping
|
||||
```
|
||||
|
||||
Phone tip: `.stignore` can drop `meta/` and `processed.json` if you only
|
||||
want images on the device.
|
||||
|
||||
`assets/` (filter lists + trimmed `filters.json`) lives in the repo and is
|
||||
bind-mounted read-only into `worker`/`web` at `/data/assets`.
|
||||
|
||||
## API
|
||||
|
||||
| Method | Path | Auth |
|
||||
|--------|------|------|
|
||||
| `GET /api/health` | `{ok, upload}` | no |
|
||||
| `GET /api/jobs` | job list | no |
|
||||
| `GET /api/jobs/{id}` | status, variants, intermediates | no |
|
||||
| `POST /api/jobs` | multipart `file` | `Authorization: Bearer <UPLOAD_TOKEN>` |
|
||||
|
||||
Empty `UPLOAD_TOKEN` disables uploads (fail closed). File download stays
|
||||
on `GET /jobs/{id}/files/{name}`.
|
||||
|
||||
## Running it
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# set UPLOAD_TOKEN (python3 -c "import secrets; print(secrets.token_urlsafe(32))")
|
||||
# local: set DATA_HOST_DIR=./data
|
||||
# prod: leave unset (defaults to the Syncthing share path)
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Drop a photo into `incoming/` (via Syncthing or locally). After a few
|
||||
seconds (poll interval + processing time) it shows up on the web UI; flat
|
||||
files land in `variants/` and `intermediates/` for the phone.
|
||||
Share a photo into the Android app, or drop one into `incoming/` locally.
|
||||
After a few seconds it shows up on the web UI.
|
||||
|
||||
Android build: [`android/README.md`](android/README.md).
|
||||
|
||||
## Development
|
||||
|
||||
@@ -71,13 +83,14 @@ pytest -q # unit tests (no gmic/rembg)
|
||||
|
||||
CI (`.gitea/workflows/ci.yml`): ruff + pytest on push/PR.
|
||||
|
||||
Architecture notes: `ARCHITECTURE.md`. Cleanup log: `CLEANUP_PLAN.md`.
|
||||
Architecture notes: `ARCHITECTURE.md`.
|
||||
|
||||
## Configuration (`.env`, see `.env.example`)
|
||||
|
||||
| Var | Default | Notes |
|
||||
|-----|---------|-------|
|
||||
| `DATA_HOST_DIR` | `/home/frank/sync.schwenk.online/data/livef12` | Syncthing share root; use `./data` locally |
|
||||
| `DATA_HOST_DIR` | `/home/frank/sync.schwenk.online/data/livef12` | data root; use `./data` locally |
|
||||
| `UPLOAD_TOKEN` | empty | required for `POST /api/jobs`; empty disables upload |
|
||||
| `OUTPUT_COUNT` | `3` | Variants generated per uploaded photo |
|
||||
| `BLEND_OPACITY` | `30%` | Default blend opacity for auto-generated variants |
|
||||
| `FILTER_TIMEOUT` | `120` | Seconds before a single gmic call is killed |
|
||||
@@ -90,17 +103,13 @@ Architecture notes: `ARCHITECTURE.md`. Cleanup log: `CLEANUP_PLAN.md`.
|
||||
|
||||
- **Firewall:** Traefik must already route `live.f12.rocks` — this repo
|
||||
only adds the router labels; it assumes the external `traefik` docker
|
||||
network exists. No SFTP port needed.
|
||||
network exists.
|
||||
- **rembg model download:** first background-removal call downloads the
|
||||
`u2net` ONNX model (~176 MB) from GitHub. This needs outbound internet
|
||||
on first run and can take a minute or two depending on the link; the
|
||||
model is cached in the `rembg_cache` named volume afterwards, so
|
||||
restarts don't re-download it.
|
||||
`u2net` ONNX model (~176 MB) from GitHub. Cached in `rembg_cache`.
|
||||
- **Incoming is append-only:** the worker only ever copies out of
|
||||
`incoming/` and never deletes or moves anything there — plan disk space
|
||||
accordingly, or clean up `incoming/` manually between events.
|
||||
`incoming/` and never deletes or moves anything there.
|
||||
- **Sequential processing:** the worker handles one photo at a time
|
||||
(`cpus: "1.0"`, no concurrency) — fine for an event pace, but a burst of
|
||||
uploads will just queue up and get processed in order.
|
||||
(`cpus: "1.0"`, no concurrency).
|
||||
- Deploy itself (`docker compose up -d` on the server) is on you — this
|
||||
repo doesn't run it automatically.
|
||||
repo doesn't run it automatically. Remember `UPLOAD_TOKEN` in the
|
||||
server `.env` or the app cannot upload.
|
||||
|
||||
Reference in New Issue
Block a user