mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:27:02 +01:00
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Replace dufs's plain file list with a React 19 + Vite + TypeScript SPA served same-origin by dufs, so the cloud URL becomes a real gallery behind the existing auth: thumbnail grid, image lightbox (zoom + keyboard nav), inline video, drag/ pick upload, download, confirm-guarded delete, and a simple .txt/.md editor. Serving architecture (de-risked against the real dufs 0.46.0): --assets does not override the UI, so dufs `render-spa` serves the app shell, the SPA lists dirs via WebDAV PROPFIND (render-flag-immune) and streams files via GET (Range for video seeking), with HashRouter so navigation stays at "/". Verified render-spa leaves KeePass file GET/PUT/?hash intact (Phase 1 unaffected). Adds generate_thumbnails.sh (ImageMagick images + ffmpeg video posters → ~/cloud/.thumbs, idempotent) and setup_cloud_gallery.sh (build + deploy the SPA into the cloud root, enable render-spa, wire thumbnail refresh into the Phase-2 media sync). setup_dufs_cloud.sh now emits render-spa only when the SPA is deployed, so a fresh dufs keeps its default listing. Verified: pnpm build + lint (strictTypeChecked) clean; 38 vitest tests, coverage 92.8% stmts / 96% lines (dufs-client 100%); local dufs --render-spa serves shell + assets + thumbnails + PROPFIND (all 200). Live deploy left for the user (needs sudo; enables render-spa on the public service). Note: host ffmpeg is broken (libvpx.so.11) so video posters are skipped until it's fixed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
86 lines
3.1 KiB
Bash
Executable File
86 lines
3.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# generate_thumbnails.sh — make small preview thumbnails for the cloud media so
|
|
# the web gallery (cloud_gallery) loads fast instead of downloading full files.
|
|
#
|
|
# For every image/video under the dufs cloud it writes a ~400px JPEG to
|
|
# $CLOUD_ROOT/.thumbs/<same-path>.jpg
|
|
# which the gallery requests as `/.thumbs/<path>.jpg`. Images are resized with
|
|
# ImageMagick; videos get a poster frame via ffmpeg. Idempotent: a thumbnail
|
|
# newer than its source is skipped. flock-guarded, self-contained.
|
|
#
|
|
# CLOUD_ROOT comes from the dufs serve-path (~/.config/dufs/dufs.yaml), else
|
|
# ~/cloud. Usage: generate_thumbnails.sh [ROOT_DIR]
|
|
|
|
set -euo pipefail
|
|
|
|
readonly THUMB_MAX=400
|
|
|
|
log() { printf '[thumbnails] %s\n' "$*" >&2; }
|
|
|
|
CLOUD_ROOT="${CLOUD_ROOT:-}"
|
|
if [[ -z "$CLOUD_ROOT" && -f "$HOME/.config/dufs/dufs.yaml" ]]; then
|
|
CLOUD_ROOT="$(sed -nE 's/^serve-path:[[:space:]]*//p' "$HOME/.config/dufs/dufs.yaml" | head -1)"
|
|
fi
|
|
CLOUD_ROOT="${CLOUD_ROOT:-$HOME/cloud}"
|
|
ROOT="${1:-$CLOUD_ROOT}"
|
|
readonly THUMBS="$CLOUD_ROOT/.thumbs"
|
|
|
|
FD="$(command -v fd || command -v fdfind || true)"
|
|
[[ -n "$FD" ]] || { log "ERROR: fd (fd-find) not installed"; exit 1; }
|
|
command -v ffmpeg >/dev/null || { log "ERROR: ffmpeg not installed"; exit 1; }
|
|
MAGICK="$(command -v magick || command -v convert || true)"
|
|
[[ -n "$MAGICK" ]] || { log "ERROR: ImageMagick (magick/convert) not installed"; exit 1; }
|
|
|
|
readonly IMG_EXTS=(jpg jpeg png gif bmp tiff tif webp heic heif avif)
|
|
readonly VID_EXTS=(mp4 avi mkv mov wmv flv webm m4v 3gp ogv mpg mpeg mts m2ts vob)
|
|
FD_ARGS=()
|
|
for e in "${IMG_EXTS[@]}" "${VID_EXTS[@]}"; do FD_ARGS+=(-e "$e"); done
|
|
|
|
STATE_DIR="${THUMB_STATE:-$HOME/.local/state/media-cloud-sync}"
|
|
mkdir -p "$STATE_DIR" "$THUMBS"
|
|
exec 9>"$STATE_DIR/thumbs.lock"
|
|
flock -n 9 || { log "another thumbnail run is in progress — skipping"; exit 0; }
|
|
|
|
is_video() {
|
|
local ext="${1##*.}"
|
|
ext="${ext,,}"
|
|
for v in "${VID_EXTS[@]}"; do [[ "$ext" == "$v" ]] && return 0; done
|
|
return 1
|
|
}
|
|
|
|
made=0 skipped=0 failed=0
|
|
while IFS= read -r src; do
|
|
[[ -f "$src" ]] || continue
|
|
# Thumbnail path mirrors the file's path under the cloud root.
|
|
rel="${src#"$CLOUD_ROOT"}"
|
|
dst="$THUMBS${rel}.jpg"
|
|
if [[ -f "$dst" && "$dst" -nt "$src" ]]; then
|
|
skipped=$((skipped + 1)); continue
|
|
fi
|
|
mkdir -p "$(dirname "$dst")"
|
|
if is_video "$src"; then
|
|
if ffmpeg -y -loglevel error -ss 1 -i "$src" -frames:v 1 \
|
|
-vf "scale=${THUMB_MAX}:-2:force_original_aspect_ratio=decrease" \
|
|
"$dst" </dev/null 2>/dev/null; then
|
|
made=$((made + 1))
|
|
else
|
|
# Fall back to the very first frame (videos shorter than 1s).
|
|
if ffmpeg -y -loglevel error -i "$src" -frames:v 1 \
|
|
-vf "scale=${THUMB_MAX}:-2" "$dst" </dev/null 2>/dev/null; then
|
|
made=$((made + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
fi
|
|
else
|
|
if "$MAGICK" "${src}[0]" -auto-orient -thumbnail "${THUMB_MAX}x${THUMB_MAX}>" \
|
|
-strip "$dst" 2>/dev/null; then
|
|
made=$((made + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
fi
|
|
done < <("$FD" "${FD_ARGS[@]}" --type f --exclude '.thumbs' --exclude '_thumbs' . "$ROOT" 2>/dev/null || true)
|
|
|
|
log "done: $made made, $skipped current, $failed failed → $THUMBS"
|