]> git.draconx.ca Git - mpdhacks.git/blobdiff - mpdthumb.sh
mpdthumb: Fix failure when readpicture/albumart both return data.
[mpdhacks.git] / mpdthumb.sh
index 30f30a16ff6440a8248bb7922d15fd6ebdc971fe..e87f8370f72ab0f8673e4b21edc42218c7d61ff3 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-# Copyright © 2019-2020 Nick Bowler
+# Copyright © 2019-2022 Nick Bowler
 #
 # Generate thumbnails for cover art retrieved from MPD.
 #
 : "${THUMBNAILDIR=$XDG_CACHE_HOME/mpdthumb}"
 
 # Try to find mpdexec...
-case $0 in
-/*) self=$0 ;;
-*/*) self=$PWD/${0#./} ;;
-*) self=`command -v $0` ;;
+argv0=$0
+case $argv0 in
+/*) self=$argv0 ;;
+*/*) self=$PWD/${argv0#./} ;;
+*) self=`command -v $argv0` ;;
 esac
 owndir=${self%/*}
 
@@ -33,8 +34,65 @@ else
   : "${CONVERT=convert}"
 fi
 
-size=x128
+print_version () {
+  cat <<'EOF'
+mpdthumb.sh 0.9
+Copyright © 2021 Nick Bowler
+License GPLv3+: GNU General Public License version 3 or any later version.
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law.
+EOF
+}
+
+print_usage () {
+  test $# -eq 0 || { printf '%s: ' "$argv0"; printf "$@"; }
+  printf 'Usage: %s [options] file [file ...]\n' "$argv0"
+  test $# -eq 0 || printf 'Try %s --help for more information.\n' "$argv0"
+}
+
+print_help () {
+  print_usage
+  cat <<EOF
+This is "mpdthumb": a tool to retrieve cover art thumbnails from MPD.
+
+One or more filenames may be specified.  The cover art (if any) for each
+file is retrieved and scaled to the requested size.  The thumbnails are
+cached locally so that subsequent lookups return results very quickly.
+
+The filenames of the generated thumbnails are printed one per line in
+the same order of the associated files specified on the command line.
+If a given file has no cover art, then a blank line is printed.
+
+Options:
+  --size=[W][xH]    Specify the dimensions (in pixels) of a bounding
+                    rectangle into which the image will be scaled to fit.
+                    One (but not both) of the dimensions may be omitted
+                    to indicate no limit in that direction.  The default
+                    size is x128.
+  --small           Equivalent to --size=56
+  --binarylimit[=ARG]
+                    If ARG is omitted or 'yes', force the use of the
+                    binarylimit command to reduce the amount of data
+                    transferred in the hot cache case.  If ARG is 'no',
+                    then the old "large offset" method is used which is
+                    incompatible with newer MPD servers.  The default is
+                    'auto', which queries the server to determine the
+                    appropriate method.
+  --no-binarylimit  Equivalent to --binarylimit=no
+  --embedded        Attempt to retrieve embedded covers using readpicture.
+                    This requires (and implies) --binarylimit.
+  --no-embedded     Use only album-level covers.  This is the default.
+  --version         Print a version message and then exit.
+  --help            Print this message and then exit.
+
+Report bugs to <nbowler@draconx.ca>.
+EOF
+}
 
+opt_binarylimit=auto
+opt_embedded=no
+
+size=x128
 lastarg=
 dashdash=
 for arg; do
@@ -44,11 +102,18 @@ for arg; do
   fi
 
   case $dashdash$arg in
+  --binarylimit=*) opt_binarylimit=${arg#--*=} ;;
+  --no-binarylimit) opt_binarylimit=no ;;
+  --binarylimit) opt_binarylimit=yes ;;
+  --embedded) opt_embedded=yes ;;
+  --no-embedded) opt_embedded=no ;;
   --size=*) size=${arg#--size=} ;;
   --small) size=56 ;;
   --size) lastarg=$arg ;;
+  --version) print_version; exit ;;
+  --help) print_help; exit ;;
   --) dashdash=: ;;
-  -*) printf '%s: unrecognized argument: %s\n' "$0" "$arg" 1>&2; exit 1 ;;
+  -*) print_usage 'unrecognized argument: %s\n' "$arg" 1>&2; exit 1 ;;
   *) set x "$@" "$arg"; shift
   esac
 
@@ -57,12 +122,12 @@ done
 
 w=${size%x*} h=${size#*x}
 if expr "$w$h" : '[0-9][0-9]*$' >/dev/null; then :; else
-  printf '%s: invalid --size setting: %s\n' "$0" "$size" 1>&2
+  print_usage 'invalid --size setting: %s\n' "$size" 1>&2
   exit 1
 fi
 
 case $# in 0)
-  printf 'usage: %s [options] file [file ...]\n' "$0" 1>&2
+  print_usage 'at least one filename is required\n' 1>&2
   exit 1
 esac
 
@@ -74,29 +139,91 @@ tmp=`mktemp`
 exec 5>"$tmp" 6<"$tmp"
 rm -f "$tmp"
 
-for arg; do
-  arg=${arg%/*}/
-  shift; set x "$@" "$arg"; shift
+# --embedded implies --binarylimit
+case $opt_embedded in
+yes) use_embedded=true ;;
+*) use_embedded=false ;;
+esac
+$use_embedded && opt_binarylimit=yes
 
-  printf '%s\n' "$arg" | sed '/[       \\"'\'']/ {
+# Test for binarylimit command in MPD server.  We want to minimize data
+# transfer in order to make cache hits as fast as possible.
+#
+# On older servers we can set a ridiculously large offset which causes the
+# server to send no data, but this is rejected by new servers.  On new servers
+# we can limit the data transferred explicitly, but not less than 64 bytes
+# for some reason (probably not a big problem).
+
+use_binarylimit=true
+case $opt_binarylimit in
+auto) $MPDEXEC binarylimit 64 2>/dev/null || use_binarylimit=false ;;
+no) use_binarylimit=false ;;
+yes) :;;
+*)
+  print_usage 'invalid --binarylimit argument; must be yes, no or auto' 1>&2
+  exit 1
+esac
+
+if $use_binarylimit; then
+  printf 'binarylimit 64\n' >&3
+  binarylimit_offset=0
+else
+  binarylimit_offset=2147483647
+fi
+
+for file; do
+  case $file in
+  */*) dir=${file%/*}/ ;;
+  *) dir=/ ;;
+  esac
+
+  if $use_embedded; then
+    shift; set x "$@" "$file" "$dir"; shift
+  else
+    file=
+  fi
+
+  printf '%s\n' "$dir" ${file:+"$file"} | sed -n '/[   \\"'\'']/ {
     s/[\\"]/\\&/g
     s/.*/"&"/
   }
