[SCM] morituri/master: * morituri/rip/cd.py: Add asserts for comparing id's between the simple toc and the full table. Create the output directory before ripping the htoa. Ignore data tracks for now. Don't fail if we have no AccurateRip responses. * morituri/image/table.py: Add a session ivar to Track. Factor in session leadin when calculating track length of last track in a session. add getMusicBrainzSubmitURL() add _getSessionGap() because the session gap size is different for session 2 and all following. Use it in merge() to get offsets right. Fix getAccurateRipURL by only using the audio tracks for the 'length in tracks' number Temporarily disable writing out data tracks to a .cue file, since it's not implemented yet. Add canCue to see if we can write a .cue file from the given table, and debug why not if not. * morituri/program/cdrdao.py: Rework to rip each session separately instead of using session 9. This fixes session 9 read-toc missing the pregap. Add a simple LineParser for handling output from disk-info. Count tracks relatively for the session, because the output for session 2 for track numbers picks up where session 1 left off. Don't set leadout from TOC printing since for the same reason session 2's leadout is absolute, not relative to start of session. Add a DiscInfoTask. Convert Table and Toc reading tasks to multitasks, first getting the number of sessions, then reading table/toc for each session. * morituri/test/test_image_table.py: Fix up MusicBrainz disc id for my Ladyhawke disc. Add AccurateRip URL verification, compared against EAC's. * morituri/test/test_image_toc.py: Use two separate session read-toc output files to verify the case of Das Capital. Verify musicbrainz URL.

js at users.alioth.debian.org js at users.alioth.debian.org
Sun Oct 19 20:09:04 UTC 2014


The following commit has been merged in the master branch:
commit 262801e554578cadb1c1a7ac6f5a79039d0b5557
Author: Thomas Vander Stichele <thomas (at) apestaart (dot) org>
Date:   Mon May 25 14:59:45 2009 +0000

    	* morituri/rip/cd.py:
    	  Add asserts for comparing id's between the simple toc and
    	  the full table.
    	  Create the output directory before ripping the htoa.
    	  Ignore data tracks for now.
    	  Don't fail if we have no AccurateRip responses.
    	* morituri/image/table.py:
    	  Add a session ivar to Track.
    	  Factor in session leadin when calculating track length
    	  of last track in a session.
    	  add getMusicBrainzSubmitURL()
    	  add _getSessionGap() because the session gap size is different
    	  for session 2 and all following.
    	  Use it in merge() to get offsets right.
    	  Fix getAccurateRipURL by only using the audio tracks for the
    	  'length in tracks' number
    	  Temporarily disable writing out data tracks to a .cue file,
    	  since it's not implemented yet.
    	  Add canCue to see if we can write a .cue file from the given table,
    	  and debug why not if not.
    	* morituri/program/cdrdao.py:
    	  Rework to rip each session separately instead of using session 9.
    	  This fixes session 9 read-toc missing the pregap.
    	  Add a simple LineParser for handling output from disk-info.
    	  Count tracks relatively for the session, because the output for
    	  session 2 for track numbers picks up where session 1 left off.
    	  Don't set leadout from TOC printing since for the same reason
    	  session 2's leadout is absolute, not relative to start of session.
    	  Add a DiscInfoTask.
    	  Convert Table and Toc reading tasks to multitasks, first getting the
    	  number of sessions, then reading table/toc for each session.
    	* morituri/test/test_image_table.py:
    	  Fix up MusicBrainz disc id for my Ladyhawke disc.
    	  Add AccurateRip URL verification, compared against EAC's.
    	* morituri/test/test_image_toc.py:
    	  Use two separate session read-toc output files to verify
    	  the case of Das Capital.
    	  Verify musicbrainz URL.

diff --git a/ChangeLog b/ChangeLog
index 1e8fad6..f7760db 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,47 @@
 2009-05-25  Thomas Vander Stichele  <thomas at apestaart dot org>
 
