Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AccurateRip V2 support #187

Merged
merged 16 commits into from
Sep 15, 2017
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ install:
- sudo apt-get -qq update
- sudo pip install --upgrade -qq pip
- sudo apt-get -qq install cdparanoia cdrdao flac libcdio-dev libiso9660-dev libsndfile1-dev python-cddb python-gobject python-musicbrainzngs python-mutagen python-setuptools sox swig
- sudo pip install pycdio
- sudo pip install pycdio requests

# Testing dependencies
- sudo apt-get -qq install python-twisted-core
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Whipper relies on the following packages in order to run correctly and provide a
- [python-cddb](http://cddb-py.sourceforge.net/), for showing but not using metadata if disc not available in the MusicBrainz DB
- [pycdio](https://pypi.python.org/pypi/pycdio/) (to avoid bugs please use `pycdio` **0.20** & `libcdio` >= **0.90** or, with previous `libcdio` versions, `pycdio` **0.17**), for drive identification
- Required for drive offset and caching behavior to be stored in the configuration file
- [requests](https://pypi.python.org/pypi/requests) for retrieving AccurateRip database entries
- [libsndfile](http://www.mega-nerd.com/libsndfile/), for reading wav files
- [flac](https://xiph.org/flac/), for reading flac files
- [sox](http://sox.sourceforge.net/), for track peak detection
Expand Down
18 changes: 8 additions & 10 deletions whipper/command/accurip.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -38,32 +38,30 @@ def add_arguments(self):
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):
sys.stdout.write("Track %d:\n" % (track + 1))
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]
Expand Down
201 changes: 44 additions & 157 deletions whipper/command/cd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -41,7 +40,7 @@
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 = '''
Expand All @@ -68,12 +67,6 @@


class _CD(BaseCommand):

"""
@type program: L{program.Program}
@ivar eject: whether to eject the drive after completing
"""

eject = True

@staticmethod
Expand Down Expand Up @@ -150,21 +143,11 @@ def do(self):
"--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" % (
Expand All @@ -174,10 +157,10 @@ def do(self):
"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()
Expand All @@ -200,15 +183,9 @@ def do(self):
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()

Expand Down Expand Up @@ -348,41 +325,27 @@ def doCommand(self):
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)
Expand All @@ -395,9 +358,9 @@ def ripIfNotRipped(number):

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

Expand Down Expand Up @@ -464,13 +427,11 @@ def ripIfNotRipped(number):
"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))
Expand Down Expand Up @@ -507,109 +468,35 @@ def ripIfNotRipped(number):
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'))

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

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.write_m3u(discName, htoapath)

self.program.verifyImage(self.runner, responses)
if self.program.verifyImage(self.runner, self.ittoc, self.itable):
print('rip verified as accurate')
else:
print('rip NOT verified as accurate')

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)


Expand Down
7 changes: 6 additions & 1 deletion whipper/command/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading