Merge pull request #187 from RecursiveForest/rewrite-accuraterip
AccurateRip V2 support
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
import sys
|
||||
|
||||
from whipper.command.basecommand import BaseCommand
|
||||
from whipper.common import accurip
|
||||
from whipper.common.accurip import get_db_entry, ACCURATERIP_URL
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -38,20 +38,18 @@ retrieves and display accuraterip data from the given URL
|
||||
help="accuraterip URL to load data from")
|
||||
|
||||
def do(self):
|
||||
url = self.options.url
|
||||
cache = accurip.AccuCache()
|
||||
responses = cache.retrieve(url)
|
||||
responses = get_db_entry(self.options.url.lstrip(ACCURATERIP_URL))
|
||||
|
||||
count = responses[0].trackCount
|
||||
count = responses[0].num_tracks
|
||||
|
||||
sys.stdout.write("Found %d responses for %d tracks\n\n" % (
|
||||
len(responses), count))
|
||||
|
||||
for (i, r) in enumerate(responses):
|
||||
if r.trackCount != count:
|
||||
if r.num_tracks != count:
|
||||
sys.stdout.write(
|
||||
"Warning: response %d has %d tracks instead of %d\n" % (
|
||||
i, r.trackCount, count))
|
||||
i, r.num_tracks, count))
|
||||
|
||||
# checksum and confidence by track
|
||||
for track in range(count):
|
||||
@@ -59,11 +57,11 @@ retrieves and display accuraterip data from the given URL
|
||||
checksums = {}
|
||||
|
||||
for (i, r) in enumerate(responses):
|
||||
if r.trackCount != count:
|
||||
if r.num_tracks != count:
|
||||
continue
|
||||
|
||||
assert len(r.checksums) == r.trackCount
|
||||
assert len(r.confidences) == r.trackCount
|
||||
assert len(r.checksums) == r.num_tracks
|
||||
assert len(r.confidences) == r.num_tracks
|
||||
|
||||
entry = {}
|
||||
entry["confidence"] = r.confidences[track]
|
||||
|
||||
@@ -19,16 +19,15 @@
|
||||
# along with whipper. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import cdio
|
||||
import os
|
||||
import glob
|
||||
import urllib2
|
||||
import socket
|
||||
import sys
|
||||
import logging
|
||||
import gobject
|
||||
from whipper.command.basecommand import BaseCommand
|
||||
from whipper.common import (
|
||||
accurip, common, config, drive, program, task
|
||||
accurip, config, drive, program, task
|
||||
)
|
||||
from whipper.program import cdrdao, cdparanoia, utils
|
||||
from whipper.result import result
|
||||
@@ -41,7 +40,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 +67,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
|
||||
@@ -150,21 +143,11 @@ class _CD(BaseCommand):
|
||||
"--cdr not passed")
|
||||
return -1
|
||||
|
||||
# FIXME ?????
|
||||
# Hackish fix for broken commit
|
||||
offset = 0
|
||||
info = drive.getDeviceInfo(self.device)
|
||||
if info:
|
||||
try:
|
||||
offset = self.config.getReadOffset(*info)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# now, read the complete index table, which is slower
|
||||
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 +157,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 +183,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()
|
||||
|
||||
@@ -346,41 +323,27 @@ 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:
|
||||
msg = ("output directory %s is a finished rip" %
|
||||
dirname.encode('utf-8'))
|
||||
logger.critical(msg)
|
||||
raise RuntimeError(msg)
|
||||
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)
|
||||
@@ -393,9 +356,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
|
||||
|
||||
@@ -462,13 +425,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)
|
||||
raise
|
||||
raise RuntimeError(
|
||||
"CRCs did not match for track %d\n" % number
|
||||
)
|
||||
|
||||
sys.stdout.write(
|
||||
'Peak level: {:.2%} \n'.format(trackResult.peak))
|
||||
@@ -501,113 +462,37 @@ Log files will log the path to tracks relative to this directory.
|
||||
self.program.saveRipResult()
|
||||
|
||||
# check for hidden track one audio
|
||||
htoapath = None
|
||||
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)
|
||||
htoapath = self.program.result.tracks[0].filename
|
||||
print('found Hidden Track One Audio from frame %d to %d' % (
|
||||
start, stop))
|
||||
_ripIfNotRipped(0)
|
||||
|
||||
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)
|
||||
|
||||
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 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
|
||||
self.program.verifyImage(self.runner, self.ittoc)
|
||||
except accurip.EntryNotFound:
|
||||
print('AccurateRip entry not found')
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -117,16 +117,12 @@ Verifies the image from the given .cue files against the AccurateRip database.
|
||||
def do(self):
|
||||
prog = program.Program(config.Config())
|
||||
runner = task.SyncRunner()
|
||||
cache = accurip.AccuCache()
|
||||
|
||||
for arg in self.options.cuefile:
|
||||
arg = arg.decode('utf-8')
|
||||
cueImage = image.Image(arg)
|
||||
cueImage.setup(runner)
|
||||
|
||||
url = cueImage.table.getAccurateRipURL()
|
||||
responses = cache.retrieve(url)
|
||||
|
||||
# FIXME: this feels like we're poking at internals.
|
||||
prog.cuePath = arg
|
||||
prog.result = result.RipResult()
|
||||
@@ -135,9 +131,14 @@ Verifies the image from the given .cue files against the AccurateRip database.
|
||||
tr.number = track.number
|
||||
prog.result.tracks.append(tr)
|
||||
|
||||
prog.verifyImage(runner, responses)
|
||||
|
||||
print "\n".join(prog.getAccurateRipResults()) + "\n"
|
||||
verified = False
|
||||
try:
|
||||
verified = prog.verifyImage(runner, cueImage.table)
|
||||
except accurip.EntryNotFound:
|
||||
print('AccurateRip entry not found')
|
||||
accurip.print_report(prog.result)
|
||||
if not verified:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class Image(BaseCommand):
|
||||
|
||||
@@ -36,8 +36,13 @@ def main():
|
||||
cmd.options.eject in ('failure', 'always')):
|
||||
eject_device(e.device)
|
||||
return 255
|
||||
except RuntimeError, e:
|
||||
print(e)
|
||||
return 1
|
||||
except KeyboardInterrupt:
|
||||
return 2
|
||||
except ImportError, e:
|
||||
raise ImportError(e)
|
||||
raise
|
||||
except task.TaskException, e:
|
||||
if isinstance(e.exception, ImportError):
|
||||
raise ImportError(e.exception)
|
||||
|
||||
@@ -27,8 +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.common import checksum
|
||||
from whipper.program import arc, cdrdao, cdparanoia, utils
|
||||
from whipper.extern.task import task
|
||||
|
||||
gobject.threads_init()
|
||||
@@ -92,27 +91,14 @@ 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
|
||||
responses = None
|
||||
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())
|
||||
except accurip.EntryNotFound:
|
||||
print('Accuraterip entry not found')
|
||||
|
||||
if responses:
|
||||
logger.debug('%d AccurateRip responses found.' % len(responses))
|
||||
|
||||
if responses[0].cddbDiscId != table.getCDDBDiscId():
|
||||
logger.warning("AccurateRip response discid different: %s",
|
||||
responses[0].cddbDiscId)
|
||||
@@ -120,17 +106,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 +137,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)
|
||||
@@ -163,7 +151,7 @@ CD in the AccurateRip database."""
|
||||
# last one (to avoid readers that can't do overread
|
||||
for track in range(2, (len(table.tracks) + 1) - 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 +159,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 +176,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 +194,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' %
|
||||
|
||||
Reference in New Issue
Block a user