+	* morituri/rip/cd.py:
+	  Add asserts for comparing id's between the simple toc and
+	  the full table.
+	  Create the output directory before ripping the htoa.
+	  Ignore data tracks for now.
+	  Don't fail if we have no AccurateRip responses.
+	* morituri/image/table.py:
+	  Add a session ivar to Track.
+	  Factor in session leadin when calculating track length
+	  of last track in a session.
+	  add getMusicBrainzSubmitURL()
+	  add _getSessionGap() because the session gap size is different
+	  for session 2 and all following.
+	  Use it in merge() to get offsets right.
+	  Fix getAccurateRipURL by only using the audio tracks for the
+	  'length in tracks' number
+	  Temporarily disable writing out data tracks to a .cue file,
+	  since it's not implemented yet.
+	  Add canCue to see if we can write a .cue file from the given table,
+	  and debug why not if not.
+	* morituri/program/cdrdao.py:
+	  Rework to rip each session separately instead of using session 9.
+	  This fixes session 9 read-toc missing the pregap.
+	  Add a simple LineParser for handling output from disk-info.
+	  Count tracks relatively for the session, because the output for
+	  session 2 for track numbers picks up where session 1 left off.
+	  Don't set leadout from TOC printing since for the same reason
+	  session 2's leadout is absolute, not relative to start of session.
+	  Add a DiscInfoTask.
+	  Convert Table and Toc reading tasks to multitasks, first getting the
+	  number of sessions, then reading table/toc for each session.
+
+	* morituri/test/test_image_table.py:
+	  Fix up MusicBrainz disc id for my Ladyhawke disc.
+	  Add AccurateRip URL verification, compared against EAC's.
+	* morituri/test/test_image_toc.py:
+	  Use two separate session read-toc output files to verify
+	  the case of Das Capital.
+	  Verify musicbrainz URL.
+
+2009-05-25  Thomas Vander Stichele  <thomas at apestaart dot org>
+
 	* morituri/common/task.py:
 	  Add documentation.
 	  Use a _task counter instead of duplicating tasks to
diff --git a/morituri/image/table.py b/morituri/image/table.py
index 9ccafc4..93eb1ac 100644
--- a/morituri/image/table.py
+++ b/morituri/image/table.py
@@ -25,6 +25,8 @@ Wrap Table of Contents.
 """
 
 import copy
+import urllib
+import urlparse
 
 from morituri.common import task, common, log
 
@@ -65,11 +67,12 @@ class Track:
     indexes = None
     isrc = None
     cdtext = None
+    session = None
 
     def __repr__(self):
         return '<Track %02d>' % self.number
 
-    def __init__(self, number, audio=True):
+    def __init__(self, number, audio=True, session=None):
         self.number = number
         self.audio = audio
         self.indexes = {}
@@ -123,7 +126,6 @@ class Table(object, log.Loggable):
     @type tracks: list of L{Track}
     @ivar catalog: catalog number
     @type catalog: str
-    @ivar version: version number of the object and its API.
     """
 
     tracks = None # list of Track
@@ -131,7 +133,7 @@ class Table(object, log.Loggable):
     catalog = None # catalog number; FIXME: is this UPC ?
     cdtext = None
 
-    version = 1
+    classVersion = 1
 
     def __init__(self, tracks=None):
         if not tracks:
@@ -139,6 +141,10 @@ class Table(object, log.Loggable):
 
         self.tracks = tracks
         self.cdtext = {}
+        self.logName = "Table 0x%08X" % id(self)
+        # done this way because just having a class-defined instance var
+        # gets overridden when unpickling
+        self.instanceVersion = self.classVersion
 
     def getTrackStart(self, number):
         """
@@ -159,9 +165,20 @@ class Table(object, log.Loggable):
         @returns: the end of the given track number (ie index 1 of next track)
         @rtype:   int
         """
+        # default to end of disc
         end = self.leadout - 1
+
+        # if not last track, calculate it from the next track
         if number < len(self.tracks):
             end = self.tracks[number].getIndex(1).absolute - 1
+
+            # if on a session border, subtract the session leadin
+            this = self.tracks[number - 1]
+            next = self.tracks[number]
+            if next.session > this.session:
+                gap = self._getSessionGap(next.session)
+                end -= gap
+
         return end
 
     def getTrackLength(self, number):
