cddb discID

The www.freedb.org internet data base provides information on artists, album- and songtitles on audio CDs.

This package reads in the TOC of an audio CD and calculates the discID required for queries at freedb.org.

A problem is the the access to the TOC information of an audio CDs. The easiest way at least on linux is to use the program cdparanoia, as

 cdparanoia -Q

prints the TOC information.

Only on addition of 2s lead-in per track is required and a possible addition of 2 sec lead out in the total-length. The handling of the lead out is varying between different implementations of the protocol, but it is required from the freedb.org Test-CD.

The algorithm is a direct translation of the C Code provided in the FAQ, the freedb test CD discid is calculated correctly with an extra lead out of 2s.

The offset are provided by cdparanoia as a string mm:ss.ff (minutes:seconds.frames, an audio CD has 75 frames/s) which will be stored as 3 element list {mm ss tt}.

The variable cdtoc gives the offsets indexed by track number (starting from 0), additionally

cdtoc(num_traks) gives the number of tracks on the CD and cdtoc(total_trks) the total length of the CD.

 package   provide discid 0.1
 #
 # (C) Joachim Heidemeier 2011
 # published under the same licence terms as Tcl
 #
 namespace eval cddb {
 namespace export cddbID readTOC tocList2TrackOffset
 variable cdtoc
 #
 # transforms the  string mm:ss.ff in 3 element list
 # 2 sec lead-in time are automatically added
 # add is optional value for lead out
 #
    proc getMCF {val {add 0}} {
        set l [split $val {: .}]
        set s [lindex $l 1]
        set m [lindex $l 0]
        set t [lindex $l 2]
        scan $m %d m
        scan $s %d s
        scan $t %d t
        incr s $add
        incr s 2
        if {$s > 60} {
            lset l 0 [incr m]
            lset l 1 [expr {$s - 60}]
        } else {
            lset l 0 $m
            lset l 1 $s
        }
        lset l 2 $t
        return $l

    }
 #
 # transform TOC-List in total number of tracks
 # 75 frames / s    
 #    
 proc  tocList2TrackOffset {tocList} {
        set to [expr {([lindex $tocList 0] * 60 * 75) + ([lindex $tocList 1] * 75) + [lindex $tocList 2]}]
        return $to
 }
 #
 #
 # get TOC from CD 
 # uses " cdparanoia -Q"
 # drive is the CD rom device
 # platform dependend, alternatives could be
 # direct wrapping of libcdio
 #
 proc read_TOC {{drive /dev/sr0}} {
    variable cdtoc
 #
 # cdparanoia -Q writes to stderror  
 #   
    catch {exec cdparanoia -Q -d $drive} result
 #
 # skip the header from cdparanoia, header ends with a line of  equal signs (==...)
 #
    set mode skip
    foreach line [split $result \n] {
        if {[regexp {[=]+} $line]} {
            set mode process
        } else {
            if {$mode eq {process}} {               
                if {[regexp -nocase -all -line -- \
                    { ([0-9]+)\..*([0-9]{2}:[0-9]{2}\.[0-9]{2}).*([0-9];{2}:[0-9]{2}\.[0-9]{2}).*}\
                     $line -> total_trks length begin]} {
 # get tracks, indexed from 0
                     set cdtoc(num_trks) $total_trks   
                     set cdtoc([incr total_trks -1]) [getMCF $begin] 
                } elseif  {[regexp -nocase -all -line -- {TOTAL.*([0-9]{2}:[0-9]{2}\.[0-9]{2})} $line -> begin]} {
 #
 # get total_tracks time
 # additional lead out 2s
 #                 
                       set cdtoc(total_trks)  [getMCF $begin 2]
                       set cdtoc(total_seconds) [expr {[lindex $cdtoc(total_trks) 0] * 60 + [lindex $cdtoc(total_trks) 1]}]
                      set mode skip
                    }
                }  else {
 # in skip mode, possible errors are reported
                    if {[string match {*Unable*} $line]} { 
                        return -code error $result
                    }
                }
            }
    }
 }
 # Now starts the discid algorithm starts
 # This proc calculates  a sum over of the digits of all tracks
 # It is a direct translation of the C-Code provided in the 
 # freedb FAQ
 proc cddb_sum {n} {
    set ret 0
    while {$n > 0} {
        set ret [expr {$ret + ($n % 10)}]
        set n [expr {$n / 10}]
    }
    return $ret
 }
 #
 # calculate the actual discid
 # 
 proc cddbID {} {
    variable cdtoc
    set t 0
    set i 0
    set n 0
    set total_trks $cdtoc(num_trks)
    while {$i < $total_trks} {
        set jmin [lindex $cdtoc($i) 0]
        set jsec [lindex $cdtoc($i) 1]
           set jval [expr {($jmin * 60) + $jsec}]
        set n [expr {$n + [cddb_sum $jval]}]
        incr i   
    }
    set jmin [lindex $cdtoc(total_trks) 0]
    set jsec [lindex $cdtoc(total_trks) 1]
    set kmin [lindex $cdtoc(0) 0]
    set ksec [lindex $cdtoc(0) 1]
    set t [expr {(($jmin*60) + $jsec) - (($kmin*60)+$ksec)}]
    set r_int [expr {($n % 0xff) << 24 | $t << 8 | $total_trks}]
    set ret [format %08x $r_int]
    set cdtoc(cddbID) $ret
    return
 }
 } ;# end namespace

See also FreeDB Access