diff --git a/Cargo.toml b/Cargo.toml
index a0203cb7..e45a2f1e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -155,6 +155,13 @@ name = "bench_thumbnails_mem"
path = "examples/bench_thumbnails_mem.rs"
required-features = ["bench"]
+# Video thumbnail benchmark — Option B (server-side ffmpeg frame → WebP). Needs
+# `ffmpeg` on PATH (libx264/libx265/libvpx-vp9 to synthesize the test corpus).
+[[example]]
+name = "bench_video_thumbnails"
+path = "examples/bench_video_thumbnails.rs"
+required-features = ["bench"]
+
# DB connection-pool tail-latency benchmark (needs the dev Postgres up).
[[example]]
name = "bench_db_pool"
diff --git a/Dockerfile b/Dockerfile
index 347f01cb..e05fcd3e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -67,9 +67,11 @@ LABEL org.opencontainers.image.title="OxiCloud" \
# Install only necessary runtime dependencies and update packages
# su-exec is needed by the entrypoint to drop privileges after fixing volume permissions.
+# ffmpeg powers server-side video thumbnail extraction (one frame → WebP pipeline);
+# without it videos simply have no thumbnail (OXICLOUD_ENABLE_VIDEO_THUMBNAILS).
# No libpq: the pure-Rust sqlx postgres driver never links it.
RUN apk --no-cache upgrade && \
- apk add --no-cache libgcc ca-certificates tzdata su-exec && \
+ apk add --no-cache libgcc ca-certificates tzdata su-exec ffmpeg && \
addgroup -g 1001 -S oxicloud && \
adduser -u 1001 -S oxicloud -G oxicloud
diff --git a/benches/VIDEO-THUMB.md b/benches/VIDEO-THUMB.md
new file mode 100644
index 00000000..9aae02e5
--- /dev/null
+++ b/benches/VIDEO-THUMB.md
@@ -0,0 +1,88 @@
+# Video thumbnails — server-side ffmpeg (Option B)
+
+Video thumbnails are now generated **server-side on upload**: a lifecycle hook
+streams the (decrypted) blob to a temp file, `ffmpeg` extracts one representative
+frame, and that frame goes through the **same WebP pipeline as photos** — so
+video thumbnails are WebP, blob-hash keyed (dedup'd), and content-negotiated,
+exactly like images.
+
+This replaces the old browser path, which only generated a thumbnail when the
+Photos grid first rendered a video tile, the `
` 404'd, and the browser
+**re-downloaded the video** to seek a frame and PUT 3 JPEGs back.
+
+## What it buys
+
+1. **Coverage incl. HEVC/iPhone.** The browser `