@@ -245,6 +262,8 @@ class Table(object, log.Loggable):
         @rtype:   str
         @returns: the 28-character base64-encoded disc ID
         """
+        values = self._getMusicBrainzValues()
+
         # MusicBrainz disc id does not take into account data tracks
         import sha
         import base64
@@ -252,26 +271,17 @@ class Table(object, log.Loggable):
         sha1 = sha.new()
 
         # number of first track
-        sha1.update("%02X" % 1)
+        sha1.update("%02X" % values[0])
 
         # number of last track
-        sha1.update("%02X" % self.getAudioTracks())
+        sha1.update("%02X" % values[1])
 
-        leadout = self.leadout
-        # if the disc is multi-session, last track is the data track,
-        # and we should subtract 11250 + 150 from the last track's offset
-        # for the leadout
-        if self.hasDataTracks():
-            assert not self.tracks[-1].audio
-            leadout = self.tracks[-1].getIndex(1).absolute - 11250 - 150
-
-        # treat leadout offset as track 0 offset
-        sha1.update("%08X" % (150 + leadout))
+        sha1.update("%08X" % values[2])
 
         # offsets of tracks
         for i in range(1, 100):
             try:
-                offset = self.tracks[i - 1].getIndex(1).absolute + 150
+                offset = values[2 + i]
             except IndexError:
                 #print 'track', i - 1, '0 offset'
                 offset = 0
@@ -295,6 +305,71 @@ class Table(object, log.Loggable):
 
         return result
 
+    def getMusicBrainzSubmitURL(self):
+        host = 'mm.musicbrainz.org'
+
+        discid = self.getMusicBrainzDiscId()
+        values = self._getMusicBrainzValues()
+
+        query = urllib.urlencode({
+            'id': discid,
+            'toc': ' '.join([str(v) for v in values]),
+            'tracks': self.getAudioTracks()
+        })
+
+        return urlparse.urlunparse((
+            'http', host, '/bare/cdlookup.html', '', query, ''))
+
+
+    def _getMusicBrainzValues(self):
+        """
+        Get all MusicBrainz values needed to calculate disc id and submit URL.
+
+        This includes:
+         - track number of first track
+         - number of audio tracks
+         - leadout of disc
+         - offset of index 1 of each track
+
+        @rtype:   list of int
+        """
+        # MusicBrainz disc id does not take into account data tracks
+
+        result = []
+
+        # number of first track
+        result.append(1)
+
+        # number of last audio track
+        result.append(self.getAudioTracks())
+
+        leadout = self.leadout
+        # if the disc is multi-session, last track is the data track,
+        # and we should subtract 11250 + 150 from the last track's offset
+        # for the leadout
+        if self.hasDataTracks():
+            assert not self.tracks[-1].audio
+            leadout = self.tracks[-1].getIndex(1).absolute - 11250 - 150
+
+        # treat leadout offset as track 0 offset
+        result.append(150 + leadout)
+
+        # offsets of tracks
+        for i in range(1, 100):
+            try:
+                track = self.tracks[i - 1]
+                if not track.audio:
+                    continue
+                offset = track.getIndex(1).absolute + 150
+                result.append(offset)
+            except IndexError:
+                pass
+
+
+        self.debug('Musicbrainz values: %r', result)
+        return result
+
+
     def getAccurateRipIds(self):
         """
         Calculate the two AccurateRip ID's.
@@ -338,7 +413,7 @@ class Table(object, log.Loggable):
         return "http://www.accuraterip.com/accuraterip/" \
             "%s/%s/%s/dBAR-%.3d-%s-%s-%s.bin" % ( 
                 discId1[-1], discId1[-2], discId1[-3],
-                len(self.tracks), discId1, discId2, self.getCDDBDiscId())
+                self.getAudioTracks(), discId1, discId2, self.getCDDBDiscId())
 
     def cue(self, program='Morituri'):
         """
@@ -370,6 +445,10 @@ class Table(object, log.Loggable):
         lines.append('FILE "%s" WAVE' % path)
 
         for i, track in enumerate(self.tracks):
+            # FIXME: skip data tracks for now
+            if not track.audio:
+                continue
+
             # if there is no index 0, but there is a new file, advance
             # FILE line here
             if not track.indexes.has_key(0):
@@ -503,18 +582,7 @@ class Table(object, log.Loggable):
 
         @type  other: L{Table}
         """
-        # From cdrecord multi-session info:
-        # For the first additional session this is 11250 sectors
-        # lead-out/lead-in overhead + 150 sectors for the pre-gap of the first
-        # track after the lead-in = 11400 sectos.
-
-        # For all further session this is 6750 sectors lead-out/lead-in
-        # overhead + 150 sectors for the pre-gap of the first track after the
-        # lead-in = 6900 sectors.
-
-        gap = 11400
-        if session > 2:
-            gap = 6900
+        gap = self._getSessionGap(session)
 
         trackCount = len(self.tracks)
         sourceCounter = self.tracks[-1].getLastIndex().counter
