NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

fetch-lyrics (3966B)


      1 #!/usr/bin/env bash
      2 # rmpc on_song_change hook: fetch synced lyrics from LRCLIB when missing.
      3 #
      4 # rmpc provides (among others): $ARTIST $TITLE $ALBUM $DURATION $FILE
      5 #                               $HAS_LRC $LRC_FILE $PID $VERSION
      6 # Writes to $LRC_FILE (under lyrics_dir), then hot-indexes it via
      7 # `rmpc remote indexlrc`. Never blocks rmpc: all work happens in a
      8 # backgrounded subshell; all failures are silent (logged to cache dir).
      9 
     10 CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/rmpc-lyrics"
     11 MISS_DIR="$CACHE_DIR/misses"
     12 LOG_FILE="$CACHE_DIR/fetch.log"
     13 LRCLIB="https://lrclib.net"
     14 MISS_TTL_DAYS=14
     15 
     16 main() {
     17     mkdir -p "$MISS_DIR"
     18 
     19     # Nothing to do without metadata or if lyrics already resolved.
     20     [ "${HAS_LRC:-true}" = "false" ] || exit 0
     21     [ -n "${ARTIST:-}" ] && [ -n "${TITLE:-}" ] || exit 0
     22     [ -n "${LRC_FILE:-}" ] || exit 0
     23     [ -s "$LRC_FILE" ] && exit 0
     24 
     25     # Negative cache: don't hammer LRCLIB for known misses.
     26     local key miss
     27     key=$(printf '%s\t%s\t%s' "$ARTIST" "$TITLE" "${ALBUM:-}" | sha1sum | cut -d' ' -f1)
     28     miss="$MISS_DIR/$key"
     29     if [ -e "$miss" ] && [ -z "$(find "$miss" -mtime +"$MISS_TTL_DAYS" 2>/dev/null)" ]; then
     30         exit 0
     31     fi
     32 
     33     # Serialize concurrent hook invocations (fast track skipping).
     34     exec 9>"$CACHE_DIR/lock"
     35     flock -n 9 || exit 0
     36 
     37     # DURATION may be seconds ("312.43") or "MM:SS"; normalise to integer seconds.
     38     local dur_s=""
     39     case "${DURATION:-}" in
     40         *:*) dur_s=$(( $(echo "$DURATION" | awk -F: '{print $(NF-1)*60 + int($NF)}') )) ;;
     41         *[0-9]*) dur_s=$(printf '%.0f' "$DURATION" 2>/dev/null || true) ;;
     42     esac
     43 
     44     # Try /api/get with maximum specificity first, then progressively relax.
     45     local resp="" synced="" instrumental=""
     46     for attempt in full noalbum bare; do
     47         local args=(-sG --max-time 15 -H "Lrclib-Client: rmpc-${VERSION:-unknown}"
     48                     --data-urlencode "artist_name=$ARTIST"
     49                     --data-urlencode "track_name=$TITLE")
     50         case $attempt in
     51             full)
     52                 [ -n "${ALBUM:-}" ] && args+=(--data-urlencode "album_name=$ALBUM")
     53                 [ -n "$dur_s" ] && args+=(--data-urlencode "duration=$dur_s")
     54                 ;;
     55             noalbum)
     56                 [ -n "$dur_s" ] && args+=(--data-urlencode "duration=$dur_s")
     57                 ;;
     58         esac
     59         resp=$(curl "${args[@]}" "$LRCLIB/api/get" 2>>"$LOG_FILE") || resp=""
     60         synced=$(printf '%s' "$resp" | jq -r '.syncedLyrics // empty' 2>/dev/null)
     61         instrumental=$(printf '%s' "$resp" | jq -r '.instrumental // false' 2>/dev/null)
     62         [ -n "$synced" ] && break
     63         [ "$instrumental" = "true" ] && break
     64         sleep 1   # be polite between retries
     65     done
     66 
     67     if [ -z "$synced" ]; then
     68         # Instrumental or genuinely not found: remember the miss, stay silent.
     69         touch "$miss"
     70         printf '%s MISS %s - %s\n' "$(date -Iseconds)" "$ARTIST" "$TITLE" >>"$LOG_FILE"
     71         exit 0
     72     fi
     73 
     74     # Write the .lrc with metadata tags so rmpc's index matches it to the
     75     # song regardless of file path. UTF-8 passes through untouched.
     76     mkdir -p "$(dirname "$LRC_FILE")"
     77     local tmp
     78     tmp=$(mktemp "$(dirname "$LRC_FILE")/.fetch.XXXXXX") || exit 0
     79     {
     80         printf '[ar:%s]\n' "$ARTIST"
     81         [ -n "${ALBUM:-}" ] && printf '[al:%s]\n' "$ALBUM"
     82         printf '[ti:%s]\n' "$TITLE"
     83         printf '%s\n' "$synced" | sed -E '/^\[(ar|al|ti|length):/d'
     84     } >"$tmp"
     85     mv -f "$tmp" "$LRC_FILE"
     86     rm -f "$miss"
     87     printf '%s HIT  %s - %s -> %s\n' "$(date -Iseconds)" "$ARTIST" "$TITLE" "$LRC_FILE" >>"$LOG_FILE"
     88 
     89     # Tell the running rmpc instance about the new file (hot reload).
     90     if [ -n "${PID:-}" ]; then
     91         rmpc remote --pid "$PID" indexlrc --path "$LRC_FILE" 2>>"$LOG_FILE" || true
     92         rmpc remote --pid "$PID" status "Lyrics downloaded: $ARTIST - $TITLE" --level info 2>>"$LOG_FILE" || true
     93     fi
     94 }
     95 
     96 # Never block rmpc's hook runner.
     97 main >/dev/null 2>&1 &
     98 exit 0