Skip to content

Commit

Permalink
Merge pull request #187 from RecursiveForest/rewrite-accuraterip
Browse files Browse the repository at this point in the history
AccurateRip V2 support
  • Loading branch information
JoeLametta authored Sep 15, 2017
2 parents 7ae27de + d4aad57 commit a3e9260
Show file tree
Hide file tree
Showing 20 changed files with 840 additions and 868 deletions.
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: 43 additions & 158 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 @@ -346,41 +323,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 @@ -393,9 +356,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 @@ -462,13 +425,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 @@ -501,113 +462,37 @@ def ripIfNotRipped(number):
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'))

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)
self.program.write_m3u(discName)

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)
self.program.verifyImage(self.runner, self.ittoc)
except accurip.EntryNotFound:
print('AccurateRip entry not found')

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
Loading

0 comments on commit a3e9260

Please sign in to comment.