@@ -522,6 +590,7 @@ class Table(object, log.Loggable):
         for track in other.tracks:
             t = copy.deepcopy(track)
             t.number = track.number + trackCount
+            t.session = session
             for i in t.indexes.values():
                 if i.absolute is not None:
                     i.absolute += self.leadout + gap
@@ -534,6 +603,22 @@ class Table(object, log.Loggable):
             self.tracks.append(t)
 
         self.leadout += other.leadout + gap # FIXME
+        self.debug('Fixing leadout, now %d', self.leadout)
+
+    def _getSessionGap(self, session):
+        # From cdrecord multi-session info:
+        # For the first additional session this is 11250 sectors
+        # lead-out/lead-in overhead + 150 sectors for the pre-gap of the first
+        # track after the lead-in = 11400 sectos.
+
+        # For all further session this is 6750 sectors lead-out/lead-in
+        # overhead + 150 sectors for the pre-gap of the first track after the
+        # lead-in = 6900 sectors.
+
+        gap = 11400
+        if session > 2:
+            gap = 6900
+        return gap
 
     ### lookups
     def getNextTrackIndex(self, track, index):
@@ -584,3 +669,20 @@ class Table(object, log.Loggable):
                 return False
 
         return True
+
+    def canCue(self):
+        """
+        Check if this table can be used to generate a .cue file
+        """
+        if not self.hasTOC():
+            self.debug('No TOC, cannot cue')
+            return False
+
+        for t in self.tracks:
+            for i in t.indexes.values():
+                if i.relative is None:
+                    self.debug('Track %02d, Index %02d does not have relative',
+                        t.number, i.number)
+                    return False
+
+        return True
diff --git a/morituri/program/cdrdao.py b/morituri/program/cdrdao.py
index aae36f3..c83b827 100644
--- a/morituri/program/cdrdao.py
+++ b/morituri/program/cdrdao.py
@@ -67,9 +67,41 @@ _POSITION_RE = re.compile(r"""
     (?P<ss>\d\d)         # SS
 """, re.VERBOSE)
 
+class LineParser(object, log.Loggable):
+    """
+    Parse incoming bytes into lines
+    Calls 'parse' on owner for each parsed line.
+    """
+    def __init__(self, owner):
+        self._buffer = ""     # accumulate characters
+        self._lines = []      # accumulate lines
+        self._owner = owner
+
+    def read(self, bytes):
+        self.log('received %d bytes', len(bytes))
+        self._buffer += bytes
+
+        # parse buffer into lines if possible, and parse them
+        if "\n" in self._buffer:
+            self.log('buffer has newline, splitting')
+            lines = self._buffer.split('\n')
+            if lines[-1] != "\n":
+                # last line didn't end yet
+                self.log('last line still in progress')
+                self._buffer = lines[-1]
+                del lines[-1]
+            else:
+                self.log('last line finished, resetting buffer')
+                self._buffer = ""
+
+            for line in lines:
+                self.log('Parsing %s', line)
+                self._owner.parse(line)
+
+            self._lines.extend(lines)
 
 class OutputParser(object, log.Loggable):
-    def __init__(self, taskk):
+    def __init__(self, taskk, session=None):
         self._buffer = ""     # accumulate characters
         self._lines = []      # accumulate lines
         self._errors = []     # accumulate error lines
@@ -77,6 +109,8 @@ class OutputParser(object, log.Loggable):
         self._frames = None   # number of frames
         self._track = None    # which track are we analyzing?
         self._task = taskk
+        self._tracks = 0      # count of tracks, relative to session
+        self._session = session
 
         self.table = table.Table() # the index table for the TOC
 
@@ -154,20 +188,21 @@ class OutputParser(object, log.Loggable):
 
         m = _TRACK_RE.search(line)
         if m:
-            self._tracks = int(m.group('track'))
-            track = table.Track(self._tracks)
+            t = int(m.group('track'))
+            self._tracks += 1
+            track = table.Track(self._tracks, session=self._session)
             track.index(1, absolute=int(m.group('start')))
             self.table.tracks.append(track)
-            self.debug('Found track %d', self._tracks)
+            self.debug('Found absolute track %d, session-relative %d', t,
+                self._tracks)
 
         m = _LEADOUT_RE.search(line)
         if m:
             self.debug('Found leadout line, moving to LEADOUT state')
             self._state = 'LEADOUT'
             self._frames = int(m.group('start'))
