From 483c7b8a0ca41daf57ec718c6d56052144faa3af Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sat, 16 May 2009 19:09:27 +0000 Subject: [PATCH] * morituri/program/cdrdao.py: read all sessions by reading session 9. * morituri/image/toc.py: Parse ZERO statements properly. Also set absolute offsets when we know them from the .toc file. Properly set audio flag on tracks. FIXME: probably doesn't work if the .toc does not have lengths. * morituri/image/table.py: Add some debug to cddb disc id calculation. Fix absolutize function, it was going one index too far. raise ValueError when overriding .absolute with a wrong value. * examples/readdisc.py: Show CDDB disc id at the start. Assert when toc and table have different disc id's (to be fixed) * morituri/test/test_image_cue.py: Update for having the table already with absolute values. * morituri/test/test_image_toc.py: Add Ladyhawke CDDB test, it has a data track. --- ChangeLog | 21 ++++++++++ examples/readdisc.py | 6 +++ morituri/image/table.py | 24 ++++++++++-- morituri/image/toc.py | 69 +++++++++++++++++++++++++++++---- morituri/program/cdrdao.py | 11 ++++-- morituri/test/test_image_cue.py | 11 +++--- morituri/test/test_image_toc.py | 32 ++++++++++++--- 7 files changed, 147 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index 90e2367..5513a10 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +2009-05-16 Thomas Vander Stichele + + * morituri/program/cdrdao.py: + read all sessions by reading session 9. + * morituri/image/toc.py: + Parse ZERO statements properly. + Also set absolute offsets when we know them from the .toc file. + Properly set audio flag on tracks. + FIXME: probably doesn't work if the .toc does not have lengths. + * morituri/image/table.py: + Add some debug to cddb disc id calculation. + Fix absolutize function, it was going one index too far. + raise ValueError when overriding .absolute with a wrong value. + * examples/readdisc.py: + Show CDDB disc id at the start. + Assert when toc and table have different disc id's (to be fixed) + * morituri/test/test_image_cue.py: + Update for having the table already with absolute values. + * morituri/test/test_image_toc.py: + Add Ladyhawke CDDB test, it has a data track. + 2009-05-16 Thomas Vander Stichele * examples/readdisc.py: diff --git a/examples/readdisc.py b/examples/readdisc.py index e688cbb..5e92e7f 100644 --- a/examples/readdisc.py +++ b/examples/readdisc.py @@ -229,6 +229,7 @@ def main(argv): assert ittoc.hasTOC() # already show us some info based on this + print "CDDB disc id", ittoc.getCDDBDiscId() metadata = musicbrainz(ittoc.getMusicBrainzDiscId()) # now, read the complete index table, which is slower @@ -241,6 +242,11 @@ def main(argv): assert itable.hasTOC() + assert itable.getCDDBDiscId() == ittoc.getCDDBDiscId(), \ + "full table's id %s differs from toc id %s" % ( + itable.getCDDBDiscId(), ittoc.getCDDBDiscId()) + assert itable.getMusicBrainzDiscId() == ittoc.getMusicBrainzDiscId() + lastTrackStart = 0 # check for hidden track one audio diff --git a/morituri/image/table.py b/morituri/image/table.py index b1ced8d..aa90d39 100644 --- a/morituri/image/table.py +++ b/morituri/image/table.py @@ -208,19 +208,28 @@ class Table(object, log.Loggable): #if self.getTrackStart(1) > 0: # delta = 0 + debug = [str(len(self.tracks))] for track in self.tracks: offset = self.getTrackStart(track.number) + delta + debug.append(str(offset)) seconds = offset / common.FRAMES_PER_SECOND n += self._cddbSum(seconds) last = self.tracks[-1] # the 'real' leadout, not offset by 150 frames + # print 'THOMAS: disc leadout', self.leadout leadout = self.getTrackEnd(last.number) + 1 + self.debug('leadout LBA: %d', leadout) startSeconds = self.getTrackStart(1) / common.FRAMES_PER_SECOND leadoutSeconds = leadout / common.FRAMES_PER_SECOND t = leadoutSeconds - startSeconds + debug.append(str(leadoutSeconds + 2)) # 2 is the 150 frame cddb offset value = (n % 0xff) << 24 | t << 8 | len(self.tracks) + + # compare this debug line to cd-discid output + self.debug('cddb disc id debug: %s', + " ".join(["%08x" % value, ] + debug)) return "%08x" % value @@ -457,20 +466,27 @@ class Table(object, log.Loggable): # the first cut is the deepest counter = index.counter + #for t in self.tracks: print t, t.indexes self.debug('absolutizing') while True: + track = self.tracks[t - 1] + index = track.getIndex(i) + assert track.number == t + assert index.number == i if index.counter is None: self.debug('Track %d, index %d has no counter', t, i) break if index.counter != counter: self.debug('Track %d, index %d has a different counter', t, i) break - track = self.tracks[t - 1] - index = track.getIndex(i) - assert track.number == t - assert index.number == i self.debug('Setting absolute offset %d on track %d, index %d', index.relative, t, i) + if index.absolute is not None: + if index.absolute != index.relative: + msg = 'Track %d, index %d had absolute %d,' \ + ' overriding with %d' % ( + t, i, index.absolute, index.relative) + raise ValueError(msg) index.absolute = index.relative try: t, i = self.getNextTrackIndex(t, i) diff --git a/morituri/image/toc.py b/morituri/image/toc.py index c537668..e2c1536 100644 --- a/morituri/image/toc.py +++ b/morituri/image/toc.py @@ -39,7 +39,7 @@ _CATALOG_RE = re.compile(r'^CATALOG "(?P\d+)"$') # records _TRACK_RE = re.compile(r""" ^TRACK # TRACK - \s(?P.+)$ # mode (AUDIO, MODEx/2xxx, ...) + \s(?P.+)$ # mode (AUDIO, MODE2_FORM_MIX, MODEx/2xxx, ...) """, re.VERBOSE) _ISRC_RE = re.compile(r'^ISRC "(?P\w+)"$') @@ -50,6 +50,13 @@ _SILENCE_RE = re.compile(r""" \s(?P.*)$ # pre-gap length """, re.VERBOSE) +# ZERO is used as pre-gap source when switching mode +_ZERO_RE = re.compile(r""" + ^ZERO # ZERO + \s(?P.+) # mode (AUDIO, MODEx/2xxx, ...) + \s(?P.*)$ # zero length +""", re.VERBOSE) + _FILE_RE = re.compile(r""" ^FILE # FILE @@ -58,6 +65,14 @@ _FILE_RE = re.compile(r""" \s(?P.+)$ # stop offset """, re.VERBOSE) +_DATAFILE_RE = re.compile(r""" + ^DATAFILE # DATA FILE + \s+"(?P.*)" # 'file name' in quotes + \s+(?P\S+) # start offset + \s*.* # possible // comment +""", re.VERBOSE) + + # FIXME: start can be 0 _START_RE = re.compile(r""" ^START # START @@ -83,7 +98,8 @@ class TocFile(object, log.Loggable): counter = 0 trackNumber = 0 indexNumber = 0 - currentOffset = 0 # running absolute offset of where each track starts + absoluteOffset = 0 # running absolute offset of where each track starts + relativeOffset = 0 # running relative offset, relative to counter source currentLength = 0 # accrued during TRACK record parsing, current track totalLength = 0 # accrued during TRACK record parsing, total disc pregapLength = 0 # length of the pre-gap, current track @@ -128,13 +144,16 @@ class TocFile(object, log.Loggable): # set index 1 of previous track if there was one, using # pregapLength if applicable if currentTrack: + # FIXME: why not set absolute offsets too ? currentTrack.index(1, path=currentFile.path, - relative=currentOffset + pregapLength, counter=counter) + absolute=absoluteOffset + pregapLength, + relative=relativeOffset + pregapLength, counter=counter) self.debug('track %d, added index %r', currentTrack.number, currentTrack.getIndex(1)) trackNumber += 1 - currentOffset += currentLength + absoluteOffset += currentLength + relativeOffset += currentLength totalLength += currentLength currentLength = 0 indexNumber = 1 @@ -142,7 +161,9 @@ class TocFile(object, log.Loggable): pregapLength = 0 # FIXME: track mode - currentTrack = table.Track(trackNumber) + self.debug('found track %d, mode %s', trackNumber, trackMode) + audio = trackMode == 'AUDIO' + currentTrack = table.Track(trackNumber, audio=audio) self.table.tracks.append(currentTrack) continue @@ -163,6 +184,16 @@ class TocFile(object, log.Loggable): length = m.group('length') currentLength += common.msfToFrames(length) + # look for ZERO lines + m = _ZERO_RE.search(line) + if m: + if currentFile is not None: + self.debug('ZERO after FILE, increasing counter') + counter += 1 + currentFile = None + length = m.group('length') + currentLength += common.msfToFrames(length) + # look for FILE lines m = _FILE_RE.search(line) if m: @@ -174,12 +205,32 @@ class TocFile(object, log.Loggable): common.msfToFrames(length)) if not currentFile or filePath != currentFile.path: counter += 1 + relativeOffset = 0 self.debug('track %d, switched to new FILE, increased counter to %d', trackNumber, counter) currentFile = File(filePath, start, length) - #currentOffset += common.msfToFrames(start) + #absoluteOffset += common.msfToFrames(start) currentLength += common.msfToFrames(length) + # look for DATAFILE lines + m = _DATAFILE_RE.search(line) + if m: + filePath = m.group('name') + length = m.group('length') + # print 'THOMAS', length + self.debug('FILE %s, length %r', + filePath, common.msfToFrames(length)) + if not currentFile or filePath != currentFile.path: + counter += 1 + relativeOffset = 0 + self.debug('track %d, switched to new FILE, increased counter to %d', + trackNumber, counter) + # FIXME: assume that a MODE2_FORM_MIX track always starts at 0 + currentFile = File(filePath, 0, length) + #absoluteOffset += common.msfToFrames(start) + currentLength += common.msfToFrames(length) + + # look for START lines m = _START_RE.search(line) if m: @@ -190,7 +241,8 @@ class TocFile(object, log.Loggable): length = common.msfToFrames(m.group('length')) currentTrack.index(0, path=currentFile.path, - relative=currentOffset, counter=counter) + absolute=absoluteOffset, + relative=relativeOffset, counter=counter) self.debug('track %d, added index %r', currentTrack.number, currentTrack.getIndex(0)) # store the pregapLength to add it when we index 1 for this @@ -215,7 +267,8 @@ class TocFile(object, log.Loggable): # handle index 1 of final track, if any if currentTrack: currentTrack.index(1, path=currentFile.path, - relative=currentOffset + pregapLength, counter=counter) + absolute=absoluteOffset + pregapLength, + relative=relativeOffset + pregapLength, counter=counter) self.debug('track %d, added index %r', currentTrack.number, currentTrack.getIndex(1)) diff --git a/morituri/program/cdrdao.py b/morituri/program/cdrdao.py index b7221e0..2ca7520 100644 --- a/morituri/program/cdrdao.py +++ b/morituri/program/cdrdao.py @@ -202,7 +202,8 @@ class CDRDAOTask(task.Task): bufsize=bufsize, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) - self.debug('Started cdrdao with pid %d', self._popen.pid) + self.debug('Started cdrdao with pid %d and options %r', + self._popen.pid, self.options) self.runner.schedule(1.0, self._read, runner) @@ -269,7 +270,8 @@ class ReadTableTask(CDRDAOTask): os.close(fd) os.unlink(self._tocfilepath) - self.options = ['read-toc', self._tocfilepath] + self.options = ['read-toc', '--session', '9', + self._tocfilepath, ] def readbytes(self, bytes): self.parser.read(bytes) @@ -318,7 +320,10 @@ class ReadTOCTask(CDRDAOTask): os.close(fd) os.unlink(self._toc) - self.options = ['read-toc', '--fast-toc', self._toc] + # Reading a non-existent session gives you output for all sessions + # 9 should be a safe number + self.options = ['read-toc', '--fast-toc', '--session', '9', + self._toc, ] def readbytes(self, bytes): self.parser.read(bytes) diff --git a/morituri/test/test_image_cue.py b/morituri/test/test_image_cue.py index 1e3d4a8..3fb3096 100644 --- a/morituri/test/test_image_cue.py +++ b/morituri/test/test_image_cue.py @@ -55,20 +55,19 @@ class WriteCueFileTestCase(unittest.TestCase): os.close(fd) it = table.Table() - t = table.Track(1) - t.index(1, path='track01.wav', relative=0, counter=1) + t.index(1, absolute=0, path='track01.wav', relative=0, counter=1) it.tracks.append(t) t = table.Track(2) - t.index(0, path='track01.wav', relative=1000, counter=1) - t.index(1, path='track02.wav', relative=0, counter=2) + t.index(0, absolute=1000, path='track01.wav', relative=1000, counter=1) + t.index(1, absolute=2000, path='track02.wav', relative=0, counter=2) it.tracks.append(t) it.absolutize() - it.leadout = 2000 + it.leadout = 3000 - self.assertEquals(it.cue(), """REM DISCID 04001A02 + self.assertEquals(it.cue(), """REM DISCID 0C002802 REM COMMENT "Morituri" FILE "track01.wav" WAVE TRACK 01 AUDIO diff --git a/morituri/test/test_image_toc.py b/morituri/test/test_image_toc.py index 1bac4b0..6c3dc4f 100644 --- a/morituri/test/test_image_toc.py +++ b/morituri/test/test_image_toc.py @@ -49,9 +49,9 @@ class CureTestCase(unittest.TestCase): self.assertEquals(index.relative, value) def testSetFile(self): - self._assertAbsolute(1, 1, None) - self._assertAbsolute(2, 0, None) - self._assertAbsolute(2, 1, None) + self._assertAbsolute(1, 1, 0) + self._assertAbsolute(2, 0, 28245) + self._assertAbsolute(2, 1, 28324) self._assertPath(1, 1, "data.wav") def dump(): @@ -118,8 +118,7 @@ class BlocTestCase(unittest.TestCase): # This disc has a pre-gap, so is a good test for .CUE writing def testConvertCue(self): - self.failIf(self.toc.table.hasTOC()) - self.toc.table.absolutize() + #self.toc.table.absolutize() self.failUnless(self.toc.table.hasTOC()) cue = self.toc.table.cue() ref = open(os.path.join(os.path.dirname(__file__), @@ -128,6 +127,10 @@ class BlocTestCase(unittest.TestCase): def testCDDBId(self): self.toc.table.absolutize() + # cd-discid output: + # ad0be00d 13 15370 35019 51532 69190 84292 96826 112527 132448 + # 148595 168072 185539 203331 222103 3244 + self.assertEquals(self.toc.table.getCDDBDiscId(), 'ad0be00d') def testAccurateRip(self): @@ -156,10 +159,27 @@ class BreedersTestCase(unittest.TestCase): self.assertEquals(cdt['TITLE'], 'OVERGLAZED') def testConvertCue(self): - self.failIf(self.toc.table.hasTOC()) self.toc.table.absolutize() self.failUnless(self.toc.table.hasTOC()) cue = self.toc.table.cue() ref = open(os.path.join(os.path.dirname(__file__), 'breeders.cue')).read() self.assertEquals(cue, ref) + +# Ladyhawke has a data track +class LadyhawkeTestCase(unittest.TestCase): + def setUp(self): + self.toc = toc.TocFile(os.path.join(os.path.dirname(__file__), + 'ladyhawke.toc')) + self.toc.parse() + self.assertEquals(len(self.toc.table.tracks), 13) + #import code; code.interact(local=locals()) + self.failIf(self.toc.table.tracks[-1].audio) + + def testCDDBId(self): + self.toc.table.absolutize() + self.assertEquals(self.toc.table.getCDDBDiscId(), 'c60af50d') + # output from cd-discid: + # c60af50d 13 150 15687 31841 51016 66616 81352 99559 116070 133243 + # 149997 161710 177832 207256 2807 +