[SCM] morituri/master: * morituri/image/cue.py: * morituri/image/image.py: * morituri/image/table.py: * morituri/program/cdparanoia.py: * morituri/test/test_image_cue.py: Move to using a shared IndexTable for everything. Sadly mixed with a MultiTask rename.

js at users.alioth.debian.org js at users.alioth.debian.org
Sun Oct 19 20:08:55 UTC 2014


The following commit has been merged in the master branch:
commit 22e81b46658df97e3a680ae57bc89e634cf7ace0
Author: Thomas Vander Stichele <thomas (at) apestaart (dot) org>
Date:   Mon May 4 08:40:42 2009 +0000

    	* morituri/image/cue.py:
    	* morituri/image/image.py:
    	* morituri/image/table.py:
    	* morituri/program/cdparanoia.py:
    	* morituri/test/test_image_cue.py:
    	  Move to using a shared IndexTable for everything.
    	  Sadly mixed with a MultiTask rename.

diff --git a/ChangeLog b/ChangeLog
index c67e64f..4a249eb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,15 @@
 2009-05-04  Thomas Vander Stichele  <thomas at apestaart dot org>
 
+	* morituri/image/cue.py:
+	* morituri/image/image.py:
+	* morituri/image/table.py:
+	* morituri/program/cdparanoia.py:
+	* morituri/test/test_image_cue.py:
+	  Move to using a shared IndexTable for everything.
+	  Sadly mixed with a MultiTask rename.
+
+2009-05-04  Thomas Vander Stichele  <thomas at apestaart dot org>
+
 	* morituri/common/checksum.py:
 	  Update debugging.  Add repr.
 
diff --git a/morituri/image/cue.py b/morituri/image/cue.py
index 5e1ba7b..8407d70 100644
--- a/morituri/image/cue.py
+++ b/morituri/image/cue.py
@@ -29,7 +29,8 @@ See http://digitalx.org/cuesheetsyntax.php
 import os
 import re
 
-from morituri.common import common
+from morituri.common import common, log
+from morituri.image import table
 
 _REM_RE = re.compile("^REM\s(\w+)\s(.*)$")
 _PERFORMER_RE = re.compile("^PERFORMER\s(.*)$")
@@ -56,18 +57,21 @@ _INDEX_RE = re.compile(r"""
 """, re.VERBOSE)
 
 
-class Cue:
+class Cue(object, log.Loggable):
     def __init__(self, path):
         self._path = path
         self._rems = {}
         self._messages = []
         self.tracks = []
+        self.leadout = None
 
     def parse(self):
         state = 'HEADER'
         currentFile = None
         currentTrack = None
+        counter = 0
 
+        self.info('Parsing .cue file %s', self._path)
         handle = open(self._path, 'r')
 
         for number, line in enumerate(handle.readlines()):
@@ -86,6 +90,7 @@ class Cue:
             # look for FILE lines
             m = _FILE_RE.search(line)
             if m:
+                counter += 1
                 filePath = m.group('name')
                 fileFormat = m.group('format')
                 currentFile = File(filePath, fileFormat)
@@ -102,7 +107,8 @@ class Cue:
                 trackNumber = int(m.group('track'))
                 trackMode = m.group('mode')
 
-                currentTrack = Track(trackNumber)
+                self.debug('found track %d', trackNumber)
+                currentTrack = table.ITTrack(trackNumber)
                 self.tracks.append(currentTrack)
                 continue
 
@@ -118,37 +124,15 @@ class Cue:
                 minutes = int(m.expand('\\2'))
                 seconds = int(m.expand('\\3'))
                 frames = int(m.expand('\\4'))
+                self.debug('found index %d', indexNumber)
 
                 frameOffset = frames + seconds * 75 + minutes * 75 * 60
-                currentTrack.index(indexNumber, frameOffset, currentFile)
-                # print 'index %d, offset %d of track %r' % (indexNumber, frameOffset, currentTrack)
+                # FIXME: what do we do about File's FORMAT ?
+                currentTrack.index(indexNumber,
+                    path=currentFile.path, relative=frameOffset,
+                    counter=counter)
                 continue
 
