]> git.draconx.ca Git - mpdhacks.git/blob - mpdthumb.sh
4ff4249042917920cdc9940b55086338e2500e7e
[mpdhacks.git] / mpdthumb.sh
1 #!/bin/sh
2 #
3 # Copyright © 2019-2021 Nick Bowler
4 #
5 # Generate thumbnails for cover art retrieved from MPD.
6 #
7 # License GPLv3+: GNU General Public License version 3 or any later version.
8 # This is free software: you are free to change and redistribute it.
9 # There is NO WARRANTY, to the extent permitted by law.
10
11 : "${XDG_CACHE_HOME=$HOME/.cache}"
12 : "${THUMBNAILDIR=$XDG_CACHE_HOME/mpdthumb}"
13
14 # Try to find mpdexec...
15 argv0=$0
16 case $argv0 in
17 /*) self=$argv0 ;;
18 */*) self=$PWD/${argv0#./} ;;
19 *) self=`command -v $argv0` ;;
20 esac
21 owndir=${self%/*}
22
23 if command -v "$owndir/mpdexec.pl" >/dev/null; then
24   : "${MPDEXEC=$owndir/mpdexec.pl}"
25 elif command -v mpdexec.pl >/dev/null; then
26   : "${MPDEXEC=mpdexec.pl}"
27 else
28   : "${MPDEXEC=mpdexec}"
29 fi
30
31 if command -v gm >/dev/null; then
32   : "${CONVERT=gm convert}"
33 else
34   : "${CONVERT=convert}"
35 fi
36
37 print_version () {
38   cat <<'EOF'
39 mpdthumb.sh 0.9
40 Copyright © 2021 Nick Bowler
41 License GPLv3+: GNU General Public License version 3 or any later version.
42 This is free software: you are free to change and redistribute it.
43 There is NO WARRANTY, to the extent permitted by law.
44 EOF
45 }
46
47 print_usage () {
48   test $# -eq 0 || { printf '%s: ' "$argv0"; printf "$@"; }
49   printf 'Usage: %s [options] file [file ...]\n' "$argv0"
50   test $# -eq 0 || printf 'Try %s --help for more information.\n' "$argv0"
51 }
52
53 print_help () {
54   print_usage
55   cat <<EOF
56 This is "mpdthumb": a tool to retrieve cover art thumbnails from MPD.
57
58 One or more filenames may be specified.  The cover art (if any) for each
59 file is retrieved and scaled to the requested size.  The thumbnails are
60 cached locally so that subsequent lookups return results very quickly.
61
62 The filenames of the generated thumbnails are printed one per line in
63 the same order of the associated files specified on the command line.
64 If a given file has no cover art, then a blank line is printed.
65
66 Options:
67   --size=[W][xH]    Specify the dimensions (in pixels) of a bounding
68                     rectangle into which the image will be scaled to fit.
69                     One (but not both) of the dimensions may be omitted
70                     to indicate no limit in that direction.  The default
71                     size is x128.
72   --small           Equivalent to --size=56
73   --binarylimit[=ARG]
74                     If ARG is omitted or 'yes', force the use of the
75                     binarylimit command to reduce the amount of data
76                     transferred in the hot cache case.  If ARG is 'no',
77                     then the old "large offset" method is used which is
78                     incompatible with newer MPD servers.  The default is
79                     'auto', which queries the server to determine the
80                     appropriate method.
81   --no-binarylimit  Equivalent to --binarylimit=no
82   --version         Print a version message and then exit.
83   --help            Print this message and then exit.
84
85 Report bugs to <nbowler@draconx.ca>.
86 EOF
87 }
88
89 opt_binarylimit=auto
90
91 size=x128
92 lastarg=
93 dashdash=
94 for arg; do
95   if test ${lastarg:+y}; then
96     arg=$lastarg=$arg
97     lastarg=
98   fi
99
100   case $dashdash$arg in
101   --binarylimit=*) opt_binarylimit=${arg#--*=} ;;
102   --no-binarylimit) opt_binarylimit=no ;;
103   --binarylimit) opt_binarylimit=yes ;;
104   --size=*) size=${arg#--size=} ;;
105   --small) size=56 ;;
106   --size) lastarg=$arg ;;
107   --version) print_version; exit ;;
108   --help) print_help; exit ;;
109   --) dashdash=: ;;
110   -*) print_usage 'unrecognized argument: %s\n' "$arg" 1>&2; exit 1 ;;
111   *) set x "$@" "$arg"; shift
112   esac
113
114   shift
115 done
116
117 w=${size%x*} h=${size#*x}
118 if expr "$w$h" : '[0-9][0-9]*$' >/dev/null; then :; else
119   print_usage 'invalid --size setting: %s\n' "$size" 1>&2
120   exit 1
121 fi
122
123 case $# in 0)
124   print_usage 'at least one filename is required\n' 1>&2
125   exit 1
126 esac
127
128 tmp=`mktemp`
129 exec 3>"$tmp" 4<"$tmp"
130 rm -f "$tmp"
131
132 tmp=`mktemp`
133 exec 5>"$tmp" 6<"$tmp"
134 rm -f "$tmp"
135
136 # Test for binarylimit command in MPD server.  We want to minimize data
137 # transfer in order to make cache hits as fast as possible.
138 #
139 # On older servers we can set a ridiculously large offset which causes the
140 # server to send no data, but this is rejected by new servers.  On new servers
141 # we can limit the data transferred explicitly, but not less than 64 bytes
142 # for some reason (probably not a big problem).
143
144 use_binarylimit=true
145 case $opt_binarylimit in
146 auto) $MPDEXEC binarylimit 64 2>/dev/null || use_binarylimit=false ;;
147 no) use_binarylimit=false ;;
148 yes) :;;
149 *)
150   print_usage 'invalid --binarylimit argument; must be yes, no or auto' 1>&2
151   exit 1
152 esac
153
154 if $use_binarylimit; then
155   printf 'binarylimit 64\n' >&3
156   binarylimit_offset=0
157 else
158   binarylimit_offset=2147483647
159 fi
160
161 for arg; do
162   arg=${arg%/*}/
163   shift; set x "$@" "$arg"; shift
164
165   printf '%s\n' "$arg" | sed '/[        \\"'\'']/ {
166     s/[\\"]/\\&/g
167     s/.*/"&"/
168   }
169   s/.*/albumart & '"$binarylimit_offset"'/' >&3;
170 done
171
172 <&4 $MPDEXEC --ignore-errors >&5 2>&1 || exit
173 while read a b <&6; do
174   case $a in
175   size:) :;;
176   ACK)
177     case $b in
178     *binarylimit*) printf '%s: %s\n' "$argv0" "$a $b" 1>&2; exit 1 ;;
179     esac
180
181     echo; shift || exit; continue ;;
182   *) continue ;;
183   esac
184
185   # We combine the filename and the size to compute the cache key and
186   # hope this suffices to detect stale entries.  Unfortunately MPD does
187   # not currently give us the modified date which would be more useful...
188   file=$1; shift || exit
189   cache_id=`printf 'MPD:%s:%s' "$file" "$b" | md5sum`
190   cache_id=${cache_id:+${cache_id%% *}_$size.png}
191
192   if test ! -f "$THUMBNAILDIR/$cache_id"; then
193     if test ! -f "$THUMBNAILDIR/CACHEDIR.TAG"; then
194       mkdir -p "$THUMBNAILDIR"
195       { cat >"$THUMBNAILDIR/CACHEDIR.TAG~" || exit; } <<'EOF'
196 Signature: 8a477f597d28d172789f06886806bc55
197 EOF
198       mv -f "$THUMBNAILDIR/CACHEDIR.TAG~" "$THUMBNAILDIR/CACHEDIR.TAG"
199     fi
200
201     # Not cached, retrieve the entire image
202     $MPDEXEC --binary --download albumart "$file" >&3 || exit
203     <&4 $CONVERT -scale "$size" - "$THUMBNAILDIR/tmp.$cache_id" ||
204         { rc=$? rm -f "$THUMBNAILDIR/tmp.$cache_id"; exit $rc; }
205     mv -f "$THUMBNAILDIR/tmp.$cache_id" "$THUMBNAILDIR/$cache_id"
206   fi
207
208   printf '%s\n' "$THUMBNAILDIR/$cache_id"
209 done