Files
pigway-frame-flow/docs/PHOTO_SOURCES.md
T

218 lines
15 KiB
Markdown
Raw Permalink Normal View History

# 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
# Optional: existing NAS group allowed to read that folder (check its ACL).
# NAS_PHOTOS_GID=101
```
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. 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.
Missing host folders fail explicitly rather than being created automatically.
Open **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.
## 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.
For Synology Photos shared libraries, `synology_previews: true` allows images over
20 MiB to use their existing `@eaDir/<filename>/SYNOPHOTO_THUMB_XL.jpg` preview.
This is a read-only fallback: original files and Photos indexes remain unchanged,
no thumbnails are generated on the NAS, and the ordinary size limit still applies
to the selected preview. The catalog marks these entries `provider_preview: true`.
If the preview is missing, the oversized original remains excluded. The folder
caption still comes from the original curated directory.
## Synology Photos 人物筛选 / People filtering
可选的只读集成使用 Synology Photos **共享空间**的人物识别结果,不运行新的识别模型,也不修改人物分组或照片。个人空间暂不支持。
1. 将共享图库(如 `/volume1/photo`)挂载为 `/photos`,保持只读。
2. 在 `.env` 配置 `SYNOLOGY_PHOTOS_URL=https://NAS_IP:9115` 和 `SYNOLOGY_PHOTOS_CERT_SHA256`(服务实际 TLS 证书的 SHA-256 指纹,从可信 NAS 配置核实)。重建容器后生效。指纹变化时必须重新核实;每次连接在发送登录信息前校验同一个 TLS socket 的证书。
3. 照片数据源 → 顶部共享连接 → 连接 Synology Photos。输入有权限的 NAS 用户名、密码;只保存 session 到 `/config/synology-photos-session.json`(0600),不保存密码。会话过期后需要重新连接;断开连接删除本地 session。页面没有 password/token 回显。登录由所有人物筛选数据源共享;随后进入各数据源编辑器选择人物。
4. 选择人物,启用筛选,选择“任意”或“同时包含所有”。默认只列出已命名人物,可搜索 ID、展开未命名分组。至少选择一个人物。
5. 与当前本地目录、日期文件夹规则取交集,再随机选图。原文件夹标题、日期、地点仍保留。不匹配返回空图库;预览 / 定时任务失败,不回退到全图库。
接口:`GET/POST/DELETE /api/v1/integrations/synology-photos`(管理员);`GET /api/v1/integrations/synology-photos/people`(管理员)。Source 新增 `people_filter`、`person_ids`、`person_match: any|all`。普通图片读取权限只能使用已配置数据源,不能登录或查询人物目录。
只允许 Auth login/logout、共享空间 Person.list、Item.list、Folder.get。人物照片按 500 条分页,成员路径缓存 15 分钟,源目录缓存 60 秒;显式刷新会重新查询。无后台高频轮询,不下载全图库原图。查询失败和登录失效明确报错。大型人物相册首次查询可能需要数十秒。
This optional integration reads the existing shared-space People catalog. It does not modify Photos or perform face inference. Login stores only a protected session, never the NAS password. Certificate pinning happens before credentials are sent. Select one or more people and match any/all, intersect with the existing curated folder rules, then randomize. Empty matches and expired sessions fail closed. Person membership caches for 15 minutes; explicit refresh re-queries. This improves relevance, not blur/closed-eye detection. Recognition can miss faces. Personal-space filtering and automatic photo-quality scoring are not implemented.
## 照片方向 / Orientation
编辑照片数据源可选:横向、竖向、自动识别。自动根据目标设备尺寸和旋转方向计算画布;例如 1200×1600、rotation=270 输出 1600×1200 并选择横向。手动横向输出 1600×1200,手动竖向输出 1200×1600(以当前设备为例)。照片区和底部三行文字均随画布重排。按 EXIF 校正后的 width/height 分类,正方形不混入横向或竖向。预览、指定图片和定时随机选图使用同一筛选结果;空结果明确失败,不退回其他方向。照片不为匹配方向强制旋转或拉伸。
已有数据源 `orientation=null` 保持原来的全部方向,编辑器显示“不筛方向(原有设置)”;新建数据源默认自动。尺寸缓存持久化在 SQLite `photo_dimensions`,按路径、修改时间和大小失效。首次扫描读取本地图片尺寸和 EXIF,不批量生成全图库预览;后续刷新复用缓存。仅 NAS 本地源支持,WebDAV 不批量下载原图测尺寸。自动模式读取统一 Device Model,不依赖 KoKonna 私有字段;90°/270° 交换宽高。
Orientation options: landscape, portrait, auto. Auto follows the target device dimensions and rotation (90°/270° swap width and height). Manual direction controls both selection and rendering. Photo and caption layouts support landscape and portrait. Classification honors EXIF orientation; square photos are excluded from directional matches. Existing sources remain unfiltered (`null`) until changed. Dimensions persist in SQLite and refresh when the file changes. Local NAS sources only; no bulk original downloads from WebDAV. Empty matches fail closed. This controls selection, not photo rotation or stretching.
### Invalid photos and selection tracing
Local catalogs share the renderer's 25-million-pixel limit. Oversized originals
use an existing Synology XL preview when enabled and available; otherwise they
are excluded. Header-only catalog inspection does not guarantee full decoding.
Random rendering tries at most five distinct candidates before any device write,
skipping only invalid or unavailable images. Explicit selections never fall back.
Operation `result.photo_attempts` records source-relative item IDs, names, status
and stable error codes, including failures. This does not retry upload or switch.
### Local favorites, origins and minimal cloud storage
Stars are now stored on local assets in SQLite. The local library filters by
`origin` (`manual`, `task`, `external`, `template`, `unknown`) and `job_id`.
Task ID and name are recorded when an asset is generated; deleted tasks do not
rewrite that historical label. Older assets without reliable provenance remain
unknown. Multi-select supports star, unstar and deletion with per-item results.
Referenced assets are rejected rather than breaking pending operations/jobs.
Every new task upload is tracked by cloud ID, local asset ID and task ID. Each
task keeps its latest confirmed cloud image. Uploaded local favorites are kept
as cloud copies (one confirmed copy per local asset). New images must reach
COMPLETED with device `synced=true` before an older image can be removed. A
failed/unknown replacement leaves the previous image intact. Current, active
restore/override and playlist/explicit job references remain protected. Cleanup
checks at most once per minute and uses the existing queue, history, write guard
and UNKNOWN barrier. Deletion is never automatically retried after a failure or
unknown response. Superseded task-owned previous-image references are released
only once a replacement is confirmed; unrelated restore references are kept.
Starring a local image does not immediately upload it: KoKonna upload changes
the display. The star protects its cloud copy after a normal display upload.
Local originals remain available even if cloud cleanup removes the cloud copy.
NAS source originals are never deleted by these operations. Existing unmanaged
cloud images are not automatically swept; clearing them is an explicit action.
The previous `cleanup_unstarred` seven-day field remains accepted for API
compatibility but is superseded by the minimal-cloud policy in the scheduler.
Endpoints:
- `PUT /api/v1/assets/{asset_id}/star` with `{"starred": true}`.
- `POST /api/v1/assets/batch` with `asset_ids` (up to 100) and action
`star`, `unstar` or `delete`; per-item completion/failure results.
- `GET /api/v1/assets?origin=task&job_id=...&offset=0&limit=200`.
- Administrator `POST /api/v1/devices/{device_id}/images/clear` with
`{"confirm_current": true}` explicitly clears all listed cloud images,
including current and restore references. It returns queued operation IDs.
This is destructive, does not restore artwork, and stops at unknown writes.
The legacy cloud star route forwards to the linked local asset when available;
cloud star controls are removed from the UI. App/Synology favorites are separate.
### Camera-style watermark layout
Captioned source photos fill the photo area with an aspect-preserving cover
crop (or smart crop when requested). Legacy contain jobs use cover for this
layout; standalone image uploads still preserve their explicit fit modes.
The default white bottom band is 6% of the shorter canvas dimension (72px on
1200×1600 or 1600×1200). Title, date and location share one horizontal line.
Long text shrinks and is shortened without wrapping; source metadata is intact.
`PhotoLayoutProvider` separates layout decisions from image decoding/rendering
and device upload. The built-in `WatermarkLayout` is deterministic; an AI layout
provider is not implemented or called. Existing local/cloud images are unchanged
until a new preview or scheduled photo is generated.