-    def dump(self):
-        """
-        Dump our internal representation to a .cue file content.
-        """
-        lines = []
-        currentFile = None
-
-        for i, track in enumerate(self.tracks):
-            indexes = track._indexes.keys()
-            indexes.sort()
-            index, file = track._indexes[indexes[0]]
-            if file != currentFile:
-                lines.append('FILE "%s" WAVE' % file.path)
-                currentFile = file
-            lines.append("  TRACK %02d %s" % (i + 1, 'AUDIO'))
-            for index in indexes:
-                (offset, file) = track._indexes[index]
-                if file != currentFile:
-                    lines.append('FILE "%s" WAVE' % file.path)
-                lines.append(
-                    "    INDEX %02d %s" % (index, common.framesToMSF(offset)))
-
-        lines.append("")
-        return "\n".join(lines) 
-
     def message(self, number, message):
         """
         Add a message about a given line in the cue file.
@@ -166,11 +150,13 @@ class Cue:
             # last track, so no length known
             return -1
 
-        thisIndex = track._indexes[1] # FIXME: could be more
-        nextIndex = self.tracks[i + 1]._indexes[1] # FIXME: could be 0
+        thisIndex = track.indexes[1] # FIXME: could be more
+        nextIndex = self.tracks[i + 1].indexes[1] # FIXME: could be 0
 
-        if thisIndex[1] == nextIndex[1]: # same file
-            return nextIndex[0] - thisIndex[0]
+        c = thisIndex.counter
+        if c is not None and c == nextIndex.counter:
+            # they belong to the same source, so their relative delta is length
+            return nextIndex.relative - thisIndex.relative
 
         # FIXME: more logic
         return -1
@@ -217,49 +203,3 @@ class File:
 
     def __repr__(self):
         return '<File "%s" of format %s>' % (self.path, self.format)
-
-
-# FIXME: add type ? separate AUDIO from others
-class Track:
-    """
-    I represent a track in a cue file.
-    I have index points.
-    Each index point is linked to an audio file.
-
-    @ivar number: track number
-    @type number: int
-    """
-
-    def __init__(self, number):
-        if number < 1 or number > 99:
-            raise IndexError, "Track number must be from 1 to 99"
-
-        self.number = number
-        self._indexes = {} # index number -> (sector, File)
-
-        self.title = None
-        self.performer = None
-
-    def __repr__(self):
-        return '<Track %02d with %d indexes>' % (self.number,
-            len(self._indexes.keys()))
-
-    def index(self, number, sector, file):
-        """
-        Add the given index to the current track.
-
-        @type file: L{File}
-        """
-        if number in self._indexes.keys():
-            raise KeyError, "index %d already in track %d" % (
-                number, self.number)
-        if number < 0 or number > 99:
-            raise IndexError, "Index number must be from 0 to 99"
-
-        self._indexes[number] = (sector, file)
-
-    def getIndex(self, number):
-        """
-        @rtype: tuple of (int, File)
-        """
-        return self._indexes[number]
diff --git a/morituri/image/image.py b/morituri/image/image.py
index bb0570c..decfccf 100644
--- a/morituri/image/image.py
+++ b/morituri/image/image.py
@@ -32,6 +32,8 @@ import gst
 from morituri.common import task, checksum, log
 from morituri.image import cue, table
 
+from morituri.test import common
+
 class Image(object, log.Loggable):
     """
     @ivar table: The Table of Contents for this image.
@@ -47,6 +49,7 @@ class Image(object, log.Loggable):
         self.cue.parse()
         self._offsets = [] # 0 .. trackCount - 1
         self._lengths = [] # 0 .. trackCount - 1
+
         self.table = None
 
     def getRealPath(self, path):
