diff options
Diffstat (limited to 'bin/immich-fetch.sh')
| -rwxr-xr-x | bin/immich-fetch.sh | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/bin/immich-fetch.sh b/bin/immich-fetch.sh new file mode 100755 index 0000000..961cd63 --- /dev/null +++ b/bin/immich-fetch.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# Fetch today's Immich "N years ago" memories into a cache directory, for +# widgets/immich.lua. Run from conky's ${execi}: prints nothing on success and +# never blocks the dashboard, since the widget only ever reads the cache. +# +# Writes $CACHE_DIR/<asset id>.png per photo plus index.txt: +# generated <epoch> +# url <server URL> +# photo <asset id>|<memory year>|<localDateTime>|<place> +# An index with no photo lines means "no memories today", which the card shows +# differently from a missing index ("the fetch never ran"). +# +# Leaves any existing cache untouched when the server cannot be reached, rather +# than replacing good data with an error. + +set -u + +ENV_FILE="${IMMICH_ENV:-$HOME/.config/udt/immich.env}" +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/udt/immich" +INDEX="$CACHE_DIR/index.txt" + +if [ ! -r "$ENV_FILE" ]; then + echo "immich-fetch: no $ENV_FILE (copy immich.env.example)" >&2 + exit 1 +fi + +set -a +# shellcheck source=/dev/null +. "$ENV_FILE" +set +a + +if [ -z "${URL:-}" ] || [ -z "${KEY:-}" ]; then + echo "immich-fetch: URL or KEY is empty in $ENV_FILE" >&2 + exit 1 +fi + +# Photos are personal data: keep the cache private from the first byte. +umask 077 +mkdir -p "$CACHE_DIR" + +TMP="$INDEX.tmp.$$" +MEM="$CACHE_DIR/memories.json.tmp.$$" +trap 'rm -f "$TMP" "$MEM" "$CACHE_DIR"/*.part.$$' EXIT + +api() { + curl -fsS --max-time 20 -H "x-api-key: $KEY" "${URL%/}/api/$1" +} + +if ! api "memories?for=$(date +%F)" > "$MEM" 2>/dev/null \ + || ! jq -e 'type == "array"' "$MEM" > /dev/null 2>&1; then + echo "immich-fetch: memories request failed" >&2 + exit 1 +fi + +echo "generated $(date +%s)" > "$TMP" +# For the card's click-through to the photo in Immich's web UI. +echo "url ${URL%/}" >> "$TMP" + +# One line per asset: id, the memory's year, and when the photo was taken. +# localDateTime is the camera's wall clock, which is what "taken at" means; +# the UTC fileCreatedAt would shift an evening photo into the next day. +jq -r '.[] | select(.type == "on_this_day") | .data.year as $y + | .assets[] | [.id, $y, .localDateTime] | @tsv' "$MEM" | +while IFS=$'\t' read -r id year taken; do + png="$CACHE_DIR/$id.png" + if [ ! -s "$png" ]; then + # The preview is a JPEG (or WebP); cairo only loads PNG. Shrunk here + # so the widget holds a ~1MB surface rather than an 11MB one. + # -auto-orient: previews already come rotated, this is belt and braces. + if ! api "assets/$id/thumbnail?size=preview" 2>/dev/null \ + | magick - -auto-orient -resize '800x800>' "png:$png.part.$$" 2>/dev/null; then + continue + fi + mv -f "$png.part.$$" "$png" + fi + + # Location needs the key's asset.read permission. Without it this 403s and + # the place is simply empty: the card shows '--', the photo still shows. + place=$(api "assets/$id" 2>/dev/null | jq -r '.exifInfo // {} | + ([.city, .country] | map(select(. != null and . != "")) | join(", ")) as $p + | if $p != "" then $p + elif .latitude != null and .longitude != null + then "\(.latitude * 1000 | round / 1000), \(.longitude * 1000 | round / 1000)" + else "" end' 2>/dev/null) + + echo "photo $id|$year|$taken|$place" >> "$TMP" +done + +mv -f "$TMP" "$INDEX" + +# Drop photos no longer in today's memories, so the directory holds one day. +for f in "$CACHE_DIR"/*.png; do + [ -e "$f" ] || continue + id=$(basename "$f" .png) + grep -q "^photo $id|" "$INDEX" || rm -f "$f" +done |