-            self.debug('Found leadout at offset %r', self._frames)
-            self.table.leadout = self._frames
-            self.info('%d tracks found', self._tracks)
+            self.debug('Found absolute leadout at offset %r', self._frames)
+            self.info('%d tracks found for this session', self._tracks)
             return
 
     def _parse_LEADOUT(self, line):
@@ -208,11 +243,17 @@ class CDRDAOTask(task.Task):
         self.runner.schedule(1.0, self._read, runner)
 
     def _read(self, runner):
+        ret = self._popen.recv()
+
+        if ret:
+            self.log("read from stdout: %s", ret)
+            self.readbytesout(ret)
+
         ret = self._popen.recv_err()
 
         if ret:
             self.log("read from stderr: %s", ret)
-            self.readbytes(ret)
+            self.readbyteserr(ret)
 
         if self._popen.poll() is None:
             # not finished yet
@@ -239,11 +280,17 @@ class CDRDAOTask(task.Task):
         os.kill(self._popen.pid, signal.SIGTERM)
         self.stop()
 
-    def readbytes(self, bytes):
+    def readbytesout(self, bytes):
+        """
+        Called when bytes have been read from stdout.
+        """
+        pass
+
+    def readbyteserr(self, bytes):
         """
         Called when bytes have been read from stderr.
         """
-        raise NotImplementedError
+        pass
 
     def done(self):
         """
@@ -251,39 +298,86 @@ class CDRDAOTask(task.Task):
         """
         raise NotImplementedError
 
+class DiscInfoTask(CDRDAOTask):
+    """
+    I am a task that reads information about a disc.
+
+    @ivar sessions: the number of sessions
+    @type sessions: int
+    """
+
+    description = "Scanning disc..."
+    table = None
+
+    def __init__(self, device=None):
+        """
+        @param device:  the device to rip from
+        @type  device:  str
+        """
+        CDRDAOTask.__init__(self)
+
+        self.options = ['disk-info', ]
+        if device:
+            self.options.extend(['--device', device, ])
+
+        self.parser = LineParser(self)
+
+    def readbytesout(self, bytes):
+        self.parser.read(bytes)
+
+    def parse(self, line):
+        # called by parser
+        if line.startswith('Sessions'):
+            self.sessions = int (line[line.find(':') + 1:])
+            self.debug('Found %d sessions', self.sessions)
+
+    def done(self):
+        pass
+
 
-class ReadTableTask(CDRDAOTask):
+# Read stuff for one session
+class ReadSessionTask(CDRDAOTask):
     """
-    I am a task that reads all indexes of a CD.
+    I am a task that reads things for one session.
 
     @ivar table: the index table
     @type table: L{table.Table}
     """
 
-    description = "Scanning indexes..."
+    description = "Reading session"
     table = None
+    extraOptions = None
 
-    def __init__(self, device=None):
+    def __init__(self, session=None, device=None):
         """
-        @param device: the device to rip from
-        @type  device: str
+        @param session: the session to read
+        @type  session: int
+        @param device:  the device to rip from
+        @type  device:  str
         """
         CDRDAOTask.__init__(self)
         self.parser = OutputParser(self)
-        (fd, self._tocfilepath) = tempfile.mkstemp(suffix='.morituri')
+        (fd, self._tocfilepath) = tempfile.mkstemp(
+            suffix='.readtablesession.morituri')
         os.close(fd)
         os.unlink(self._tocfilepath)
 
         self.options = ['read-toc', ]
         if device:
             self.options.extend(['--device', device, ])
-        self.options.extend(['--session', '9', self._tocfilepath, ])
+        if session:
+            self.options.extend(['--session', str(session)])
+            self.description = "%s of session %d..." % (
+                self.description, session)
+        if self.extraOptions:
+            self.options.extend(self.extraOptions)
+
+        self.options.extend([self._tocfilepath, ])
 
-    def readbytes(self, bytes):
+    def readbyteserr(self, bytes):
         self.parser.read(bytes)
 
     def done(self):
-        # FIXME: instead of reading only a TOC, output a complete Table
         # by merging the TOC info.
         self._tocfile = toc.TocFile(self._tocfilepath)
         self._tocfile.parse()