-  s/.*/albumart & 2147483647/' >&3;
+  s/.*/albumart & '"$binarylimit_offset"'/
+  1h
+  2s/^albumart/readpicture/p
+  $ {
+    g
+    p
+  }' >&3;
 done
 
-<&4 $MPDEXEC --ignore-errors >&5 2>&1 || exit
+valid=
+<&4 $MPDEXEC --verbose --ignore-errors >&5 2>&1 || exit
 while read a b <&6; do
   case $a in
-  size:) :;;
-  ACK) echo; shift || exit; continue ;;
+  readpicture|albumart)
+    prevn=$# file=$1 mode=$a; shift || exit; continue
+    ;;
+  size:)
+    if test x"$mode/$valid" = x"albumart/readpicture$prevn"; then
+      # readpicture result was OK, skip over albumart result
+      continue
+    fi
+    valid=$mode$#
+    ;;
+  ACK)
+    case $b in
+    *'{binarylimit}'*|*'{readpicture}'*)
+      printf '%s: %s\n' "$argv0" "$a $b" 1>&2; exit 1 ;;
+    esac
+    test x"$valid" = x"readpicture$prevn" || echo
+    continue
+    ;;
   *) continue ;;
   esac
 
   # We combine the filename and the size to compute the cache key and
   # hope this suffices to detect stale entries.  Unfortunately MPD does
   # not currently give us the modified date which would be more useful...
-  file=$1; shift || exit
   cache_id=`printf 'MPD:%s:%s' "$file" "$b" | md5sum`
   cache_id=${cache_id:+${cache_id%% *}_$size.png}
 
@@ -110,7 +237,7 @@ EOF
     fi
 
     # Not cached, retrieve the entire image
-    $MPDEXEC --binary --download albumart "$file" >&3 || exit
+    $MPDEXEC --binary --download "$mode" "$file" >&3 || exit
     <&4 $CONVERT -scale "$size" - "$THUMBNAILDIR/tmp.$cache_id" ||
         { rc=$? rm -f "$THUMBNAILDIR/tmp.$cache_id"; exit $rc; }
     mv -f "$THUMBNAILDIR/tmp.$cache_id" "$THUMBNAILDIR/$cache_id"