Animated grid previews for videos and GIFs

376a05c · Gabriel Morell · 2026-09-26 04:40

43 files +453 -66
Message
{commit_body(@commit)}

Files changed

modified Dockerfile
+4 −0
@@ -52,11 +52,13 @@ RUN cargo build --release \
52 52 --bin queue-worker \
53 53 --bin registration-token \
54 54 --bin user-admin \
55 + --bin backfill-previews \
55 56 && mkdir -p /out \
56 57 && cp target/release/webservice /out/ \
57 58 && cp target/release/queue-worker /out/ \
58 59 && cp target/release/registration-token /out/ \
59 60 && cp target/release/user-admin /out/ \
61 + && cp target/release/backfill-previews /out/ \
60 62 && rm -rf target
61 63
62 64 # ---------------------------------------------------------------------------
@@ -93,6 +95,7 @@ COPY --from=rust-builder /out/webservice /usr/local/bin/dms-webservice
93 95 COPY --from=rust-builder /out/queue-worker /usr/local/bin/dms-queue-worker
94 96 COPY --from=rust-builder /out/registration-token /usr/local/bin/dms-registration-token
95 97 COPY --from=rust-builder /out/user-admin /usr/local/bin/dms-user-admin
98 +COPY --from=rust-builder /out/backfill-previews /usr/local/bin/dms-backfill-previews
96 99 COPY --from=assets /app/assets/f /app/assets/f
97 100 COPY --chown=dms:dms templates /app/templates
98 101 # OCR models bundled at a fixed image path — read-only and tied to the app
@@ -117,4 +120,5 @@ VOLUME ["/data"]
117 120 # `docker run … dms-queue-worker --kind ocr` → categorizer
118 121 # `docker run … dms-registration-token …` → CLI
119 122 # `docker run … dms-user-admin …` → user management CLI
123 +# `docker run … dms-backfill-previews` → queue grid previews for older videos/GIFs
120 124 CMD ["dms-webservice"]
modified android/README.md
+1 −1
@@ -27,7 +27,7 @@ intent filter forwards it to the pair screen with the fields pre-filled.
27 27
28 28 - QR scan → pair → token + base URL persisted (encrypted)
29 29 - `GET /api/profile` round-trip for verification
30 −- **Home grid**: paginated upload thumbnails from `/files/<path>.thumb.<ext>` with original-file fallback; videos use `.thumb.jpg` with the `.poster.jpg` frame as fallback, and carry a play badge
30 +- **Home grid**: paginated upload thumbnails from `/files/<path>.thumb.<ext>` with original-file fallback; videos and GIFs animate, showing the server's short `.preview.webp` loop (Android 9+) before falling back to `.thumb` / `.poster.jpg` / the original, and videos carry a play badge
31 31 - **Search**: free-text + tag filter (server parses `ns:value` and `ns:*` wildcards)
32 32 - **Search**: free-text query, per-source toggles (file name / item name / description / user tags / ocr / desc / other namespaces) with full parity to the web `/search` page; matched tags surface as chips on each result card
33 33 - **Detail**: full image — or, for a video, an ExoPlayer that streams it from `/files/` looping and muted (with an unmute toggle) like the web page — metadata, tag chips that pivot to a tag-filtered search, plus an "Add tags" input that writes via the new `POST /api/uploads/{id}/tags`
modified android/app/build.gradle.kts
+1 −0
@@ -106,6 +106,7 @@ dependencies {
106 106 debugImplementation(libs.compose.ui.tooling)
107 107
108 108 implementation(libs.coil.compose)
109 + implementation(libs.coil.gif)
109 110 implementation(libs.kotlinx.coroutines)
110 111
111 112 implementation(libs.media3.exoplayer)
modified android/app/src/main/AndroidManifest.xml
+1 −0
@@ -9,6 +9,7 @@
9 9 <uses-feature android:name="android.hardware.camera" android:required="false"/>
10 10
11 11 <application
12 + android:name=".DmsApplication"
12 13 android:icon="@mipmap/ic_icon"
13 14 android:roundIcon="@mipmap/ic_icon"
14 15 android:label="DMS"
added android/app/src/main/java/org/neiam/dms/app/DmsApplication.kt
+24 −0
@@ -0,0 +1,24 @@
1 +package org.neiam.dms.app
2 +
3 +import android.app.Application
4 +import android.os.Build
5 +import coil.ImageLoader
6 +import coil.ImageLoaderFactory
7 +import coil.decode.GifDecoder
8 +import coil.decode.ImageDecoderDecoder
9 +
10 +/**
11 + * Hands Coil an image loader that can animate. The grid shows each video's
12 + * and GIF's animated WebP preview; the stock loader would decode just its
13 + * first frame. The platform ImageDecoder (API 28+) plays animated WebP and
14 + * GIF; below that only GIF animates and a preview shows as a still.
15 + */
16 +class DmsApplication : Application(), ImageLoaderFactory {
17 + override fun newImageLoader(): ImageLoader =
18 + ImageLoader.Builder(this)
19 + .components {
20 + if (Build.VERSION.SDK_INT >= 28) add(ImageDecoderDecoder.Factory())
21 + else add(GifDecoder.Factory())
22 + }
23 + .build()
24 +}
modified android/app/src/main/java/org/neiam/dms/app/data/PathHelpers.kt
+16 −6

Click to load diff…

modified android/app/src/main/java/org/neiam/dms/app/ui/UploadCard.kt
+39 −22

Click to load diff…

modified android/gradle/libs.versions.toml
+1 −0

Click to load diff…

modified assets/f/js/src.js
+19 −13

Click to load diff…

modified compose.yml
+7 −0

Click to load diff…

added src/bin/backfill-previews.rs
+120 −0

Click to load diff…

modified src/bin/queue-worker.rs
+3 −2

Click to load diff…

modified src/config.rs
+6 −0

Click to load diff…

modified src/handlers/uploads.rs
+7 −2

Click to load diff…

modified src/queue/messages.rs
+13 −6

Click to load diff…

modified src/queue/mod.rs
+1 −0

Click to load diff…

modified src/workers/mod.rs
+1 −0

Click to load diff…

added src/workers/preview.rs
+170 −0

Click to load diff…

modified templates/search.html
+9 −7

Click to load diff…

modified templates/uploads/list.html
+9 −7

Click to load diff…

modified templates/uploads/show.html
+1 −0

Click to load diff…

Parents: 6f01817