@@ -60,6 +63,7 @@ class Image(object, log.Loggable):
         Do initial setup, like figuring out track lengths, and
         constructing the Table of Contents.
         """
+        self.debug('setup image start')
         verify = ImageVerifyTask(self)
         self.debug('verifying image')
         runner.run(verify)
@@ -69,7 +73,7 @@ class Image(object, log.Loggable):
 
         # CD's have a standard lead-in time of 2 seconds;
         # checksums that use it should add it there
-        offset = self.cue.tracks[0].getIndex(1)[0]
+        offset = self.cue.tracks[0].getIndex(1).relative
 
         tracks = []
 
@@ -77,14 +81,21 @@ class Image(object, log.Loggable):
             length = self.cue.getTrackLength(self.cue.tracks[i])
             if length == -1:
                 length = verify.lengths[i + 1]
-            tracks.append(table.Track(i + 1, offset, offset + length - 1))
+            t = table.ITTrack(i + 1, audio=True)
+            tracks.append(t)
+            # FIXME: this probably only works for non-compliant .CUE files
+            # where pregap is put at end of previous file
+            t.index(1, absolute=offset, path=self.cue.tracks[i].getIndex(1).path,
+                relative=0)
 
             offset += length
 
-        self.table = table.Table(tracks)
+        self.table = table.IndexTable(tracks)
+        self.table.leadout = offset
+        self.debug('setup image done')
 
 
-class AccurateRipChecksumTask(task.MultiTask):
+class AccurateRipChecksumTask(task.MultiSeparateTask):
     """
     I calculate the AccurateRip checksums of all tracks.
     """
@@ -96,22 +107,22 @@ class AccurateRipChecksumTask(task.MultiTask):
         cue = image.cue
         self.checksums = []
 
+        self.debug('Checksumming %d tracks' % len(cue.tracks))
         for trackIndex, track in enumerate(cue.tracks):
-            index = track._indexes[1]
+            index = track.indexes[1]
             length = cue.getTrackLength(track)
-            file = index[1]
-            offset = index[0]
+            self.debug('track %d has length %d' % (trackIndex + 1, length))
 
-            path = image.getRealPath(file.path)
+            path = image.getRealPath(index.path)
             checksumTask = checksum.AccurateRipChecksumTask(path,
                 trackNumber=trackIndex + 1, trackCount=len(cue.tracks),
-                frameStart=offset * checksum.SAMPLES_PER_FRAME,
+                frameStart=index.relative * checksum.SAMPLES_PER_FRAME,
                 frameLength=length * checksum.SAMPLES_PER_FRAME)
             self.addTask(checksumTask)
 
     def stop(self):
         self.checksums = [t.checksum for t in self.tasks]
-        task.MultiTask.stop(self)
+        task.MultiSeparateTask.stop(self)
 
 class AudioLengthTask(task.Task):
     """
@@ -151,7 +162,7 @@ class AudioLengthTask(task.Task):
         
         self.stop()
 
-class ImageVerifyTask(task.MultiTask):
+class ImageVerifyTask(task.MultiSeparateTask):
     """
     I verify a disk image and get the necessary track lengths.
     """
@@ -167,13 +178,11 @@ class ImageVerifyTask(task.MultiTask):
 
         for trackIndex, track in enumerate(cue.tracks):
             self.debug('verifying track %d', trackIndex + 1)
-            index = track._indexes[1]
-            offset = index[0]
+            index = track.indexes[1]
             length = cue.getTrackLength(track)
-            file = index[1]
 
             if length == -1:
-                path = image.getRealPath(file.path)
+                path = image.getRealPath(index.path)
                 self.debug('schedule scan of audio length of %s', path)
                 taskk = AudioLengthTask(path)
                 self.addTask(taskk)
@@ -184,13 +193,12 @@ class ImageVerifyTask(task.MultiTask):
     def stop(self):
         for trackIndex, track, taskk in self._tasks:
             # print '%d has length %d' % (trackIndex, taskk.length)
-            index = track._indexes[1]
-            offset = index[0]
+            index = track.indexes[1]
             assert taskk.length % checksum.SAMPLES_PER_FRAME == 0
             end = taskk.length / checksum.SAMPLES_PER_FRAME
-            self.lengths[trackIndex] = end - offset
+            self.lengths[trackIndex] = end - index.relative
 
-        task.MultiTask.stop(self)
+        task.MultiSeparateTask.stop(self)
 
 # FIXME: move this method to a different module ?
 def getAccurateRipResponses(data):
diff --git a/morituri/image/table.py b/morituri/image/table.py
index 1725e3d..bf22bd9 100644
--- a/morituri/image/table.py
+++ b/morituri/image/table.py
@@ -29,8 +29,7 @@ import struct
 
 import gst
 
