2026-10-05 00:52:39 +08:00
|
|
|
|
# Photo sources
|
|
|
|
|
|
|
|
|
|
|
|
Photo sources are independent read-only providers. They never call a frame driver.
|
|
|
|
|
|
SQLite persists source definitions and Scheduler jobs; passwords are not stored there.
|
|
|
|
|
|
The shared DisplayService handles fitting, upload, idempotency, overrides and restore.
|
|
|
|
|
|
|
|
|
|
|
|
## NAS local folder
|
|
|
|
|
|
|
|
|
|
|
|
Add the actual existing photo directory to `.env`:
|
|
|
|
|
|
|
|
|
|
|
|
```dotenv
|
|
|
|
|
|
NAS_PHOTOS_DIR=/volume2/photo
|
2026-10-05 07:59:48 +08:00
|
|
|
|
# Optional: existing NAS group allowed to read that folder (check its ACL).
|
|
|
|
|
|
# NAS_PHOTOS_GID=101
|
2026-10-05 00:52:39 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The path above is an example, not a detected directory. Enable the optional mount:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
docker compose -f docker-compose.yml -f deploy/docker-compose.photos.yml config -q
|
|
|
|
|
|
docker compose -f docker-compose.yml -f deploy/docker-compose.photos.yml up -d
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The folder is mounted **read-only** at `/photos`. The existing container user must
|
2026-10-05 07:59:48 +08:00
|
|
|
|
already have read/traverse permission. If the Synology ACL grants a group access,
|
|
|
|
|
|
set `NAS_PHOTOS_GID` to that existing group ID; the optional Compose overlay adds
|
|
|
|
|
|
only container group membership, keeping UID 10333 and the photo bind mount read-only.
|
|
|
|
|
|
Group 101 is a deployment example, not a universal default. Frame Flow does not
|
|
|
|
|
|
change NAS permissions. Unreadable roots return an explicit HTTP 403 rather than
|
|
|
|
|
|
an empty catalog.
|
2026-10-05 00:52:39 +08:00
|
|
|
|
Missing host folders fail explicitly rather than being created automatically.
|
|
|
|
|
|
|
|
|
|
|
|
Open **Gallery → Photo sources → Add source**, choose **NAS local folder**,
|
|
|
|
|
|
enter a relative folder (`.` for root or `family` for a subfolder).
|
|
|
|
|
|
Supported formats: JPEG, PNG, WebP. Optional recursion excludes hidden directories,
|
|
|
|
|
|
Synology `@eaDir` thumbnail trees, and symlinks. Original files remain unchanged.
|
|
|
|
|
|
|
|
|
|
|
|
## WebDAV
|
|
|
|
|
|
|
|
|
|
|
|
Open Settings and allow the server hostname under Image URL hosts.
|
|
|
|
|
|
Add a WebDAV source using a plain HTTPS directory URL on port 443.
|
|
|
|
|
|
The implementation uses read-only `PROPFIND Depth: 1` and `GET`, following
|
|
|
|
|
|
[RFC 4918](https://www.rfc-editor.org/rfc/rfc4918#section-9.1).
|
|
|
|
|
|
This version lists images immediately inside one folder; recursive WebDAV traversal
|
|
|
|
|
|
is not supported. Basic authentication is supported; OAuth, SSO and Digest are not.
|
|
|
|
|
|
Redirects, private-address endpoints and URL-embedded credentials are rejected.
|
|
|
|
|
|
|
|
|
|
|
|
For authentication, reference an environment variable such as
|
|
|
|
|
|
`FRAME_FLOW_SOURCE_CLOUD`; set its value to a JSON object containing `username`
|
|
|
|
|
|
and `password`, or set `FRAME_FLOW_SOURCE_CLOUD_FILE` to a mounted Secret file.
|
|
|
|
|
|
Compose's env_file supplies the variable; a Secret file belongs in the existing
|
|
|
|
|
|
`/config` mount with restricted permissions and container read access. Never commit
|
|
|
|
|
|
credentials. The UI/API stores only the variable name.
|
|
|
|
|
|
|
|
|
|
|
|
## Use
|
|
|
|
|
|
|
|
|
|
|
|
- **Browse photos** returns paginated metadata (50 items/page), without fetching originals.
|
|
|
|
|
|
- **Preview random photo** imports one selected photo into local Frame Flow storage;
|
|
|
|
|
|
the frame remains unchanged. Display uses that exact previewed asset.
|
|
|
|
|
|
- **Schedule random photos** creates a device-bound Cron job, paused by default.
|
|
|
|
|
|
Each execution picks a random photo; ordinary random selection can repeat.
|
|
|
|
|
|
Editing the job supports other schedule types, fitting/profile via advanced JSON,
|
|
|
|
|
|
and the existing temporary override/restore options.
|
|
|
|
|
|
- Sources can be edited, disabled or removed. Remove referencing jobs before deletion.
|
|
|
|
|
|
|
|
|
|
|
|
Metadata is cached for 60 seconds, with explicit refresh. Each catalog is limited
|
|
|
|
|
|
to 10,000 supported images (not a complete inventory for larger folders).
|
|
|
|
|
|
Maximum selected image size is 20 MiB; WebDAV XML responses are limited to 2 MiB.
|
|
|
|
|
|
Preview imports use the existing local asset store; monitor disk space over time.
|
|
|
|
|
|
Unreadable/invalid images fail the operation without retrying frame writes.
|
|
|
|
|
|
|
|
|
|
|
|
## API
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /api/v1/photo-sources`
|
|
|
|
|
|
- `PUT /api/v1/photo-sources/{id}` (admin)
|
|
|
|
|
|
- `DELETE /api/v1/photo-sources/{id}` (admin)
|
|
|
|
|
|
- `GET /api/v1/photo-sources/{id}/images?offset=0&limit=50&refresh=false`
|
|
|
|
|
|
|
|
|
|
|
|
Display or Scheduler content:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"type":"photo_source","source_id":"family","fit":"contain","profile":"color"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Optional `item_id` selects an exact catalog item. Omit it for random selection.
|
|
|
|
|
|
The device upload endpoint accepts the same content with `display_now=false`
|
|
|
|
|
|
(default) to import/preview locally. `POST /api/v1/display` performs the usual
|
|
|
|
|
|
protected asynchronous device display. Schemas are in `/openapi.json` and `/docs`.
|
|
|
|
|
|
|
|
|
|
|
|
Native Aliyun Drive/Baidu Drive/Google Drive adapters are not implemented.
|
|
|
|
|
|
A compatible WebDAV gateway or an independently managed NAS sync folder can be used.
|
|
|
|
|
|
Frame Flow does not install a gateway or receive vendor API credentials automatically.
|
2026-10-05 01:36:05 +08:00
|
|
|
|
|
|
|
|
|
|
## Date-folder captions / 整理照片文字
|
|
|
|
|
|
|
|
|
|
|
|
Enable **Only photos in date-prefixed folders** and **Show folder caption below
|
|
|
|
|
|
the photo** when editing a source. Curated sources created by the UI increase the
|
|
|
|
|
|
catalog limit to 100,000 photos; other sources default to 10,000. The API exposes
|
|
|
|
|
|
`catalog_limit` explicitly. It remains a bounded catalog; reaching the limit is
|
|
|
|
|
|
shown in the UI.
|
|
|
|
|
|
|
|
|
|
|
|
Example folder name: `20261005 家庭出游@示例地点`.
|
|
|
|
|
|
The image contains the photo and three separate caption lines:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
家庭出游
|
|
|
|
|
|
2026-10-05
|
|
|
|
|
|
示例地点
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Without `@`, the location line is omitted. Folder names support `YYYYMMDD`,
|
|
|
|
|
|
`YYYY-MM-DD`, `YYYY.MM.DD`, and `YYYY年MM月DD日`, with calendar-valid dates.
|
|
|
|
|
|
Subfolders inherit their nearest dated ancestor; image filenames and EXIF do not
|
|
|
|
|
|
replace this curated date. Synology `#recycle`, hidden folders, and `@eaDir` are
|
|
|
|
|
|
excluded. Extremely long captions shrink and then truncate visually; full values
|
|
|
|
|
|
remain available in catalog metadata.
|
|
|
|
|
|
|
|
|
|
|
|
Output is 1200×1600: a 1200×1280 photo area plus a white caption area. Original
|
|
|
|
|
|
files remain unchanged. Contain/cover/smart and color/grayscale profiles still apply.
|
|
|
|
|
|
A prepared source definition is in `deploy/organized-photos-source.json`; use it
|
|
|
|
|
|
with `PUT /api/v1/photo-sources/organized-photos` after mounting the library.
|