The problem

I have a whole bunch of audiobooks that I want to listen to in the car. However, they are distributed in longer chunks than I would like -- typically 25 to 50 minutes per mp3 file. I tried out a few mp3 slicers, but they generally didn't work very well. Eventually I decided to use cdrdao directly to cut the two second pause between tracks and standardise on a five minute track length.

This left me with the problem of generating the TOC files cdrdao reads to decide what it is going to burn.

The solution

The following python script takes a list of WAV files in the order they are to be burned and writes as many TOC files as are needed. The code isn't as neat or documented as it should be, but I'm too tired and busy to do that now. Please bug me about it later if it hasn't been fixed...

#!/usr/bin/python

import sys
import wave

files = sys.argv[1:]

class CD(object):
    def __init__(self):
        self.tracks = []
        self.maxLen = 79 * 60
        self.trackLen = 5 * 60

    def getRemainingSize(self):
        size = 0
        for track in self.tracks:
            if track['length'] == None:
                size += self.trackLen
            else:
                size += track['length']
        return self.maxLen - size

    def getNextTrackLen(self):
        return min(self.trackLen, self.getRemainingSize(self))

    def addTrack(self, wavfile, start, length=None, end=None):
        if length == None: length = self.trackLen
        if self.getRemainingSize() == 0:
            return None
        if length > self.getRemainingSize():
            length = self.getRemainingSize()
        if end:
            tlength = None
        else:
            tlength = length
        self.tracks.append({'file': wavfile, 'start': start, 'length': tlength})
        return length

    def getTOC(self):
        toc = 'CD_DA\n'
        for track in self.tracks:
            trackBlock = '\nTRACK AUDIO\n FILE "'
            trackBlock += track['file'] + '" '
            trackBlock += '%s:%s:0' % (int(track['start']/60),
                                                            track['start']%60)
            if track['length'] != None:
                trackBlock += ' %s:%s:0' % (int(track['length']/60),
                                                            track['length']%60)
            toc += trackBlock
        return toc


class CDs(object):
    def __init__(self):
        self.cds = []

    def addWav(self, wavfile):
        if len(self.cds) == 0:
            self.cds.append(CD())
        CDRem = self.cds[-1].getRemainingSize()
        print wavfile
        wav = wave.open(wavfile)
        wavPos = 0
        wavLen = wav.getnframes()/wav.getframerate()
        end = None

        while 1:
            print wavPos, "of", wavLen
            if (wavLen - wavPos) < self.cds[-1].trackLen: end = 1
            result = self.cds[-1].addTrack(wavfile, wavPos, end=end)
            if result == None:
                wavPos = max(0, wavPos-15)
                self.cds.append(CD())
                continue
            wavPos += result
            if end: break

cdCollection = CDs()
for wavfile in files:
    cdCollection.addWav(wavfile)
    tocnum = 0
for cd in cdCollection.cds:
    tocnum += 1
    tocfile = open('cd%02d.toc'%(tocnum), 'w')
    tocfile.write(cd.getTOC())
    tocfile.close()