@@ -302,12 +396,23 @@ class ReadTableTask(CDRDAOTask):
         # copy the leadout from the parser's table
         # FIXME: how do we get the length of the last audio track in the case
         # of a data track ?
-        self.table.leadout = self.parser.table.leadout
+        # self.table.leadout = self.parser.table.leadout
 
         # we should have parsed it from the initial output
         assert self.table.leadout is not None
 
-class ReadTOCTask(CDRDAOTask):
+
+class ReadTableSessionTask(ReadSessionTask):
+    """
+    I am a task that reads all indexes of a CD for a session.
+
+    @ivar table: the index table
+    @type table: L{table.Table}
+    """
+
+    description = "Scanning indexes"
+
+class ReadTOCSessionTask(ReadSessionTask):
     """
     I am a task that reads the TOC of a CD, without pregaps.
 
@@ -315,33 +420,73 @@ class ReadTOCTask(CDRDAOTask):
     @type table: L{table.Table}
     """
 
-    description = "Reading TOC..."
+    description = "Reading TOC"
+    extraOptions = ['--fast-toc', ]
+
+    def done(self):
+        ReadSessionTask.done(self)
+
+        assert self.table.hasTOC(), "This Table Index should be a TOC"
+
+# read all sessions
+class ReadAllSessionsTask(task.MultiSeparateTask):
+    """
+    I am a base class for tasks that need to read all sessions.
+
+    @ivar table: the index table
+    @type table: L{table.Table}
+    """
+
     table = None
+    _readClass = None
 
     def __init__(self, device=None):
         """
-        @param device: the device to rip from
-        @type  device: str
+        @param device:  the device to rip from
+        @type  device:  str
         """
-        CDRDAOTask.__init__(self)
-        self.parser = OutputParser(self)
+        task.MultiSeparateTask.__init__(self)
 
-        (fd, self._toc) = tempfile.mkstemp(suffix='.morituri')
-        os.close(fd)
-        os.unlink(self._toc)
+        self._device = device
 
-        # Reading a non-existent session gives you output for all sessions
-        # 9 should be a safe number
-        self.options = ['read-toc', '--fast-toc', ]
-        if device:
-            self.options.extend(['--device', device, ])
-        self.options.extend(['--session', '9', self._toc, ])
+        self.tasks = [DiscInfoTask(device=device), ]
 
-    def readbytes(self, bytes):
-        self.parser.read(bytes)
+    def stopped(self, taskk):
+        # After first task, schedule additional ones
+        if taskk == self.tasks[0]:
+            for i in range(taskk.sessions):
+                self.tasks.append(self._readClass(session=i + 1,
+                    device=self._device))
 
-    def done(self):
-        os.unlink(self._toc)
-        self.table = self.parser.table
+        if self._task == len(self.tasks):
+            self.table = self.tasks[1].table
+            if len(self.tasks) > 2:
+                for i, t in enumerate(self.tasks[2:]):
+                    self.table.merge(t.table, i + 2)
 
-        assert self.table.hasTOC(), "This Table Index should be a TOC"
+            assert self.table.leadout is not None
+
+        task.MultiSeparateTask.stopped(self, taskk)
+
+
+class ReadTableTask(ReadAllSessionsTask):
+    """
+    I am a task that reads all indexes of a CD for all sessions.
+
+    @ivar table: the index table
+    @type table: L{table.Table}
+    """
+
+    description = "Scanning indexes..."
+    _readClass = ReadTableSessionTask
+
+class ReadTOCTask(ReadAllSessionsTask):
+    """
+    I am a task that reads the TOC of a CD, without pregaps.
+
+    @ivar table: the index table that matches the TOC.
+    @type table: L{table.Table}
+    """
+
+    description = "Reading TOC..."
+    _readClass = ReadTOCSessionTask
diff --git a/morituri/rip/cd.py b/morituri/rip/cd.py
index 0c57f8e..260de6f 100644
--- a/morituri/rip/cd.py
+++ b/morituri/rip/cd.py
@@ -150,8 +150,12 @@ def getPath(outdir, template, metadata, i):
         v['A'] = filterForPath(metadata.artist)
         v['d'] = filterForPath(metadata.title)
         if i >= 0:
