| 1 | #! /bin/bash |
| 2 | |
| 3 | set -e |
| 4 | : ${JBDIR=/mnt/jb} |
| 5 | |
| 6 | ###-------------------------------------------------------------------------- |
| 7 | ### CD identification algorithms. |
| 8 | ### |
| 9 | ### 1. CDDB |
| 10 | ### |
| 11 | ### CCLLLLNN [NTRACK TRACK-START... LENGTH] |
| 12 | ### |
| 13 | ### CC is a checksum of the track start times; LLLL is the offset of the |
| 14 | ### leadout track, in seconds (rounded down), and NN is the total number of |
| 15 | ### tracks. All of these are in hexadecimal, and include the 150-frame (2 |
| 16 | ### second) pre-gap. All of these are in hexadecimal. Since a CD can have |
| 17 | ### at most 99 tracks, and can contain no more than 90 minutes of audio (!), |
| 18 | ### the other two items fit without needing reduction. |
| 19 | ### |
| 20 | ### The checksum is the sum of the decimal digits of the track start times, |
| 21 | ### in seconds, reduced modulo 255. |
| 22 | ### |
| 23 | ### NTRACK is the number of tracks; LENGTH is the offset of the leadout in |
| 24 | ### seconds. These are the same as in the checksum, so repeating them is |
| 25 | ### pointless, but it's done anyway. The TRACK-STARTs are the track start |
| 26 | ### offsets, in frames. |
| 27 | ### |
| 28 | ### |
| 29 | ### 2. AccurateRip |
| 30 | ### |
| 31 | ### DA1-DA2-CDDBID |
| 32 | ### |
| 33 | ### CDDBID is the CDDB id as described above. DA1 is simply the sum of the |
| 34 | ### track starts, including the lead-out track; DA2 is the sum of the |
| 35 | ### products TRACKNO * OFFSET for the audio tracks only, but including the |
| 36 | ### final lead-out -- so a data track makes the last audio track look very |
| 37 | ### long. Another wrinkle: the OFFSET for the first track is forced to 1 if |
| 38 | ### it's zero (to avoid the entry being lost, I presume, though I'm not sure |
| 39 | ### why this is ever so useful). |
| 40 | ### |
| 41 | ### |
| 42 | ### 3. MusicBrainz |
| 43 | ### |
| 44 | ### The MusicBrainz identification is a base64-encoded SHA-1 hash of the |
| 45 | ### table of contents. The base64 encoding uses `.', `_' and `-' in place of |
| 46 | ### `+', `/' and `=', because the standard characters /all/ have special |
| 47 | ### meanings in URL query strings. (Duh. And I'm not quite sure why we |
| 48 | ### still need the trailing marker.) |
| 49 | ### |
| 50 | ### The message to be hashed is FIRST LAST LENGTH TRACK-START..., where FIRST |
| 51 | ### and LAST are the first and last track numbers, LENGTH is the offset of |
| 52 | ### the lead-out, in frames, and the TRACK-STARTs are the start offsets of |
| 53 | ### the tracks, in order, also in frames. The track numbers are two |
| 54 | ### uppercase hex digits; the frame offsets are eight. All of these are |
| 55 | ### simply concatenated together. |
| 56 | ### |
| 57 | ### MusicBrainz only concerns itself with the audio tracks. If there's a |
| 58 | ### data track, then we ignore it, and the lead-out is considered to be 11400 |
| 59 | ### frames before the data track. |
| 60 | |
| 61 | ###-------------------------------------------------------------------------- |
| 62 | ### Command line. |
| 63 | |
| 64 | format=cddb |
| 65 | while getopts "acCm" opt; do |
| 66 | case "$opt" in |
| 67 | a) format=accuraterip ;; |
| 68 | c) format=cddb ;; |
| 69 | C) format=cddb-tracks ;; |
| 70 | m) format=musicbrainz ;; |
| 71 | *) exit 1 ;; |
| 72 | esac |
| 73 | done |
| 74 | shift $((OPTIND - 1)) |
| 75 | |
| 76 | case $# in |
| 77 | 0) |
| 78 | ;; |
| 79 | 1) |
| 80 | if [ -r "$1/.discid" ]; then |
| 81 | exec <"$1/.discid" |
| 82 | else |
| 83 | exec < <($JBDIR/bin/flaccrip-toc "$1") |
| 84 | fi |
| 85 | ;; |
| 86 | *) |
| 87 | echo >&2 "Usage: $0 [-acCm] [DIRECTORY]" |
| 88 | exit 1 |
| 89 | ;; |
| 90 | esac |
| 91 | |
| 92 | ###-------------------------------------------------------------------------- |
| 93 | ### Main work. |
| 94 | |
| 95 | ## Initial setup. |
| 96 | cddbck=0 |
| 97 | cddbtracks="" |
| 98 | nt=0 nat=0 |
| 99 | da=0 db=0 |
| 100 | mbtracks="" |
| 101 | |
| 102 | ## Wander through the table of contents picking up unconsidered trifles. |
| 103 | while read type offset; do |
| 104 | |
| 105 | ## Bump the track numbers here. Most things want 1-based numbering, so |
| 106 | ## this is right. Don't bump for the end marker. Those who care |
| 107 | ## (AccurateRip) will sort it out for themselves. |
| 108 | case "$type" in |
| 109 | T) nt=$((nt + 1)) nat=$((nat + 1));; |
| 110 | D) nt=$((nt + 1)) ;; |
| 111 | esac |
| 112 | |
| 113 | ## Update the CDDB state. This is common to several formats. |
| 114 | case "$type" in |
| 115 | [TD]) |
| 116 | o=$((offset + 150)) |
| 117 | s=$((o/75)) |
| 118 | cddbtracks="${cddbtracks:+$cddbtracks }$o" |
| 119 | while :; do |
| 120 | case "$s" in |
| 121 | ?*) cddbck=$((cddbck + ${s:0:1})); s=${s#?} ;; |
| 122 | *) break ;; |
| 123 | esac |
| 124 | done |
| 125 | ;; |
| 126 | E) |
| 127 | final=$offset |
| 128 | ;; |
| 129 | esac |
| 130 | |
| 131 | ## Update other bits of information. |
| 132 | case "$type" in |
| 133 | T) |
| 134 | da=$((da + offset)) |
| 135 | db=$((db + nat*(offset > 0 ? offset : 1))) |
| 136 | mbtracks="$mbtracks$(printf "%08X" $((offset + 150)))" |
| 137 | ;; |
| 138 | D) |
| 139 | mbfinal=$((offset - 11250)) |
| 140 | ;; |
| 141 | E) |
| 142 | da=$((da + offset)) |
| 143 | db=$((db + (nat + 1)*(offset > 0 ? offset : 1))) |
| 144 | case "${mbfinal+t}" in |
| 145 | t) ;; |
| 146 | *) mbfinal=$((offset + 150)) ;; |
| 147 | esac |
| 148 | ;; |
| 149 | esac |
| 150 | done |
| 151 | |
| 152 | ## Sort out the CDDB id. |
| 153 | cddbid=$(printf "%02x%04x%02x" $((cddbck%255)) $((final/75)) $nt) |
| 154 | |
| 155 | ###-------------------------------------------------------------------------- |
| 156 | ### Produce the answer. |
| 157 | |
| 158 | case "$format" in |
| 159 | cddb) |
| 160 | echo "$cddbid" |
| 161 | ;; |
| 162 | cddb-tracks) |
| 163 | echo "$cddbid $nt $cddbtracks $((final/75 + 2))" |
| 164 | ;; |
| 165 | accuraterip) |
| 166 | printf "%03d-%08x-%08x-%s\n" $nat $da $db $cddbid |
| 167 | ;; |
| 168 | musicbrainz) |
| 169 | mb=$(printf "%02X%02X%08X%s" 1 $nat $mbfinal $mbtracks) |
| 170 | for ((i = nat; i < 99; i++)); do |
| 171 | mb="${mb}00000000" |
| 172 | done |
| 173 | printf "%s" $mb | |
| 174 | openssl dgst -sha1 -binary | |
| 175 | openssl base64 | tr '+/=' '._-' |
| 176 | ;; |
| 177 | esac |