* morituri/image/table.py:

Add logging.
	  Add methods to clear a table of files, and to absolutize indexes
	  as long as the source is the same file, and to set a File on a
	  given index, adjusting all following indexes that match the
	  duration, and check if the IndexTable has all information for a TOC.
	* morituri/image/toc.py:
	  Add logging.
	  Use a counter for the source.
	  Fix up index offset calculation.
	* morituri/program/cdrdao.py:
	  Use a real IndexTable as the result, instead of a TocFile.
	* morituri/image/cue.py:
	  Use a real IndexTable to store tracks.
	* morituri/test/test_image_toc.py:
	  The toc file now has a table which has the tracks.
	  Fix the tests to adjust for wrong index calculations.
	* morituri/test/test_image_cue.py:
	* morituri/test/test_image_image.py:
	* morituri/image/image.py:
	  The cue file now has a table which has the tracks.
	* morituri/test/test_image_table.py:
	  Add assertions to make sure when the table can serve as a TOC.
	* examples/readdisc.py:
	  Adjust for changes.  Fix up to include AccurateRip results.
	  First time we can do a complete normal rip including verifying
	  against AccurateRip results!
This commit is contained in:
Thomas Vander Stichele
2009-05-05 10:01:41 +00:00
parent a5d70dd317
commit 2f464207db
11 changed files with 403 additions and 109 deletions

View File

