AccurateRip V2 support

- output path no longer has fallbacks
- refactor accuraterip cache
- use requests to download accuraterip entries
- add tests for accuraterip functionality
- remove gobject support from accuraterip-checksum calculation
- default track template now includes extension
- begin to remove support for continuing rip
- begin to use print instead of sys.stdout.write() throughout
This commit is contained in:
Samantha Baldwin
2017-09-04 21:26:34 -04:00
parent 3f248bfc00
commit bfa0308880
14 changed files with 799 additions and 819 deletions

View File

@@ -19,6 +19,7 @@
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
import argparse
import cdio
import os
import glob
import urllib2
@@ -41,7 +42,7 @@ logger = logging.getLogger(__name__)
SILENT = 1e-10
MAX_TRIES = 5
DEFAULT_TRACK_TEMPLATE = u'%r/%A - %d/%t. %a - %n'
DEFAULT_TRACK_TEMPLATE = u'%r/%A - %d/%t. %a - %n.%x'
DEFAULT_DISC_TEMPLATE = u'%r/%A - %d/%A - %d'
TEMPLATE_DESCRIPTION = '''
@@ -68,12 +69,6 @@ disc and track template are:
class _CD(BaseCommand):
"""
@type program: L{program.Program}
@ivar eject: whether to eject the drive after completing
"""
eject = True
@staticmethod
@@ -164,7 +159,7 @@ class _CD(BaseCommand):
self.itable = self.program.getTable(self.runner,
self.ittoc.getCDDBDiscId(),
self.ittoc.getMusicBrainzDiscId(),
self.device, offset)
self.device, self.options.offset)
assert self.itable.getCDDBDiscId() == self.ittoc.getCDDBDiscId(), \
"full table's id %s differs from toc id %s" % (
@@ -174,10 +169,10 @@ class _CD(BaseCommand):
"full table's mb id %s differs from toc id mb %s" % (
self.itable.getMusicBrainzDiscId(),
self.ittoc.getMusicBrainzDiscId())
assert self.itable.getAccurateRipURL() == \
self.ittoc.getAccurateRipURL(), \
assert self.itable.accuraterip_path() == \
self.ittoc.accuraterip_path(), \
"full table's AR URL %s differs from toc AR URL %s" % (
self.itable.getAccurateRipURL(), self.ittoc.getAccurateRipURL())
self.itable.accuraterip_url(), self.ittoc.accuraterip_url())
if self.program.metadata:
self.program.metadata.discid = self.ittoc.getMusicBrainzDiscId()
@@ -200,15 +195,9 @@ class _CD(BaseCommand):
self.program.result.title = self.program.metadata \
and self.program.metadata.title \
or 'Unknown Title'
try:
import cdio
_, self.program.result.vendor, self.program.result.model, \
self.program.result.release = \
cdio.Device(self.device).get_hwinfo()
except ImportError:
raise ImportError("Pycdio module import failed.\n"
"This is a hard dependency: if not "
"available please install it")
_, self.program.result.vendor, self.program.result.model, \
self.program.result.release = \
cdio.Device(self.device).get_hwinfo()
self.doCommand()
@@ -348,41 +337,25 @@ Log files will log the path to tracks relative to this directory.
self.program.result.overread = self.options.overread
self.program.result.logger = self.options.logger
# write disc files
disambiguate = False
while True:
discName = self.program.getPath(self.program.outdir,
self.options.disc_template,
self.mbdiscid, 0,
disambiguate=disambiguate)
dirname = os.path.dirname(discName)
if os.path.exists(dirname):
sys.stdout.write("Output directory %s already exists\n" %
dirname.encode('utf-8'))
logs = glob.glob(os.path.join(dirname, '*.log'))
if logs:
sys.stdout.write(
"Output directory %s is a finished rip\n" %
dirname.encode('utf-8'))
if not disambiguate:
disambiguate = True
continue
return
else:
break
discName = self.program.getPath(self.program.outdir,
self.options.disc_template,
self.mbdiscid,
self.program.metadata)
dirname = os.path.dirname(discName)
if os.path.exists(dirname):
logs = glob.glob(os.path.join(dirname, '*.log'))
if logs:
bye("output directory %s is a finished rip" %
dirname.encode('utf-8'))
else:
sys.stdout.write("Creating output directory %s\n" %
sys.stdout.write("output directory %s already exists\n" %
dirname.encode('utf-8'))
os.makedirs(dirname)
break
# FIXME: say when we're continuing a rip
# FIXME: disambiguate if the pre-existing rip is different
print("creating output directory %s" % dirname.encode('utf-8'))
os.makedirs(dirname)
# FIXME: turn this into a method
def ripIfNotRipped(number):
def _ripIfNotRipped(number):
logger.debug('ripIfNotRipped for track %d' % number)
# we can have a previous result
trackResult = self.program.result.getTrackResult(number)
@@ -395,9 +368,9 @@ Log files will log the path to tracks relative to this directory.
path = self.program.getPath(self.program.outdir,
self.options.track_template,
self.mbdiscid, number,
disambiguate=disambiguate) \
+ '.' + 'flac'
self.mbdiscid,
self.program.metadata,
track_number=number)
logger.debug('ripIfNotRipped: path %r' % path)
trackResult.number = number
@@ -464,12 +437,11 @@ Log files will log the path to tracks relative to this directory.
"track can't be ripped. "
"Rip attempts number is equal to 'MAX_TRIES'")
if trackResult.testcrc == trackResult.copycrc:
sys.stdout.write('Checksums match for track %d\n' %
number)
sys.stdout.write('CRCs match for track %d\n' % number)
else:
sys.stdout.write(
'ERROR: checksums did not match for track %d\n' %
number)
'ERROR: CRCs did not match for track %d\n' % number
)
raise
sys.stdout.write(
@@ -507,109 +479,35 @@ Log files will log the path to tracks relative to this directory.
htoa = self.program.getHTOA()
if htoa:
start, stop = htoa
sys.stdout.write(
'Found Hidden Track One Audio from frame %d to %d\n' % (
start, stop))
# rip it
ripIfNotRipped(0)
print('found Hidden Track One Audio from frame %d to %d' % (
start, stop))
_ripIfNotRipped(0)
htoapath = self.program.result.tracks[0].filename
for i, track in enumerate(self.itable.tracks):
# FIXME: rip data tracks differently
if not track.audio:
sys.stdout.write(
'WARNING: skipping data track %d, not implemented\n' % (
i + 1, ))
print('skipping data track %d, not implemented' % i + 1)
# FIXME: make it work for now
track.indexes[1].relative = 0
continue
ripIfNotRipped(i + 1)
# write disc files
discName = self.program.getPath(self.program.outdir,
self.options.disc_template,
self.mbdiscid, 0,
disambiguate=disambiguate)
dirname = os.path.dirname(discName)
if not os.path.exists(dirname):
os.makedirs(dirname)
_ripIfNotRipped(i + 1)
logger.debug('writing cue file for %r', discName)
self.program.writeCue(discName)
# write .m3u file
logger.debug('writing m3u file for %r', discName)
m3uPath = u'%s.m3u' % discName
handle = open(m3uPath, 'w')
u = u'#EXTM3U\n'
handle.write(u.encode('utf-8'))
self.program.write_m3u(discName, htoapath)
def writeFile(handle, path, length):
targetPath = common.getRelativePath(path, m3uPath)
u = u'#EXTINF:%d,%s\n' % (length, targetPath)
handle.write(u.encode('utf-8'))
u = '%s\n' % targetPath
handle.write(u.encode('utf-8'))
if self.program.verifyImage(self.runner, self.ittoc, self.itable):
print('rip verified as accurate')
else:
print('rip NOT verified as accurate')
if htoapath:
writeFile(handle, htoapath,
self.itable.getTrackStart(1) / common.FRAMES_PER_SECOND)
for i, track in enumerate(self.itable.tracks):
if not track.audio:
continue
path = self.program.getPath(self.program.outdir,
self.options.track_template,
self.mbdiscid, i + 1,
disambiguate=disambiguate
) + '.' + 'flac'
writeFile(handle, path,
(self.itable.getTrackLength(i + 1) /
common.FRAMES_PER_SECOND))
handle.close()
# verify using accuraterip
url = self.ittoc.getAccurateRipURL()
sys.stdout.write("AccurateRip URL %s\n" % url)
accucache = accurip.AccuCache()
try:
responses = accucache.retrieve(url)
except urllib2.URLError, e:
if isinstance(e.args[0], socket.gaierror):
if e.args[0].errno == -2:
sys.stdout.write("Warning: network error: %r\n" % (
e.args[0], ))
responses = None
else:
raise
else:
raise
if not responses:
sys.stdout.write('Album not found in AccurateRip database\n')
if responses:
sys.stdout.write('%d AccurateRip reponses found\n' %
len(responses))
if responses[0].cddbDiscId != self.itable.getCDDBDiscId():
sys.stdout.write(
"AccurateRip response discid different: %s\n" %
responses[0].cddbDiscId)
self.program.verifyImage(self.runner, responses)
sys.stdout.write("\n".join(
self.program.getAccurateRipResults()) + "\n")
accurip.print_report(self.program.result)
self.program.saveRipResult()
# write log file
self.program.writeLog(discName, self.logger)

View File

@@ -27,7 +27,7 @@ import gobject
from whipper.command.basecommand import BaseCommand
from whipper.common import accurip, common, config, drive
from whipper.common import task as ctask
from whipper.program import cdrdao, cdparanoia, utils
from whipper.program import arc, cdrdao, cdparanoia, utils
from whipper.common import checksum
from whipper.extern.task import task
@@ -92,23 +92,7 @@ CD in the AccurateRip database."""
table = t.table
logger.debug("CDDB disc id: %r", table.getCDDBDiscId())
url = table.getAccurateRipURL()
logger.debug("AccurateRip URL: %s", url)
# FIXME: download url as a task too
responses = []
import urllib2
try:
handle = urllib2.urlopen(url)
data = handle.read()
responses = accurip.getAccurateRipResponses(data)
except urllib2.HTTPError, e:
if e.code == 404:
sys.stdout.write(
'Album not found in AccurateRip database.\n')
return 1
else:
raise
responses = accurip.get_db_entry(table.accuraterip_path())
if responses:
logger.debug('%d AccurateRip responses found.' % len(responses))
@@ -120,17 +104,19 @@ CD in the AccurateRip database."""
# now rip the first track at various offsets, calculating AccurateRip
# CRC, and matching it against the retrieved ones
def match(archecksum, track, responses):
# archecksums is a tuple of accuraterip checksums: (v1, v2)
def match(archecksums, track, responses):
for i, r in enumerate(responses):
if archecksum == r.checksums[track - 1]:
return archecksum, i
for checksum in archecksums:
if checksum == r.checksums[track - 1]:
return checksum, i
return None, None
for offset in self._offsets:
sys.stdout.write('Trying read offset %d ...\n' % offset)
try:
archecksum = self._arcs(runner, table, 1, offset)
archecksums = self._arcs(runner, table, 1, offset)
except task.TaskException, e:
# let MissingDependency fall through
@@ -149,9 +135,9 @@ CD in the AccurateRip database."""
'WARNING: cannot rip with offset %d...\n' % offset)
continue
logger.debug('AR checksum calculated: %s' % archecksum)
logger.debug('AR checksums calculated: %s %s' % archecksums)
c, i = match(archecksum, 1, responses)
c, i = match(archecksums, 1, responses)
if c:
count = 1
logger.debug('MATCHED against response %d' % i)
@@ -161,9 +147,10 @@ CD in the AccurateRip database."""
# now try and rip all other tracks as well, except for the
# last one (to avoid readers that can't do overread
for track in range(2, (len(table.tracks) + 1) - 1):
# for track in range(2, (len(table.tracks) + 1) - 1):
for track in range(2, (len(table.tracks) + 1)):
try:
archecksum = self._arcs(runner, table, track, offset)
archecksums = self._arcs(runner, table, track, offset)
except task.TaskException, e:
if isinstance(e.exception, cdparanoia.FileSizeError):
sys.stdout.write(
@@ -171,7 +158,7 @@ CD in the AccurateRip database."""
offset)
continue
c, i = match(archecksum, track, responses)
c, i = match(archecksums, track, responses)
if c:
logger.debug('MATCHED track %d against response %d' % (
track, i))
@@ -188,9 +175,8 @@ CD in the AccurateRip database."""
sys.stdout.write('No matching offset found.\n')
sys.stdout.write('Consider trying again with a different disc.\n')
# TODO MW: Update this further for ARv2 code
def _arcs(self, runner, table, track, offset):
# rips the track with the given offset, return the arcs checksum
# rips the track with the given offset, return the arcs checksums
logger.debug('Ripping track %r with offset %d ...', track, offset)
fd, path = tempfile.mkstemp(
@@ -207,15 +193,15 @@ CD in the AccurateRip database."""
track, offset)
runner.run(t)
# TODO MW: Update this to also use the v2 checksum(s)
t = checksum.FastAccurateRipChecksumTask(path,
trackNumber=track,
trackCount=len(table.tracks),
wave=True, v2=False)
runner.run(t)
v1 = arc.accuraterip_checksum(
path, track, len(table.tracks), wave=True, v2=False
)
v2 = arc.accuraterip_checksum(
path, track, len(table.tracks), wave=True, v2=True
)
os.unlink(path)
return "%08x" % t.checksum
return ("%08x" % v1, "%08x" % v2)
def _foundOffset(self, device, offset):
sys.stdout.write('\nRead offset of device is: %d.\n' %