-from morituri.common import task, checksum
-from morituri.image import cue
+from morituri.common import task, checksum, common
 
 class Track:
     """
@@ -194,3 +193,230 @@ class Table:
             "%s/%s/%s/dBAR-%.3d-%s-%s-%s.bin" % ( 
                 discId1[-1], discId1[-2], discId1[-3],
                 len(self.tracks), discId1, discId2, self.getCDDBDiscId())
+
+
+class ITTrack:
+    """
+    I represent a track entry in an IndexTable.
+
+    @ivar number: track number (1-based)
+    @type number: int
+    @ivar audio:  whether the track is audio
+    @type audio:  bool
+    @type indexes: dict of number -> L{Index}
+    """
+
+    number = None
+    audio = None
+    indexes = None
+
+    def __repr__(self):
+        return '<Track %02d>' % self.number
+
+    def __init__(self, number, audio=True):
+        self.number = number
+        self.audio = audio
+        self.indexes = {}
+
+    def index(self, number, absolute=None, path=None, relative=None, counter=None):
+        i = Index(number, absolute, path, relative, counter)
+        self.indexes[number] = i
+
+    def getIndex(self, number):
+        return self.indexes[number]
+
+    def getFirstIndex(self):
+        indexes = self.indexes.keys()
+        indexes.sort()
+        return self.indexes[indexes[0]]
+
+class Index:
+    """
+    @ivar counter: counter for the index source; distinguishes between
+                   the matching FILE lines in .cue files for example
+    """
+    number = None
+    absolute = None
+    path = None
+    relative = None
+    counter = None
+
+    def __init__(self, number, absolute=None, path=None, relative=None, counter=None):
+        self.number = number
+        self.absolute = absolute
+        self.path = path
+        self.relative = relative
+        self.counter = counter
+
+    def __repr__(self):
+        return '<Index %02d, absolute %d, path %d, relative %d>' % (
+            self.number, self.absolute, self.path, self.relative)
+
+class IndexTable:
+    """
+    I represent the Table of Contents of a CD.
+
+    @ivar tracks: tracks on this CD
+    @type tracks: list of L{ITTrack}
+    """
+
+    tracks = None # list of ITTrack
+    leadout = None # offset where the leadout starts
+
+    def __init__(self, tracks=None):
+        if not tracks:
+            tracks = []
+
+        self.tracks = tracks
+
+    def getTrackStart(self, number):
+        """
+        @param number: the track number, 1-based
+        @type  number: int
+
+        @returns: the start of the given track number's index 1, in CD frames
+        @rtype:   int
+        """
+        track = self.tracks[number - 1]
+        return track.getIndex(1).absolute
+
+    def getTrackEnd(self, number):
+        """
+        @param number: the track number, 1-based
+        @type  number: int
+
+        @returns: the end of the given track number (ie index 1 of next track)
+        @rtype:   int
+        """
+        end = self.leadout - 1
+        if number < len(self.tracks):
+            end = self.tracks[number].getIndex(1).absolute - 1
+        return end
+
+    def getTrackLength(self, number):
+        """
+        @param number: the track number, 1-based
+        @type  number: int
+
+        @returns: the length of the given track number, in CD frames
+        @rtype:   int
+        """
+        track = self.tracks[number - 1]
+        return self.getTrackEnd(number) - self.getTrackStart(number) + 1
+
+    def getAudioTracks(self):
+        """
+        @returns: the number of audio tracks on the CD
+        @rtype:   int
+        """
+        return len([t for t in self.tracks if t.audio])
+
+    def _cddbSum(self, i):
+        ret = 0
+        while i > 0:
+            ret += (i % 10)
+            i /= 10
+
+        return ret
+
+    def getCDDBDiscId(self):
+        """
+        Calculate the CDDB disc ID.
+
+        @rtype:   str
+        @returns: the 8-character hexadecimal disc ID
+        """
+        # cddb disc id takes into account data tracks
+        # last byte is the number of tracks on the CD
+        n = 0
+
+        for track in self.tracks:
+            # CD's have a standard lead-in time of 2 seconds
+            # which gets added for CDDB disc id's
+            offset = self.getTrackStart(track.number) + \
+                2 * checksum.FRAMES_PER_SECOND
+            seconds = offset / checksum.FRAMES_PER_SECOND
+            n += self._cddbSum(seconds)
+
+        last = self.tracks[-1]
+        leadout = self.getTrackEnd(last.number)
+        frameLength = leadout - self.getTrackStart(1)
+        t = frameLength / checksum.FRAMES_PER_SECOND
+
+        value = (n % 0xff) << 24 | t << 8 | len(self.tracks)
+        
+        return "%08x" % value
+
+    def getAccurateRipIds(self):
+        """
+        Calculate the two AccurateRip ID's.
+
+        @returns: the two 8-character hexadecimal disc ID's
+        @rtype:   tuple of (str, str)
+        """
+        # AccurateRip does not take into account data tracks,
+        # but does count the data track to determine the leadout offset
+        discId1 = 0
+        discId2 = 0
+
+        for track in self.tracks:
+            if not track.audio:
+                continue
+            offset = self.getTrackStart(track.number)
+            discId1 += offset
+            discId2 += (offset or 1) * track.number
+
+        # also add end values, where leadout offset is one past the end
+        # of the last track
+        last = self.tracks[-1]
+        offset = self.getTrackEnd(last.number) + 1
+        discId1 += offset
+        discId2 += offset * (self.getAudioTracks() + 1)
+
+        discId1 &= 0xffffffff
+        discId2 &= 0xffffffff
+
+        return ("%08x" % discId1, "%08x" % discId2)
+
+    def getAccurateRipURL(self):
+        """
+        Return the full AccurateRip URL.
+
+        @returns: the AccurateRip URL
+        @rtype:   str
+        """
+        discId1, discId2 = self.getAccurateRipIds()
+
+        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())
+
+    def cue(self):
+        """
+        Dump our internal representation to a .cue file content.
+        """
+        lines = []
+
+        # add the first FILE line
+        path = self.tracks[0].getFirstIndex().path
+        currentPath = path
+        lines.append('FILE "%s" WAVE' % path)
+
+        for i, track in enumerate(self.tracks):
+            lines.append("  TRACK %02d %s" % (i + 1, 'AUDIO'))
+
+            indexes = track.indexes.keys()
+            indexes.sort()
+
+            for number in indexes:
+                index = track.indexes[number]
+                if index.path != currentPath:
+                    lines.append('FILE "%s" WAVE' % index.path)
+                lines.append("    INDEX %02d %s" % (number,
+                    common.framesToMSF(index.relative)))
+
+        lines.append("")
+        return "\n".join(lines) 
+
+
diff --git a/morituri/program/cdparanoia.py b/morituri/program/cdparanoia.py
index 62ec70c..b00b8a6 100644
--- a/morituri/program/cdparanoia.py
+++ b/morituri/program/cdparanoia.py
@@ -196,7 +196,7 @@ class ReadTrackTask(task.Task):
         self.stop()
         return
 
-class ReadVerifyTrackTask(task.MultiTask):
+class ReadVerifyTrackTask(task.MultiSeparateTask):
     """
     I am a task that reads and verifies a track using cdparanoia.
 