-            v['a'] = filterForPath(metadata.tracks[i].artist)
-            v['n'] = filterForPath(metadata.tracks[i].title)
+            try:
+                v['a'] = filterForPath(metadata.tracks[i - 1].artist)
+                v['n'] = filterForPath(metadata.tracks[i - 1].title)
+            except IndexError, e:
+                print 'ERROR: no track %d found, %r' % (i, e)
+                raise
         else:
             # htoa defaults to disc's artist
             v['a'] = filterForPath(metadata.artist)
@@ -213,13 +217,12 @@ class Rip(logcommand.LogCommand):
 
         # already show us some info based on this
         print "CDDB disc id", ittoc.getCDDBDiscId()
-        metadata = musicbrainz(ittoc.getMusicBrainzDiscId())
-
-        url = ittoc.getAccurateRipURL()
-        print "AccurateRip URL", url
+        print "MusicBrainz disc id", ittoc.getMusicBrainzDiscId()
 
-        cache = accurip.AccuCache()
-        responses = cache.retrieve(url)
+        metadata = musicbrainz(ittoc.getMusicBrainzDiscId())
+        if not metadata:
+            print 'Submit this disc to MusicBrainz at:'
+            print ittoc.getMusicBrainzSubmitURL()
 
         # now, read the complete index table, which is slower
         path = os.path.join(os.path.expanduser('~'), '.morituri', 'cache',
@@ -237,7 +240,12 @@ class Rip(logcommand.LogCommand):
         assert itable.getCDDBDiscId() == ittoc.getCDDBDiscId(), \
             "full table's id %s differs from toc id %s" % (
                 itable.getCDDBDiscId(), ittoc.getCDDBDiscId())
-        assert itable.getMusicBrainzDiscId() == ittoc.getMusicBrainzDiscId()
+        assert itable.getMusicBrainzDiscId() == ittoc.getMusicBrainzDiscId(), \
+            "full table's mb id %s differs from toc id mb %s" % (
+            itable.getMusicBrainzDiscId(), ittoc.getMusicBrainzDiscId())
+        assert itable.getAccurateRipURL() == ittoc.getAccurateRipURL(), \
+            "full table's AR URL %s differs from toc AR URL %s" % (
+            itable.getAccurateRipURL(), ittoc.getAccurateRipURL())
 
         outdir = self.options.output_directory or os.getcwd()
 
@@ -257,6 +265,10 @@ class Rip(logcommand.LogCommand):
                 
             # rip it
             htoapath = getPath(outdir, self.options.track_template, metadata, -1) + '.wav'
+            dirname = os.path.dirname(htoapath)
+            if not os.path.exists(dirname):
+                os.makedirs(dirname)
+
             htoalength = stop - start
             if not os.path.exists(htoapath):
                 print 'Ripping track %d: %s' % (0, os.path.basename(htoapath))
@@ -265,7 +277,7 @@ class Rip(logcommand.LogCommand):
                     offset=int(self.options.offset),
                     device=self.parentCommand.options.device)
                 function(runner, t)
-                if t.checksum:
+                if t.checksum is not None:
                     print 'Checksums match for track %d' % 0
                 else:
                     print 'ERROR: checksums did not match for track %d' % 0
@@ -274,6 +286,12 @@ class Rip(logcommand.LogCommand):
 
 
         for i, track in enumerate(itable.tracks):
+            # FIXME: rip data tracks differently
+            if not track.audio:
+                # FIXME: make it work for now
+                track.indexes[1].relative = 0
+                continue
+
             path = getPath(outdir, self.options.track_template, metadata, i) + '.wav'
             dirname = os.path.dirname(path)
             if not os.path.exists(dirname):
@@ -305,6 +323,7 @@ class Rip(logcommand.LogCommand):
             os.makedirs(dirname)
 
         # write .cue file
+        assert itable.canCue()
         cuePath = '%s.cue' % discName
         handle = open(cuePath, 'w')
         handle.write(itable.cue())
@@ -329,13 +348,12 @@ class Rip(logcommand.LogCommand):
         handle.close()
 
         # verify using accuraterip
-        print "CDDB disc id", itable.getCDDBDiscId()
-        print "MusicBrainz disc id", itable.getMusicBrainzDiscId()
-        url = itable.getAccurateRipURL()
+        url = ittoc.getAccurateRipURL()
         print "AccurateRip URL", url
 
         cache = accurip.AccuCache()
         responses = cache.retrieve(url)
+
         if not responses:
             print 'Album not found in AccurateRip database'
 
@@ -364,7 +382,7 @@ class Rip(logcommand.LogCommand):
             confidence = None
 
             # match against each response's checksum
