1#!/bin/bash
2
3#
4# MOC - music on console
5# Copyright (C) 2004-2005 Damian Pietras <daper@daper.net>
6#
7# md5check.sh Copyright (C) 2012 John Fitzgerald
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14
15#
16# TODO: - Make format, rate and channels explicit where possible.
17#       - Add remaining decoder check functions.
18#       - Fix decoders with genuine MD5 mismatches.
19#       - Fix decoders with genuine format mismatches.
20#       - Handle format mismatches which aren't genuine.
21#       - Possibly rewrite in Perl for speed.
22#
23
24declare -A UNKNOWN
25declare -A UNSUPPORTED
26
27EXTRA=false
28IGNORE=false
29RC=0
30SILENT=false
31TREMOR=false
32VERBOSE=false
33
34# Clean error termination.
35function die {
36  echo '***' $@ > /dev/stderr
37  exit 1
38}
39
40# Provide usage information.
41function usage () {
42  echo "Usage: ${0##*/} [-e] [-s|-v] [LOGFILE]"
43  echo "       ${0##*/} -h"
44}
45
46# Provide help information.
47function help () {
48  echo
49  echo "MOC MD5 sum checking tool"
50  echo
51  usage
52  echo
53  echo "  -e|--extra      Perform extra checks"
54  echo "  -h|--help       This help information"
55  echo "  -i|--ignore     Ignore known problems"
56  echo "  -s|--silent     Output no results"
57  echo "  -v|--verbose    Output all results"
58  echo
59  echo "  LOGFILE         MOC server log file name, or '-' for stdin"
60  echo "                  (default: 'mocp_server_log' in the current directory)"
61  echo
62  echo "Exit codes:       0 - No errors or mismatches"
63  echo "                  1 - Error occurred"
64  echo "                  2 - Mismatch found"
65  echo
66}
67
68# Check the FAAD decoder's samples.
69FAAD=$(which faad 2>/dev/null)
70function aac () {
71  local ENDIAN OPTS
72
73  [[ -x "$FAAD" ]] || die faad2 not installed
74
75  [[ "${FMT:0:1}" = "f" ]] && ENDIAN=le
76  OPTS="-w -q -f2"
77
78  [[ "${FMT:1:2}" = "16" ]] && OPTS="$OPTS -b1"
79  [[ "${FMT:1:2}" = "24" ]] && OPTS="$OPTS -b2"
80  [[ "${FMT:1:2}" = "32" ]] && OPTS="$OPTS -b3"
81
82  SUM2=$($FAAD $OPTS "$FILE" | md5sum)
83  LEN2=$($FAAD $OPTS "$FILE" | wc -c)
84}
85
86# Check the FFmpeg decoder's samples.
87FFMPEG=$(which avconv 2>/dev/null || which ffmpeg 2>/dev/null)
88function ffmpeg () {
89  local ENDIAN OPTS
90
91  [[ -x "$FFMPEG" ]] || die ffmpeg/avconv not installed
92
93  [[ "${FMT:0:1}" = "f" ]] && ENDIAN=le
94  OPTS="-ac $CHANS -ar $RATE -f $FMT$ENDIAN"
95
96  SUM2="$($FFMPEG -i "$FILE" $OPTS - </dev/null 2>/dev/null | md5sum)"
97  LEN2=$($FFMPEG -i "$FILE" $OPTS - </dev/null 2>/dev/null | wc -c)
98
99  [[ "$($FFMPEG -i "$FILE" </dev/null 2>&1)" =~ Audio:\ .*\ (mono|stereo) ]] || \
100    IGNORE_SUM=$IGNORE
101}
102
103# Check the FLAC decoder's samples.
104SOX=$(which sox 2>/dev/null)
105function flac () {
106  local OPTS
107
108  [[ -x "$SOX" ]] || die "SoX (for flac) not installed"
109
110  [[ "${FMT:0:1}" = "s" ]] && OPTS="-e signed" || OPTS="-e unsigned"
111  [[ "${FMT:1:1}" = "8" ]] && OPTS="$OPTS -b8 -L"
112  [[ "${FMT:1:2}" = "16" ]] && OPTS="$OPTS -b16"
113  [[ "${FMT:1:2}" = "24" ]] && OPTS="$OPTS -b24"
114  [[ "${FMT:1:2}" = "32" ]] && OPTS="$OPTS -b32"
115  [[ "$FMT" =~ "le" ]] && OPTS="$OPTS -L"
116  [[ "$FMT" =~ "be" ]] && OPTS="$OPTS -B"
117  OPTS="$OPTS -r$RATE -c$CHANS"
118
119  SUM2=$($SOX "$FILE" $OPTS -t raw - | md5sum)
120  LEN2=$($SOX "$FILE" $OPTS -t raw - | wc -c)
121}
122
123# Check the Ogg/Vorbis decoder's samples.
124OGGDEC=$(which oggdec 2>/dev/null)
125function vorbis () {
126  [[ -x "$OGGDEC" ]] || die oggdec not installed
127  SUM2="$($OGGDEC -RQ -o - "$FILE" | md5sum)"
128  LEN2=$($OGGDEC -RQ -o - "$FILE" | wc -c)
129}
130
131# Check the LibSndfile decoder's samples.
132SOX=$(which sox 2>/dev/null)
133function sndfile () {
134  # LibSndfile doesn't have a decoder, use SoX.
135  [[ -x "$SOX" ]] || die "sox (for sndfile) not installed"
136  SUM2="$($SOX "$FILE" -t f32 - | md5sum)"
137  LEN2=$($SOX "$FILE" -t f32 - | wc -c)
138  [[ "$NAME" == *-s32le-* ]] && IGNORE_SUM=$IGNORE
139}
140
141# Check the MP3 decoder's samples.
142SOX=$(which sox 2>/dev/null)
143function mp3 () {
144  # Lame's decoder only does 16-bit, use SoX.
145  [[ -x "$SOX" ]] || die "sox (for mp3) not installed"
146  SUM2="$($SOX "$FILE" -t s32 - | md5sum)"
147  LEN2=$($SOX "$FILE" -t s32 - | wc -c)
148  IGNORE_SUM=$IGNORE
149  IGNORE_LEN=$IGNORE
150}
151
152# Check the Speex decoder's samples.
153SPEEX=$(which speexdec 2>/dev/null)
154function speex () {
155  [[ -x "$SPEEX" ]] || die speexdec not installed
156  SUM2="$($SPEEX "$FILE" - 2>/dev/null | md5sum)"
157  LEN2=$($SPEEX "$FILE" - 2>/dev/null | wc -c)
158  IGNORE_SUM=$IGNORE
159  IGNORE_LEN=$IGNORE
160}
161
162# Process command line options.
163for OPTS
164do
165  case $1 in
166    -e|--extra) EXTRA=true
167                ;;
168   -i|--ignore) IGNORE=true
169                ;;
170  -v|--verbose) VERBOSE=true
171                SILENT=false
172                ;;
173   -s|--silent) SILENT=true
174                VERBOSE=false
175                ;;
176     -h|--help) help
177                exit 0
178                ;;
179          --|-) break
180                ;;
181            -*) echo Unrecognised option: $1
182                usage > /dev/stderr
183                exit 1
184                ;;
185             *) break
186                ;;
187  esac
188  shift
189done
190
191# Allow for log file parameter.
192LOG="${1:-mocp_server_log}"
193[[ "$LOG" = "-" ]] && LOG=/dev/fd/0
194
195# Output formatting.
196$SILENT || echo
197
198# Process server log file.
199while read
200do
201
202  # Extract MOC revision header.
203  [[ "$REPLY" =~ "This is Music On Console" ]] && \
204      REVN="$(echo "$REPLY" | sed 's/^.*Music/Music/')"
205
206  # Check for Tremor decoder.
207  [[ "$REPLY" =~ Loaded\ [0-9]+\ decoders:.*vorbis\(tremor\) ]] && \
208     TREMOR=true
209
210  # Extract file's full pathname.
211  [[ "$REPLY" =~ "Playing item" ]] && \
212     FILE="$(echo "$REPLY" | sed 's/^.* item [0-9]*: \(.*\)$/\1/')"
213
214  # Ignore all non-MD5 lines.
215  [[ "$REPLY" =~ "MD5" ]] || continue
216
217  # Extract fields of interest.
218  NAME="$(echo "$REPLY" | sed 's/^.*MD5(\([^)]*\)) = .*$/\1/')"
219  REST="$(echo "$REPLY" | sed 's/^.*MD5([^)]*) = \(.*\)$/\1/')"
220  SUM=$(echo $REST | cut -f1 -d' ')
221  LEN=$(echo $REST | cut -f2 -d' ')
222  DEC=$(echo $REST | cut -f3 -d' ')
223  $TREMOR && [[ "$DEC" = "vorbis" ]] && DEC=tremor
224  FMT=$(echo $REST | cut -f4 -d' ')
225  CHANS=$(echo $REST | cut -f5 -d' ')
226  RATE=$(echo $REST | cut -f6 -d' ')
227
228  # Check that we have the full pathname and it's not a dangling symlink.
229  [[ "$NAME" = "$(basename "$FILE")" ]] || die Filename mismatch
230  [[ -L "$FILE" && ! -f "$FILE" ]] && continue
231
232  # Get the independant MD5 sum and length of audio file.
233  case $DEC in
234  aac|ffmpeg|flac|mp3|sndfile|speex|vorbis)
235      IGNORE_LEN=false
236      IGNORE_SUM=false
237      $DEC
238      SUM2=$(echo "$SUM2" | cut -f1 -d' ')
239      ;;
240  modplug|musepack|sidplay2|timidity|tremor|wavpack)
241      $IGNORE && continue
242      [[ "${UNSUPPORTED[$DEC]}" ]] || {
243        echo -e "*** Decoder not yet supported: $DEC\n" > /dev/stderr
244        UNSUPPORTED[$DEC]="Y"
245      }
246      continue
247      ;;
248  *)  [[ "${UNKNOWN[$DEC]}" ]] || {
249        echo -e "*** Unknown decoder: $DEC\n" > /dev/stderr
250        UNKNOWN[$DEC]="Y"
251      }
252      continue
253      ;;
254  esac
255
256  # Compare results.
257  BADFMT=false
258  $EXTRA && [[ "${NAME:0:9}" = "sinewave-" ]] && {
259    FMT2=$(echo $NAME | cut -f2 -d'-' | sed "s/24/32/")
260    CHANS2=$(echo $NAME | cut -f3 -d'-')
261    RATE2=$(echo $NAME | cut -f4 -d'-' | cut -f1 -d'.')
262    [[ "$FMT" = "$FMT2" ]] || BADFMT=true
263    [[ "$CHANS" = "$CHANS2" ]] || BADFMT=true
264    [[ "$RATE" = "$RATE2" ]] || BADFMT=true
265  }
266  BADSUM=false; $IGNORE_SUM || [[ "$SUM" = "$SUM2" ]] || BADSUM=true
267  BADLEN=false; $IGNORE_LEN || [[ "$LEN" = "$LEN2" ]] || BADLEN=true
268
269  # Set exit code.
270  $BADFMT || $BADSUM || $BADLEN && RC=2
271
272  # Determine output requirements.
273  $SILENT && continue
274  $BADFMT || $BADSUM || $BADLEN || $VERBOSE || continue
275
276  # Report result.
277  [[ "$REVN" ]] && {
278    echo "Test Results for $REVN"
279    echo
280    REVN=
281  }
282  echo "$NAME:"
283  echo "    $SUM $LEN $DEC $FMT $CHANS $RATE"
284  echo "    $SUM2 $LEN2"
285  $BADFMT && echo "*** Format mismatch"
286  $BADSUM && echo "*** MD5 sum mismatch"
287  $BADLEN && echo "*** Length mismatch"
288  echo
289
290done < $LOG
291
292$SILENT || {
293  case "$RV" in
294  1)
295    echo "No mismatches found"
296    ;;
297  2)
298    echo "NOTE: This tool is still being refined.  Do not accept mismatches"
299    echo "      at face value; they may be due to factors such as sample size"
300    echo "      differences.  But it does provide a reason to investigate"
301    echo "      such mismatches further (and further refine this tool if false)."
302    echo
303    ;;
304  esac
305}
306
307exit $RC
308