@@ -250,4 +250,4 @@ class ReadVerifyTrackTask(task.MultiTask):
             print 'ERROR: read and verify failed'
             self.checksum = None
 
-        task.MultiTask.stop(self)
+        task.MultiSeparateTask.stop(self)
diff --git a/morituri/test/test_image_cue.py b/morituri/test/test_image_cue.py
index 537d886..53549d4 100644
--- a/morituri/test/test_image_cue.py
+++ b/morituri/test/test_image_cue.py
@@ -5,7 +5,9 @@ import os
 import tempfile
 import unittest
 
-from morituri.image import cue
+from morituri.test import common
+
+from morituri.image import table, cue
 
 class KingsSingleTestCase(unittest.TestCase):
     def setUp(self):
@@ -51,26 +53,26 @@ class WriteCueTestCase(unittest.TestCase):
     def testWrite(self):
         fd, path = tempfile.mkstemp(suffix='morituri.test.cue')
         os.close(fd)
-        c = cue.Cue(path)
 
-        f = cue.File('track01.wav', 'AUDIO')
-        t = cue.Track(1)
-        t.index(1, 0, f)
-        c.tracks.append(t)
+        it = table.IndexTable()
+        
+
+        t = table.ITTrack(1)
+        t.index(1, path='track01.wav', relative=0)
+        it.tracks.append(t)
 
-        t = cue.Track(2)
-        t.index(0, 1000, f)
-        f = cue.File('track02.wav', 'AUDIO')
-        t.index(1, 1100, f)
-        c.tracks.append(t)
+        t = table.ITTrack(2)
+        t.index(0, path='track01.wav', relative=1000)
+        t.index(1, path='track02.wav', relative=0)
+        it.tracks.append(t)
 
-        self.assertEquals(c.dump(), """FILE "track01.wav" WAVE
+        self.assertEquals(it.cue(), """FILE "track01.wav" WAVE
   TRACK 01 AUDIO
     INDEX 01 00:00:00
   TRACK 02 AUDIO
     INDEX 00 00:13:25
 FILE "track02.wav" WAVE
-    INDEX 01 00:14:50
+    INDEX 01 00:00:00
 """)
 
         

-- 
morituri packaging



More information about the pkg-multimedia-commits mailing list