-            for j, r in enumerate(responses):
+            for j, r in enumerate(responses or []):
                 if "%08x" % csum == r.checksums[i]:
                     if not response:
                         response = r
diff --git a/morituri/test/test_image_table.py b/morituri/test/test_image_table.py
index 8aea419..3b4e1e4 100644
--- a/morituri/test/test_image_table.py
+++ b/morituri/test/test_image_table.py
@@ -1,9 +1,11 @@
 # -*- Mode: Python; test-case-name: morituri.test.test_image_table -*-
 # vi:si:et:sw=4:sts=4:ts=4
 
+from morituri.image import table
+
 import unittest
 
-from morituri.image import table
+from morituri.test import common
 
 def h(i):
     return "0x%08x" % i
@@ -39,13 +41,18 @@ class LadyhawkeTestCase(unittest.TestCase):
         self.assertEquals(self.table.getCDDBDiscId(), "c60af50d")
 
     def testMusicBrainz(self):
-        # track
+        # output from mb-submit-disc:
+        # http://mm.musicbrainz.org/bare/cdlookup.html?toc=1+12+195856+150+15687+31841+51016+66616+81352+99559+116070+133243+149997+161710+177832&tracks=12&id=KnpGsLhvH.lPrNc1PBL21lb9Bg4-
+        # however, not (yet) in musicbrainz database
+
         self.assertEquals(self.table.getMusicBrainzDiscId(),
-            "qrJJkrLvXz5Nkvym3oZM4KI9U4A-")
+            "KnpGsLhvH.lPrNc1PBL21lb9Bg4-")
 
     def testAccurateRip(self):
         self.assertEquals(self.table.getAccurateRipIds(), (
             "0013bd5a", "00b8d489"))
+        self.assertEquals(self.table.getAccurateRipURL(),
+        "http://www.accuraterip.com/accuraterip/a/5/d/dBAR-012-0013bd5a-00b8d489-c60af50d.bin")
 
 class MusicBrainzTestCase(unittest.TestCase):
     # example taken from http://musicbrainz.org/doc/DiscIDCalculation
diff --git a/morituri/test/test_image_toc.py b/morituri/test/test_image_toc.py
index 6b6c6aa..31502fd 100644
--- a/morituri/test/test_image_toc.py
+++ b/morituri/test/test_image_toc.py
@@ -179,24 +179,6 @@ class LadyhawkeTestCase(unittest.TestCase):
         # c60af50d 13 150 15687 31841 51016 66616 81352 99559 116070 133243
         # 149997 161710 177832 207256 2807
 
-# Das Capital has a htoa and a data track
-# the fast.toc was generated with cdrdao read-toc --fast-toc --session 9
-class CapitalTestCase(unittest.TestCase):
-    def setUp(self):
-        self.toc = toc.TocFile(os.path.join(os.path.dirname(__file__),
-            'capital.fast.toc'))
-        self.toc.parse()
-        self.assertEquals(len(self.toc.table.tracks), 12)
-        #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(), 'b910140c')
-        # output from cd-discid:
-        # b910140c 12 24320 44855 64090 77885 88095 104020 118245 129255 141765 164487 181780 209250 4440
-    testCDDBId.skip = 'not implemented yet'
-
 class CapitalMergeTestCase(unittest.TestCase):
     def setUp(self):
         self.toc1 = toc.TocFile(os.path.join(os.path.dirname(__file__),
@@ -220,5 +202,9 @@ class CapitalMergeTestCase(unittest.TestCase):
         self.assertEquals(self.table.getCDDBDiscId(), 'b910140c')
         # output from cd-discid:
         # b910140c 12 24320 44855 64090 77885 88095 104020 118245 129255 141765 164487 181780 209250 4440
-    testCDDBId.skip = 'not implemented yet'
+
+    def testMusicBrainz(self):
+        # URL to submit: http://mm.musicbrainz.org/bare/cdlookup.html?toc=1+11+197850+24320+44855+64090+77885+88095+104020+118245+129255+141765+164487+181780&tracks=11&id=MAj3xXf6QMy7G.BIFOyHyq4MySE-
+        self.assertEquals(self.table.getMusicBrainzDiscId(),
+            "MAj3xXf6QMy7G.BIFOyHyq4MySE-")
 

-- 
morituri packaging



More information about the pkg-multimedia-commits mailing list