3 # Copyright © 2019-2021 Nick Bowler
5 # Generate thumbnails for cover art retrieved from MPD.
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.
11 : "${XDG_CACHE_HOME=$HOME/.cache}"
12 : "${THUMBNAILDIR=$XDG_CACHE_HOME/mpdthumb}"
14 # Try to find mpdexec...
18 */*) self=$PWD/${argv0#./} ;;
19 *) self=`command -v $argv0` ;;
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}"
28 : "${MPDEXEC=mpdexec}"
31 if command -v gm >/dev/null; then
32 : "${CONVERT=gm convert}"
34 : "${CONVERT=convert}"
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.
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"
56 This is "mpdthumb": a tool to retrieve cover art thumbnails from MPD.
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.
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.
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
72 --small Equivalent to --size=56
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
81 --no-binarylimit Equivalent to --binarylimit=no
82 --embedded Attempt to retrieve embedded covers using readpicture.
83 This requires (and implies) --binarylimit.
84 --no-embedded Use only album-level covers. This is the default.
85 --version Print a version message and then exit.
86 --help Print this message and then exit.
88 Report bugs to <nbowler@draconx.ca>.
99 if test ${lastarg:+y}; then
104 case $dashdash$arg in
105 --binarylimit=*) opt_binarylimit=${arg#--*=} ;;
106 --no-binarylimit) opt_binarylimit=no ;;
107 --binarylimit) opt_binarylimit=yes ;;
108 --embedded) opt_embedded=yes ;;
109 --no-embedded) opt_embedded=no ;;
110 --size=*) size=${arg#--size=} ;;
112 --size) lastarg=$arg ;;
113 --version) print_version; exit ;;
114 --help) print_help; exit ;;
116 -*) print_usage 'unrecognized argument: %s\n' "$arg" 1>&2; exit 1 ;;
117 *) set x "$@" "$arg"; shift
123 w=${size%x*} h=${size#*x}
124 if expr "$w$h" : '[0-9][0-9]*$' >/dev/null; then :; else
125 print_usage 'invalid --size setting: %s\n' "$size" 1>&2
130 print_usage 'at least one filename is required\n' 1>&2
135 exec 3>"$tmp" 4<"$tmp"
139 exec 5>"$tmp" 6<"$tmp"
142 # --embedded implies --binarylimit
143 case $opt_embedded in
144 yes) use_embedded=true ;;
145 *) use_embedded=false ;;
147 $use_embedded && opt_binarylimit=yes
149 # Test for binarylimit command in MPD server. We want to minimize data
150 # transfer in order to make cache hits as fast as possible.
152 # On older servers we can set a ridiculously large offset which causes the
153 # server to send no data, but this is rejected by new servers. On new servers
154 # we can limit the data transferred explicitly, but not less than 64 bytes
155 # for some reason (probably not a big problem).
158 case $opt_binarylimit in
159 auto) $MPDEXEC binarylimit 64 2>/dev/null || use_binarylimit=false ;;
160 no) use_binarylimit=false ;;
163 print_usage 'invalid --binarylimit argument; must be yes, no or auto' 1>&2
167 if $use_binarylimit; then
168 printf 'binarylimit 64\n' >&3
171 binarylimit_offset=2147483647
176 */*) dir=${file%/*}/ ;;
180 if $use_embedded; then
181 shift; set x "$@" "$file" "$dir"; shift
186 printf '%s\n' "$dir" ${file:+"$file"} | sed -n '/[ \\"'\'']/ {
190 s/.*/albumart & '"$binarylimit_offset"'/
192 2s/^albumart/readpicture/p
200 <&4 $MPDEXEC --verbose --ignore-errors >&5 2>&1 || exit
201 while read a b <&6; do
203 readpicture|albumart)
204 prevn=$# file=$1 mode=$a; shift || exit; continue
211 *'{binarylimit}'*|*'{readpicture}'*)
212 printf '%s: %s\n' "$argv0" "$a $b" 1>&2; exit 1 ;;
214 test x"$valid" = x"readpicture$prevn" || echo
220 # We combine the filename and the size to compute the cache key and
221 # hope this suffices to detect stale entries. Unfortunately MPD does
222 # not currently give us the modified date which would be more useful...
223 cache_id=`printf 'MPD:%s:%s' "$file" "$b" | md5sum`
224 cache_id=${cache_id:+${cache_id%% *}_$size.png}
226 if test ! -f "$THUMBNAILDIR/$cache_id"; then
227 if test ! -f "$THUMBNAILDIR/CACHEDIR.TAG"; then
228 mkdir -p "$THUMBNAILDIR"
229 { cat >"$THUMBNAILDIR/CACHEDIR.TAG~" || exit; } <<'EOF'
230 Signature: 8a477f597d28d172789f06886806bc55
232 mv -f "$THUMBNAILDIR/CACHEDIR.TAG~" "$THUMBNAILDIR/CACHEDIR.TAG"
235 # Not cached, retrieve the entire image
236 $MPDEXEC --binary --download "$mode" "$file" >&3 || exit
237 <&4 $CONVERT -scale "$size" - "$THUMBNAILDIR/tmp.$cache_id" ||
238 { rc=$? rm -f "$THUMBNAILDIR/tmp.$cache_id"; exit $rc; }
239 mv -f "$THUMBNAILDIR/tmp.$cache_id" "$THUMBNAILDIR/$cache_id"
242 printf '%s\n' "$THUMBNAILDIR/$cache_id"