PLAN.md: previews on the Files tab, shipped as v0.1.0-200
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
f8c9479e93
commit
51c718e8e3
@@ -625,6 +625,57 @@ something long in one, switch away and back, refresh — it's all still there.
|
||||
so both modes share one implementation and one set of rules (absolute paths only;
|
||||
`/`, `/etc`, `/usr`, … are never deleted or moved whole). Every write is logged with
|
||||
the caller's IP.
|
||||
- **Previews on the Files tab** — **shipped 2026-10-11** (v0.1.0-200, `internal/thumbs`,
|
||||
`internal/api/thumbs.go`, `Thumb`/`ThumbNote` in `Files.tsx`). Asked for as a button that
|
||||
toggles a file preview, "obviously not for everything", cached, "use whatever GPU is on the
|
||||
machine we're looking at, and fall back to CPU", "a single small frame preview so it's
|
||||
fast". A **previews** button beside `.hidden` turns the icon column into a 256 px
|
||||
thumbnail for every image and video row (the listing says which: `preview: image|video`
|
||||
per entry, decided by extension in one place, `thumbs.Kind`), loaded as rows scroll within
|
||||
400 px of view — the list scrolls in a box of its own, so it has its own
|
||||
IntersectionObserver rather than the browser's window-based lazy loading. A video thumb
|
||||
carries a ▶ badge. The setting is in localStorage and follows the browser.
|
||||
- **ffmpeg is the one engine**, for images and video alike; `GET /api/files/thumb` is one
|
||||
ffprobe and one ffmpeg: `-ss` a tenth of the way in (30 s at most, `-noaccurate_seek`
|
||||
so one keyframe is decoded), the first real video stream (cover art is skipped), one
|
||||
frame, `scale` to fit 256, MJPEG q4 to a pipe. JPEGs decode at a reduced DCT size
|
||||
(`-lowres`, enough halvings to stay at twice the preview) — a 24 MP photo is read as
|
||||
1.5 MP. Thumbnails are the only thing Ham Lab asks a GPU to do, and it asks the way the
|
||||
Processes tab reads one: **probed, never assumed.** The probe looks ffmpeg and ffprobe
|
||||
up, opens every `/dev/dri/renderD*`, pushes a synthetic frame through each with VAAPI,
|
||||
and reads `vainfo`'s decode profiles per node where it exists; CUDA is tried the same
|
||||
way when `/dev/nvidiactl` is there (untested: nothing here has one). A video goes to the
|
||||
first GPU listing its codec; ffmpeg's own software fallback sits behind that for a
|
||||
profile the list did not distinguish, and a CPU retry behind a hardware run that fails
|
||||
outright. Images are CPU only — one frame through libjpeg at a quarter size beats a
|
||||
round trip through a GPU. Measured on `grid`: 1080p h264 0.4 s on the UHD 750 against
|
||||
0.7 s on the CPU; the 8K Osmo HEVC 1.0 s against 2.9 s. `grid`'s two GPUs are listed in
|
||||
node order and both decode the same set (h264, hevc, vp9, mjpeg, mpeg2 — **no AV1 on
|
||||
either**, per vainfo, so AV1 is dav1d on the CPU).
|
||||
- **Cached under the machine's data dir**, `<data dir>/thumbs/<xx>/<sha256>.jpg`, keyed by
|
||||
path, size and mtime — a changed file gets a new preview, an unchanged one is never
|
||||
decoded twice, and the URL carries `v=<mtime>-<size>` so the browser's own cache is
|
||||
right too (7 days, ETag, 304). A file ffmpeg could not decode is remembered beside the
|
||||
JPEGs as `.err` with ffmpeg's reason, answered as 422, and retried only when the file
|
||||
changes; a timeout is not remembered. Nothing expires: a preview is a few KB, and only
|
||||
files somebody scrolled past are ever made. At most 2–4 ffmpegs at once per machine
|
||||
(half the cores, clamped), one per file however many rows ask, and a preview's work
|
||||
runs on a clock of its own so the row that asked first leaving does not cancel it for
|
||||
the rest. Each ffmpeg runs under `nice -n 10`. `hamlet`'s backup job skips the folder.
|
||||
- **Root mode makes them as root** (`hamlab thumb`, the sibling of `hamlab fs`, caps on
|
||||
stdin, JSON out), so a file only root can read gets a preview, and the probe runs as
|
||||
root too — root opens every render node whatever groups the daemon has. Which matters:
|
||||
a render node is `root:render 0660`, and on `core` and `hamtv` the `core` user is not in
|
||||
`render`, so there the daemon's own previews are CPU and the tab says so, naming the GPU
|
||||
and the owner, with an **Allow GPU access** button (confirms, then a task writes
|
||||
`hamlab.service.d/gpu.conf` adding `render`/`video` as supplementary groups and
|
||||
restarts Ham Lab three seconds later, the shape the Incus install uses for
|
||||
`incus-admin`). Not pressed anywhere yet. Where ffmpeg is missing (the Mac mini, `gate`)
|
||||
the note offers **Install ffmpeg with dnf/apk** through the Updates machinery
|
||||
(`ffmpeg-free` on Fedora, `ffmpeg` elsewhere); the status re-probes when the task ends.
|
||||
- *Not done, on purpose:* no cap on the cache and no clear button (not asked; it only
|
||||
holds what was looked at); no tonemapping for HDR sources (a 10-bit HEVC frame comes out
|
||||
flat); no preview in the viewer for a video (it still says "binary, download it").
|
||||
- **Docker read-only view** — `docker ps`/logs; dockhand stays the source of truth.
|
||||
- **Notifications** — push on service failure / SMART warning / UPS event.
|
||||
- **Terminal extras** — search, copy-mode, font/theme, split panes, upload via
|
||||
@@ -732,8 +783,9 @@ what it has moved.
|
||||
- **Resumable uploads.** copyparty's up2k chunks a file, survives a dropped connection
|
||||
and skips what is already there; Files streams one PUT, so a phone upload that drops at
|
||||
90% starts over.
|
||||
- **A gallery.** Thumbnails for a folder of images and video, and play audio/video in
|
||||
place — `/tank/media` and `/tank/immich` are most of what is browsed.
|
||||
- **A gallery.** ~~Thumbnails for a folder of images and video~~ (the **previews** toggle,
|
||||
2026-10-11 — see the shipped entry above; a grid view is not built), and play
|
||||
audio/video in place — `/tank/media` and `/tank/immich` are most of what is browsed.
|
||||
- **Search** below the current folder by name, since `find` in a terminal is the fallback
|
||||
today.
|
||||
- *Decide when built:* whether share links (copyparty's public per-path links) are wanted
|
||||
|
||||
Reference in New Issue
Block a user