@@ -58,12 +58,18 @@ _INDEX_RE = re.compile(r"""
class CueFile(object, log.Loggable):
"""
I represent a .cue file as an object.
@type table: L{table.IndexTable}
@ivar table: the index table.
"""
def __init__(self, path):
self._path = path
self._rems = {}
self._messages = []
self.tracks = []
self.leadout = None
self.table = table.IndexTable()
def parse(self):
state = 'HEADER'
@@ -109,7 +115,7 @@ class CueFile(object, log.Loggable):
self.debug('found track %d', trackNumber)
currentTrack = table.ITTrack(trackNumber)
self.tracks.append(currentTrack)
self.table.tracks.append(currentTrack)
continue
# look for INDEX lines
@@ -145,13 +151,13 @@ class CueFile(object, log.Loggable):
# returns track length in frames, or -1 if can't be determined and
# complete file should be assumed
# FIXME: this assumes a track can only be in one file; is this true ?
i = self.tracks.index(track)
if i == len(self.tracks) - 1:
i = self.table.tracks.index(track)
if i == len(self.table.tracks) - 1:
# 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
nextIndex = self.table.tracks[i + 1].indexes[1] # FIXME: could be 0
c = thisIndex.counter
if c is not None and c == nextIndex.counter:

View File

@@ -73,19 +73,19 @@ 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).relative
offset = self.cue.table.tracks[0].getIndex(1).relative
tracks = []
for i in range(len(self.cue.tracks)):
length = self.cue.getTrackLength(self.cue.tracks[i])
for i in range(len(self.cue.table.tracks)):
length = self.cue.getTrackLength(self.cue.table.tracks[i])
if length == -1:
length = verify.lengths[i + 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,
t.index(1, absolute=offset, path=self.cue.table.tracks[i].getIndex(1).path,
relative=0)
offset += length
@@ -107,15 +107,15 @@ class AccurateRipChecksumTask(task.MultiSeparateTask):
cue = image.cue
self.checksums = []
self.debug('Checksumming %d tracks' % len(cue.tracks))
for trackIndex, track in enumerate(cue.tracks):
self.debug('Checksumming %d tracks' % len(cue.table.tracks))
for trackIndex, track in enumerate(cue.table.tracks):
index = track.indexes[1]
length = cue.getTrackLength(track)
self.debug('track %d has length %d' % (trackIndex + 1, length))
path = image.getRealPath(index.path)
checksumTask = checksum.AccurateRipChecksumTask(path,
trackNumber=trackIndex + 1, trackCount=len(cue.tracks),
trackNumber=trackIndex + 1, trackCount=len(cue.table.tracks),
frameStart=index.relative * checksum.SAMPLES_PER_FRAME,
frameLength=length * checksum.SAMPLES_PER_FRAME)
self.addTask(checksumTask)
@@ -176,7 +176,7 @@ class ImageVerifyTask(task.MultiSeparateTask):
self._tasks = []
self.lengths = {}
for trackIndex, track in enumerate(cue.tracks):
for trackIndex, track in enumerate(cue.table.tracks):
self.debug('verifying track %d', trackIndex + 1)
index = track.indexes[1]
length = cue.getTrackLength(track)

View File

@@ -29,7 +29,9 @@ import struct
import gst
from morituri.common import task, checksum, common
from morituri.common import task, checksum, common, log
from morituri.test import common as tcommon
class DeleteMeTrack:
"""
@@ -249,12 +251,12 @@ class Index:
self.counter = counter
def __repr__(self):
return '<Index %02d, absolute %r, path %r, relative %r>' % (
self.number, self.absolute, self.path, self.relative)
return '<Index %02d, absolute %r, path %r, relative %r, counter %r>' % (
self.number, self.absolute, self.path, self.relative, self.counter)
class IndexTable:
class IndexTable(object, log.Loggable):
"""
I represent the Table of Contents of a CD.
I represent a table of indexes on a CD.
@ivar tracks: tracks on this CD
@type tracks: list of L{ITTrack}
@@ -417,6 +419,140 @@ class IndexTable:
common.framesToMSF(index.relative)))
lines.append("")
return "\n".join(lines)
### methods that modify the table
def clearFiles(self):
"""
Clear all file backings.
Resets indexes paths and relative offsets.
"""
# FIXME: do a loop over track indexes better, with a pythonic
# construct that allows you to do for t, i in ...
t = self.tracks[0].number
index = self.tracks[0].getFirstIndex()
i = index.number
# the first cut is the deepest
counter = index.counter
self.debug('clearing path')
while True:
track = self.tracks[t - 1]
index = track.getIndex(i)
self.debug('Clearing path on track %d, index %d', t, i)
index.path = None
index.relative = None
try:
t, i = self.getNextTrackIndex(t, i)
except IndexError:
break
def setFile(self, track, index, path, length):
"""
Sets the given file as the source from the given index on.
Will loop over all indexes that fall within the given length,
to adjust the path.
Assumes all indexes have an absolute offset and will raise if not.
"""
t = self.tracks[track - 1]
i = t.indexes[index]
start = i.absolute
assert start is not None, "index %r is missing absolute offset" % i
end = start + length
# FIXME: check border conditions here, esp. wrt. toc's off-by-one bug
while i.absolute <= end:
self.debug('Setting path and relative on track %d, index %d',
track, index)
i.path = path
i.relative = i.absolute - start
try:
track, index = self.getNextTrackIndex(track, index)
t = self.tracks[track - 1]
i = t.indexes[index]
except IndexError:
break
def absolutize(self):
"""
Calculate absolute offsets on indexes as much as possible.
Only possible for as long as tracks draw from the same file.
"""
t = self.tracks[0].number
index = self.tracks[0].getFirstIndex()
i = index.number
# the first cut is the deepest
counter = index.counter
self.debug('absolutizing')
while True:
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)
index.absolute = index.relative
try:
t, i = self.getNextTrackIndex(t, i)
except IndexError:
break
### lookups
def getNextTrackIndex(self, track, index):
"""
Return the next track and index.
@param track: track number, 1-based
@raises IndexError: on last index
@rtype: tuple of (int, int)
"""
t = self.tracks[track - 1]
indexes = t.indexes.keys()
position = indexes.index(index)
if position + 1 < len(indexes):
return track, indexes[position + 1]
track += 1
if track > len(self.tracks):
raise IndexError, "No index beyond track %d, index %d" % (
track - 1, index)
t = self.tracks[track - 1]
indexes = t.indexes.keys()
return track, indexes[0]
# various tests for types of IndexTable
def hasTOC(self):
"""
Check if the Index Table has a complete TOC.
a TOC is a list of all tracks and their Index 01, with absolute
offsets, as well as the leadout.
"""
if not self.leadout:
self.debug('no leadout, no TOC')
return False
for t in self.tracks:
if 1 not in t.indexes.keys():
self.debug('no index 1, no TOC')
return False
if t.indexes[1].absolute is None:
self.debug('no absolute index 1, no TOC')
return False
return True

View File

@@ -27,7 +27,8 @@ Reading .toc files
import os
import re
from morituri.common import common
from morituri.common import common, log
from morituri.image import table
# header
_CATALOG_RE = re.compile(r'^CATALOG "(?P<catalog>\d+)"$')
@@ -64,16 +65,17 @@ _INDEX_RE = re.compile(r"""
\s(?P<offset>.+)$ # start offset
""", re.VERBOSE)
class TocFile(object):
class TocFile(object, log.Loggable):
def __init__(self, path):
self._path = path
self._messages = []
self.tracks = []
self.table = table.IndexTable()
def parse(self):
state = 'HEADER'
currentFile = None
currentTrack = None
counter = 0
trackNumber = 0
indexNumber = 0
currentOffset = 0 # running absolute offset of where each track starts
@@ -100,8 +102,8 @@ class TocFile(object):
# handle index 1 of previous track, if any
if currentTrack:
currentTrack.index(1, currentOffset + pregapLength,
currentFile)
currentTrack.index(1, path=currentFile.path,
relative=currentOffset + pregapLength, counter=counter)
trackNumber += 1
currentOffset += currentLength
@@ -109,13 +111,18 @@ class TocFile(object):
indexNumber = 1
trackMode = m.group('mode')
currentTrack = Track(trackNumber)
self.tracks.append(currentTrack)
# FIXME: track mode
currentTrack = table.ITTrack(trackNumber)
self.table.tracks.append(currentTrack)
continue
# look for SILENCE lines
m = _SILENCE_RE.search(line)
if m:
if currentFile is not None:
self.debug('SILENCE after FILE, increasing counter')
counter += 1
currentFile = None
length = m.group('length')
currentLength += common.msfToFrames(length)
@@ -125,6 +132,13 @@ class TocFile(object):
filePath = m.group('name')
start = m.group('start')
length = m.group('length')
self.debug('FILE %s, start %r, length %r',
filePath, common.msfToFrames(start),
common.msfToFrames(length))
if not currentFile or filePath != currentFile.path:
counter += 1
self.debug('track %d, switched to new FILE, increased counter to %d',
trackNumber, counter)
currentFile = File(filePath, start, length)
#currentOffset += common.msfToFrames(start)
currentLength += common.msfToFrames(length)
@@ -138,8 +152,9 @@ class TocFile(object):
continue
length = common.msfToFrames(m.group('length'))
currentTrack.index(0, currentOffset, currentFile)
currentLength += length
currentTrack.index(0, path=currentFile.path,
relative=currentOffset - length, counter=counter)
#currentLength += length
pregapLength = length
# look for INDEX lines
@@ -149,11 +164,13 @@ class TocFile(object):
self.message(number, 'INDEX without preceding TRACK')
indexNumber += 1
offset = common.msfToFrames(m.group('offset'))
currentTrack.index(indexNumber, offset, currentFile)
currentTrack.index(indexNumber, path=currentFile.path,
relative=offset, counter=counter)
# handle index 1 of final track, if any
if currentTrack:
currentTrack.index(1, currentOffset + pregapLength, currentFile)
currentTrack.index(1, path=currentFile.path,
relative=currentOffset + pregapLength, counter=counter)
def message(self, number, message):
"""
@@ -167,16 +184,18 @@ class TocFile(object):
# returns track length in frames, or -1 if can't be determined and
# complete file should be assumed
# FIXME: this assumes a track can only be in one file; is this true ?
i = self.tracks.index(track)
if i == len(self.tracks) - 1:
i = self.table.tracks.index(track)
if i == len(self.table.tracks) - 1:
# 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.table.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
@@ -218,15 +237,15 @@ class File:
"""
def __init__(self, path, start, length):
self.path = path
self.start = start
self.length = length
#self.start = start
#self.length = length
def __repr__(self):
return '<File "%s">' % (self.path, )
# FIXME: add type ? separate AUDIO from others
class Track:
class DeleteMeTrack:
"""
I represent a track in a cue file.
I have index points.