Some of this seems to be debug code which has been left in, some of it seems to just be old code that was commented out and never put back in and probably just forgotten about. Either way, we use git for a reason, so there's no need for these code snippets to stick around. The code history can be inspected and old code retrieved that way.
205 lines
6.1 KiB
Python
205 lines
6.1 KiB
Python
# -*- Mode: Python; test-case-name: whipper.test.test_image_cue -*-
|
|
# vi:si:et:sw=4:sts=4:ts=4
|
|
|
|
# Copyright (C) 2009 Thomas Vander Stichele
|
|
|
|
# This file is part of whipper.
|
|
#
|
|
# whipper is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# whipper is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
Reading .cue files
|
|
|
|
See http://digitalx.org/cuesheetsyntax.php
|
|
"""
|
|
|
|
import re
|
|
import codecs
|
|
|
|
from whipper.common import common
|
|
from whipper.image import table
|
|
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_REM_RE = re.compile("^REM\s(\w+)\s(.*)$")
|
|
_PERFORMER_RE = re.compile("^PERFORMER\s(.*)$")
|
|
_TITLE_RE = re.compile("^TITLE\s(.*)$")
|
|
|
|
_FILE_RE = re.compile(r"""
|
|
^FILE # FILE
|
|
\s+"(?P<name>.*)" # 'file name' in quotes
|
|
\s+(?P<format>\w+)$ # format (WAVE/MP3/AIFF/...)
|
|
""", re.VERBOSE)
|
|
|
|
_TRACK_RE = re.compile(r"""
|
|
^\s+TRACK # TRACK
|
|
\s+(?P<track>\d\d) # two-digit track number
|
|
\s+(?P<mode>.+)$ # mode (AUDIO, MODEx/2xxx, ...)
|
|
""", re.VERBOSE)
|
|
|
|
_INDEX_RE = re.compile(r"""
|
|
^\s+INDEX # INDEX
|
|
\s+(\d\d) # two-digit index number
|
|
\s+(\d\d) # minutes
|
|
:(\d\d) # seconds
|
|
:(\d\d)$ # frames
|
|
""", re.VERBOSE)
|
|
|
|
|
|
class CueFile(object):
|
|
"""
|
|
I represent a .cue file as an object.
|
|
|
|
@type table: L{table.Table}
|
|
@ivar table: the index table.
|
|
"""
|
|
logCategory = 'CueFile'
|
|
|
|
def __init__(self, path):
|
|
"""
|
|
@type path: unicode
|
|
"""
|
|
assert type(path) is unicode, "%r is not unicode" % path
|
|
|
|
self._path = path
|
|
self._rems = {}
|
|
self._messages = []
|
|
self.leadout = None
|
|
self.table = table.Table()
|
|
|
|
def parse(self):
|
|
state = 'HEADER'
|
|
currentFile = None
|
|
currentTrack = None
|
|
counter = 0
|
|
|
|
logger.info('Parsing .cue file %r', self._path)
|
|
handle = codecs.open(self._path, 'r', 'utf-8')
|
|
|
|
for number, line in enumerate(handle.readlines()):
|
|
line = line.rstrip()
|
|
|
|
m = _REM_RE.search(line)
|
|
if m:
|
|
tag = m.expand('\\1')
|
|
value = m.expand('\\2')
|
|
if state != 'HEADER':
|
|
self.message(number, 'REM %s outside of header' % tag)
|
|
else:
|
|
self._rems[tag] = value
|
|
continue
|
|
|
|
# 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)
|
|
|
|
# look for TRACK lines
|
|
m = _TRACK_RE.search(line)
|
|
if m:
|
|
if not currentFile:
|
|
self.message(number, 'TRACK without preceding FILE')
|
|
continue
|
|
|
|
state = 'TRACK'
|
|
|
|
trackNumber = int(m.group('track'))
|
|
|
|
logger.debug('found track %d', trackNumber)
|
|
currentTrack = table.Track(trackNumber)
|
|
self.table.tracks.append(currentTrack)
|
|
continue
|
|
|
|
# look for INDEX lines
|
|
m = _INDEX_RE.search(line)
|
|
if m:
|
|
if not currentTrack:
|
|
self.message(number, 'INDEX without preceding TRACK')
|
|
print 'ouch'
|
|
continue
|
|
|
|
indexNumber = int(m.expand('\\1'))
|
|
minutes = int(m.expand('\\2'))
|
|
seconds = int(m.expand('\\3'))
|
|
frames = int(m.expand('\\4'))
|
|
frameOffset = frames \
|
|
+ seconds * common.FRAMES_PER_SECOND \
|
|
+ minutes * common.FRAMES_PER_SECOND * 60
|
|
|
|
logger.debug('found index %d of track %r in %r:%d',
|
|
indexNumber, currentTrack, currentFile.path, frameOffset)
|
|
# FIXME: what do we do about File's FORMAT ?
|
|
currentTrack.index(indexNumber,
|
|
path=currentFile.path, relative=frameOffset,
|
|
counter=counter)
|
|
continue
|
|
|
|
def message(self, number, message):
|
|
"""
|
|
Add a message about a given line in the cue file.
|
|
|
|
@param number: line number, counting from 0.
|
|
"""
|
|
self._messages.append((number + 1, message))
|
|
|
|
def getTrackLength(self, track):
|
|
# 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.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.table.tracks[i + 1].indexes[1] # FIXME: could be 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
|
|
|
|
def getRealPath(self, path):
|
|
"""
|
|
Translate the .cue's FILE to an existing path.
|
|
|
|
@type path: unicode
|
|
"""
|
|
return common.getRealPath(self._path, path)
|
|
|
|
|
|
class File:
|
|
"""
|
|
I represent a FILE line in a cue file.
|
|
"""
|
|
|
|
def __init__(self, path, format):
|
|
"""
|
|
@type path: unicode
|
|
"""
|
|
assert type(path) is unicode, "%r is not unicode" % path
|
|
|
|
self.path = path
|
|
self.format = format
|
|
|
|
def __repr__(self):
|
|
return '<File %r of format %s>' % (self.path, self.format)
|