# 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 ``` 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 already have read/traverse permission. Frame Flow does not change NAS permissions. 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.