diff --git a/man/whipper-cd-rip.rst b/man/whipper-cd-rip.rst index b09566f..2b8447b 100644 --- a/man/whipper-cd-rip.rst +++ b/man/whipper-cd-rip.rst @@ -69,6 +69,10 @@ Options | Number of rip attempts before giving up if can't rip a track. This | defaults to 5; 0 means infinity. +| **-k** | **--keep-going** +| continue ripping further tracks instead of giving up if a track can't be +| ripped + Template schemes ================ diff --git a/whipper/command/cd.py b/whipper/command/cd.py index 853902b..205e650 100644 --- a/whipper/command/cd.py +++ b/whipper/command/cd.py @@ -192,13 +192,13 @@ class _CD(BaseCommand): cdio.Device(self.device).get_hwinfo() self.program.result.metadata = self.program.metadata - self.doCommand() + ret = self.doCommand() if (self.options.eject == 'success' and self.eject or self.options.eject == 'always'): utils.eject_device(self.device) - return None + return ret def doCommand(self): pass @@ -220,6 +220,9 @@ class Info(_CD): class Rip(_CD): summary = "rip CD" # see whipper.common.program.Program.getPath for expansion + skipped_tracks = [] + # this holds tracks that fail to rip - + # currently only used when the --keep-going option is used description = """ Rips a CD. @@ -310,6 +313,11 @@ Log files will log the path to tracks relative to this directory. "{}; 0 means " "infinity.".format(DEFAULT_MAX_RETRIES), default=DEFAULT_MAX_RETRIES) + self.parser.add_argument('-k', '--keep-going', + action='store_true', + help="continue ripping further tracks " + "instead of giving up if a track " + "can't be ripped") def handle_arguments(self): self.options.output_directory = os.path.expanduser( @@ -476,18 +484,32 @@ Log files will log the path to tracks relative to this directory. tries -= 1 logger.critical('giving up on track %d after %d times', number, tries) - raise RuntimeError("track can't be ripped. " - "Rip attempts number is equal to %d", - self.options.max_retries) - if trackResult.testcrc == trackResult.copycrc: - logger.info('CRCs match for track %d', number) + if self.options.keep_going: + logger.warning("track %d failed to rip.", number) + logger.debug("adding %s to skipped_tracks", + trackResult) + self.skipped_tracks.append(trackResult) + logger.debug("skipped_tracks = %s", + self.skipped_tracks) + trackResult.skipped = True + else: + raise RuntimeError("track can't be ripped. " + "Rip attempts number is equal " + "to %d", + self.options.max_retries) + if trackResult in self.skipped_tracks: + print("Skipping CRC comparison for track %d " + "due to rip failure" % number) else: - raise RuntimeError( - "CRCs did not match for track %d" % number - ) + if trackResult.testcrc == trackResult.copycrc: + logger.info('CRCs match for track %d', number) + else: + raise RuntimeError( + "CRCs did not match for track %d" % number + ) - print('Peak level: %.6f' % (trackResult.peak / 32768.0)) - print('Rip quality: {:.2%}'.format(trackResult.quality)) + print('Peak level: %.6f' % (trackResult.peak / 32768.0)) + print('Rip quality: {:.2%}'.format(trackResult.quality)) # overlay this rip onto the Table if number == 0: @@ -507,7 +529,8 @@ Log files will log the path to tracks relative to this directory. self.itable.getTrackStart(1), number) else: self.itable.setFile(number, 1, trackResult.filename, - self.itable.getTrackLength(number), number) + self.itable.getTrackLength(number), + number) # check for hidden track one audio htoa = self.program.getHTOA() @@ -542,6 +565,12 @@ Log files will log the path to tracks relative to this directory. logger.debug('writing m3u file for %r', discName) self.program.write_m3u(discName) + if len(self.skipped_tracks) > 0: + logger.warning("the generated cue sheet references %d track(s) " + "which failed to rip so the associated file(s) " + "won't be available", len(self.skipped_tracks)) + self.program.skipped_tracks = self.skipped_tracks + try: self.program.verifyImage(self.runner, self.itable) except accurip.EntryNotFound: @@ -551,6 +580,11 @@ Log files will log the path to tracks relative to this directory. self.program.writeLog(discName, self.logger) + if len(self.skipped_tracks) > 0: + logger.warning('%d tracks have been skipped from this rip attempt', + len(self.skipped_tracks)) + return 5 + class CD(BaseCommand): summary = "handle CDs" diff --git a/whipper/common/accurip.py b/whipper/common/accurip.py index 8f504fd..9be9a94 100644 --- a/whipper/common/accurip.py +++ b/whipper/common/accurip.py @@ -21,6 +21,7 @@ import struct import whipper +import os from urllib.error import URLError, HTTPError from urllib.request import urlopen, Request @@ -111,7 +112,11 @@ def calculate_checksums(track_paths): logger.debug('checksumming %d tracks', track_count) # This is done sequentially because it is very fast. for i, path in enumerate(track_paths): - v1_sum, v2_sum = accuraterip_checksum(path, i+1, track_count) + if os.path.exists(path): + v1_sum, v2_sum = accuraterip_checksum(path, i+1, track_count) + else: + logger.warning('Can\'t checksum %s; path doesn\'t exist', path) + v1_sum, v2_sum = None, None if v1_sum is None: logger.error('could not calculate AccurateRip v1 checksum ' 'for track %d %r', i + 1, path) diff --git a/whipper/common/program.py b/whipper/common/program.py index 302a276..58cd9a4 100644 --- a/whipper/common/program.py +++ b/whipper/common/program.py @@ -57,6 +57,7 @@ class Program: metadata = None outdir = None result = None + skipped_tracks = None def __init__(self, config, record=False): """ @@ -612,7 +613,12 @@ class Program: """ cueImage = image.Image(self.cuePath) # assigns track lengths - verifytask = image.ImageVerifyTask(cueImage) + if self.skipped_tracks is not None: + verifytask = image.ImageVerifyTask(cueImage, + [os.path.basename(t.filename) + for t in self.skipped_tracks]) + else: + verifytask = image.ImageVerifyTask(cueImage) runner.run(verifytask) if verifytask.exception: logger.error(verifytask.exceptionMessage) @@ -627,6 +633,7 @@ class Program: ]) if not (checksums and any(checksums['v1']) and any(checksums['v2'])): return False + return accurip.verify_result(self.result, responses, checksums) def write_m3u(self, discname): @@ -637,6 +644,8 @@ class Program: if not track.filename: # false positive htoa continue + if track.skipped: + continue if track.number == 0: length = (self.result.table.getTrackStart(1) / common.FRAMES_PER_SECOND) diff --git a/whipper/image/image.py b/whipper/image/image.py index 224af4a..7a356b7 100644 --- a/whipper/image/image.py +++ b/whipper/image/image.py @@ -120,7 +120,7 @@ class ImageVerifyTask(task.MultiSeparateTask): description = "Checking tracks" lengths = None - def __init__(self, image): + def __init__(self, image, skipped_tracks=[]): task.MultiSeparateTask.__init__(self) self._image = image @@ -147,7 +147,17 @@ class ImageVerifyTask(task.MultiSeparateTask): length = cue.getTrackLength(track) if length == -1: - path = image.getRealPath(index.path) + try: + path = image.getRealPath(index.path) + except KeyError: + logger.debug('Path not found; Checking ' + 'if %s is a skipped track', index.path) + if index.path in skipped_tracks: + logger.warning('Missing file %s due to skipped track', + index.path) + continue + else: + raise assert isinstance(path, str), "%r is not str" % path logger.debug('schedule scan of audio length of %r', path) taskk = AudioLengthTask(path) diff --git a/whipper/result/logger.py b/whipper/result/logger.py index 459a32f..b7043ad 100644 --- a/whipper/result/logger.py +++ b/whipper/result/logger.py @@ -15,6 +15,7 @@ class WhipperLogger(result.Logger): _accuratelyRipped = 0 _inARDatabase = 0 _errors = False + _skippedTracks = False def log(self, ripResult, epoch=time.time()): """Return logfile as string.""" @@ -139,6 +140,8 @@ class WhipperLogger(result.Logger): if self._errors: message = "There were errors" + elif self._skippedTracks: + message = "Some tracks were not ripped (skipped)" else: message = "No errors occurred" data["Health status"] = message @@ -242,8 +245,12 @@ class WhipperLogger(result.Logger): data["Result"] = "Track not present in AccurateRip database" track["AccurateRip %s" % v] = data + # Check if track has been skipped + if trackResult.skipped: + track["Status"] = "Track not ripped (skipped)" + self._skippedTracks = True # Check if Test & Copy CRCs are equal - if trackResult.testcrc == trackResult.copycrc: + elif trackResult.testcrc == trackResult.copycrc: track["Status"] = "Copy OK" else: self._errors = True diff --git a/whipper/result/result.py b/whipper/result/result.py index 461e408..47d14a0 100644 --- a/whipper/result/result.py +++ b/whipper/result/result.py @@ -38,6 +38,7 @@ class TrackResult: copycrc = None AR = None classVersion = 3 + skipped = False def __init__(self): """