def add_arguments(self): loggers = result.getLoggers().keys() default_offset = None info = drive.getDeviceInfo(self.opts.device) if info: try: default_offset = config.Config().getReadOffset(*info) sys.stdout.write("Using configured read offset %d\n" % default_offset) except KeyError: pass _CD.add_arguments(self.parser) self.parser.add_argument('-L', '--logger', action="store", dest="logger", default='whipper', help="logger to use (choose from '" "', '".join(loggers) + "')") # FIXME: get from config self.parser.add_argument('-o', '--offset', action="store", dest="offset", default=default_offset, help="sample read offset") self.parser.add_argument('-x', '--force-overread', action="store_true", dest="overread", default=False, help="Force overreading into the " "lead-out portion of the disc. Works only " "if the patched cdparanoia package is " "installed and the drive " "supports this feature. ") self.parser.add_argument('-O', '--output-directory', action="store", dest="output_directory", default=os.path.relpath(os.getcwd()), help="output directory; will be included " "in file paths in log") self.parser.add_argument('-W', '--working-directory', action="store", dest="working_directory", help="working directory; whipper will " "change to this directory " "and files will be created relative to " "it when not absolute") self.parser.add_argument('--track-template', action="store", dest="track_template", default=DEFAULT_TRACK_TEMPLATE, help="template for track file naming") self.parser.add_argument('--disc-template', action="store", dest="disc_template", default=DEFAULT_DISC_TEMPLATE, help="template for disc file naming") self.parser.add_argument('-U', '--unknown', action="store_true", dest="unknown", help="whether to continue ripping if " "the CD is unknown", default=False) self.parser.add_argument('--cdr', action="store_true", dest="cdr", help="whether to continue ripping if " "the disc is a CD-R", default=False)
def testDisambiguateOnRelease(self): """Test that disambiguation gets placed in the same part of the path as the release name. See https://github.com/JoeLametta/whipper/issues/127""" prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = 'Guy Davis' md.sortName = 'Davis, Guy' md.title = 'Call Down the Thunder' md.release = '1996' md.catalogNumber = 'RHR CD 89' prog.metadata = md templates = { u'%A/%d - %y': u'Guy Davis/Call Down the Thunder - 1996 (RHR CD 89)', # noqa: E501 u'%A - %d - %y': u'Guy Davis - Call Down the Thunder - 1996 (RHR CD 89)', # noqa: E501 u'%A/%y/%d': u'Guy Davis/1996/Call Down the Thunder (RHR CD 89)', u'%y/%d/%A': u'1996/Call Down the Thunder (RHR CD 89)/Guy Davis', u'%d/%A/%y': u'Call Down the Thunder (RHR CD 89)/Guy Davis/1996', } for template, expected_path in templates.iteritems(): path = prog.getPath(u'/tmp', template, 'mbdiscid', 0, disambiguate=True) # noqa: E501 self.assertEquals(path, u'/tmp/' + expected_path)
def do(self): runner = task.SyncRunner() t = cdparanoia.AnalyzeTask(self.options.device) runner.run(t) if t.defeatsCache is None: sys.stdout.write( 'Cannot analyze the drive. Is there a CD in it?\n') return if not t.defeatsCache: sys.stdout.write( 'cdparanoia cannot defeat the audio cache on this drive.\n') else: sys.stdout.write( 'cdparanoia can defeat the audio cache on this drive.\n') info = drive.getDeviceInfo(self.options.device) if not info: sys.stdout.write('Drive caching behaviour not saved:' 'could not get device info (requires pycdio).\n') return sys.stdout.write( 'Adding drive cache behaviour to configuration file.\n') config.Config().setDefeatsCache(info[0], info[1], info[2], t.defeatsCache)
def testStandardTemplateEmpty(self): prog = program.Program(config.Config()) path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE, 'mbdiscid', None) self.assertEqual(path, ('/tmp/unknown/Unknown Artist - mbdiscid/' 'Unknown Artist - mbdiscid'))
def testMusicBrainz(self): # output from mb-submit-disc: # https://musicbrainz.org/cdtoc/attach?toc=1+12+195856+150+ # 15687+31841+51016+66616+81352+99559+116070+133243+149997+161710+ # 177832&tracks=12&id=KnpGsLhvH.lPrNc1PBL21lb9Bg4- # however, not (yet) in MusicBrainz database # setup to test if MusicBrainz submit URL is hardcoded to use https env_original = environ.get('XDG_CONFIG_HOME') tmp_conf = mkdtemp(suffix='.config') # HACK: hijack env var to avoid overwriting user's whipper config file # This works because directory.config_path() builds the location where # whipper's conf will reside based on the value of env XDG_CONFIG_HOME environ['XDG_CONFIG_HOME'] = tmp_conf self.config = config.Config() self.config._parser.add_section('musicbrainz') self.config._parser.set('musicbrainz', 'server', 'http://musicbrainz.org') self.config.write() self.assertEqual(self.table.getMusicBrainzSubmitURL(), "http://musicbrainz.org/cdtoc/attach?toc=1+12+1958" "56+150+15687+31841+51016+66616+81352+99559+116070+13" "3243+149997+161710+177832&tracks=12&id=KnpGsLhvH.lPr" "Nc1PBL21lb9Bg4-") # HACK: continuation - restore original env value (if defined) if env_original is not None: environ['XDG_CONFIG_HOME'] = env_original else: environ.pop('XDG_CONFIG_HOME', None) self.assertEqual(self.table.getMusicBrainzDiscId(), "KnpGsLhvH.lPrNc1PBL21lb9Bg4-") rmtree(tmp_conf)
def do(self): paths = drive.getAllDevicePaths() self.config = config.Config() if not paths: logger.critical('no drives found. Create /dev/cdrom ' 'if you have a CD drive, or install ' 'pycdio for better detection') return try: import cdio as _ # noqa: F401 (TODO: fix it in a separate PR?) except ImportError: logger.error('install pycdio for vendor/model/release detection') return for path in paths: vendor, model, release = drive.getDeviceInfo(path) print("drive: %s, vendor: %s, model: %s, release: %s" % (path, vendor, model, release)) try: offset = self.config.getReadOffset(vendor, model, release) print(" Configured read offset: %d" % offset) except KeyError: # Note spaces at the beginning for pretty terminal output logger.warning("no read offset found. " "Run 'whipper offset find'") try: defeats = self.config.getDefeatsCache(vendor, model, release) print(" Can defeat audio cache: %s" % defeats) except KeyError: logger.warning("unknown whether audio cache can be " "defeated. Run 'whipper drive analyze'")
def do(self): prog = program.Program(config.Config()) runner = task.SyncRunner() for arg in self.options.cuefile: arg = arg.decode('utf-8') cueImage = image.Image(arg) cueImage.setup(runner) # FIXME: this feels like we're poking at internals. prog.cuePath = arg prog.result = result.RipResult() for track in cueImage.table.tracks: tr = result.TrackResult() tr.number = track.number prog.result.tracks.append(tr) 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)
def testIssue66TemplateFilled(self): prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = md.sortName = 'Jeff Buckley' md.title = 'Grace' path = prog.getPath(u'/tmp', u'%A/%d', 'mbdiscid', md, 0) self.assertEquals(path, u'/tmp/Jeff Buckley/Grace')
def testStandardTemplateFilled(self): prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = md.sortName = 'Jeff Buckley' md.releaseTitle = 'Grace' path = prog.getPath('/tmp', DEFAULT_DISC_TEMPLATE, 'mbdiscid', md, 0) self.assertEqual(path, ('/tmp/unknown/Jeff Buckley - Grace/' 'Jeff Buckley - Grace'))
def main(): # set user agent musicbrainzngs.set_useragent("whipper", whipper.__version__, "https://github.com/JoeLametta/whipper") try: server = config.Config().get_musicbrainz_server() except KeyError, e: sys.stderr.write('whipper: %s\n' % e.message) sys.exit()
def main(): server = config.Config().get_musicbrainz_server() musicbrainzngs.set_hostname(server) # Find whipper's plugins paths (local paths have higher priority) plugins_p = [directory.data_path('plugins')] # local path (in $HOME) if hasattr(sys, 'real_prefix'): # no getsitepackages() in virtualenv plugins_p.append( get_python_lib( plat_specific=False, standard_lib=False, prefix='/usr/local') + '/whipper/plugins') plugins_p.append( get_python_lib(plat_specific=False, standard_lib=False) + '/whipper/plugins') else: plugins_p += [x + '/whipper/plugins' for x in site.getsitepackages()] # register plugins with pkg_resources distributions, _ = pkg_resources.working_set.find_plugins( pkg_resources.Environment(plugins_p)) list(map(pkg_resources.working_set.add, distributions)) try: cmd = Whipper(sys.argv[1:], os.path.basename(sys.argv[0]), None) ret = cmd.do() except SystemError as e: logger.critical("SystemError: %s", e) if (isinstance(e, common.EjectError) and cmd.options.eject in ('failure', 'always')): eject_device(e.device) return 255 except RuntimeError as e: print(e) return 1 except KeyboardInterrupt: return 2 except ImportError as e: raise except task.TaskException as e: if isinstance(e.exception, ImportError): raise ImportError(e.exception) elif isinstance(e.exception, common.MissingDependencyException): logger.critical('missing dependency "%s"', e.exception.dependency) return 255 if isinstance(e.exception, common.EmptyError): logger.debug("EmptyError: %s", e.exception) logger.critical('could not create encoded file') return 255 # in python3 we can instead do `raise e.exception` as that would show # the exception's original context logger.critical(e.exceptionMessage) return 255 return ret if ret else 0
def _foundOffset(self, device, offset): print('\nRead offset of device is: %d.' % offset) info = drive.getDeviceInfo(device) if not info: logger.error('offset not saved: ' 'could not get device info (requires pycdio)') return logger.info('adding read offset to configuration file') config.Config().setReadOffset(info[0], info[1], info[2], offset)
def _foundOffset(self, device, offset): sys.stdout.write('\nRead offset of device is: %d.\n' % offset) info = drive.getDeviceInfo(device) if not info: sys.stdout.write('Offset not saved: could not get ' 'device info (requires pycdio).\n') return sys.stdout.write('Adding read offset to configuration file.\n') config.Config().setReadOffset(info[0], info[1], info[2], offset)
def testVerify(self): path = os.path.join(os.path.dirname(__file__), 'dBAR-020-002e5023-029d8e49-040eaa14.bin') data = open(path, "rb").read() responses = accurip.getAccurateRipResponses(data) # these crc's were calculated from an actual rip checksums = [ 1644890007, 2945205445, 3983436658, 1528082495, 1203704270, 1163423644, 3649097244, 100524219, 1583356174, 373652058, 1842579359, 2850056507, 1329730252, 2526965856, 2525886806, 209743350, 3184062337, 2099956663, 2943874164, 2321637196 ] prog = program.Program(config.Config()) prog.result = result.RipResult() # fill it with empty trackresults for i, c in enumerate(checksums): r = result.TrackResult() r.number = i + 1 prog.result.tracks.append(r) prog._verifyImageWithChecksums(responses, checksums) # now check if the results were filled in properly tr = prog.result.getTrackResult(1) self.assertEquals(tr.accurip, False) self.assertEquals(tr.ARDBMaxConfidence, 0) self.assertEquals(tr.ARDBCRC, 0) self.assertEquals(tr.ARDBCRC, 0) tr = prog.result.getTrackResult(2) self.assertEquals(tr.accurip, True) self.assertEquals(tr.ARDBMaxConfidence, 2) self.assertEquals(tr.ARDBCRC, checksums[2 - 1]) tr = prog.result.getTrackResult(10) self.assertEquals(tr.accurip, False) self.assertEquals(tr.ARDBMaxConfidence, 2) # we know track 10 was ripped wrong self.assertNotEquals(tr.ARDBCRC, checksums[10 - 1]) res = prog.getAccurateRipResults() self.assertEquals( res[1 - 1], "Track 1: rip NOT accurate (not found) " "[620b0797], DB [notfound]") self.assertEquals( res[2 - 1], "Track 2: rip accurate (max confidence 2) " "[af8c44c5], DB [af8c44c5]") self.assertEquals( res[10 - 1], "Track 10: rip NOT accurate (max confidence 2) " "[16457a5a], DB [eb6e55b4]")
def getMusicBrainzSubmitURL(self): host = config.Config().get_musicbrainz_server() discid = self.getMusicBrainzDiscId() values = self._getMusicBrainzValues() query = urlencode([ ('toc', ' '.join([str(v) for v in values])), ('tracks', self.getAudioTracks()), ('id', discid), ]) return urlunparse(('https', host, '/cdtoc/attach', '', query, ''))
def main(): try: server = config.Config().get_musicbrainz_server() except KeyError as e: sys.stderr.write('whipper: %s\n' % str(e)) sys.exit() musicbrainzngs.set_hostname(server) # register plugins with pkg_resources distributions, _ = pkg_resources.working_set.find_plugins( pkg_resources.Environment([directory.data_path('plugins')])) list(map(pkg_resources.working_set.add, distributions)) try: cmd = Whipper(sys.argv[1:], os.path.basename(sys.argv[0]), None) ret = cmd.do() except SystemError as e: sys.stderr.write('whipper: error: %s\n' % e) if (isinstance(e, common.EjectError) and cmd.options.eject in ('failure', 'always')): eject_device(e.device) return 255 except RuntimeError as e: print(e) return 1 except KeyboardInterrupt: return 2 except ImportError as e: raise except task.TaskException as e: if isinstance(e.exception, ImportError): raise ImportError(e.exception) elif isinstance(e.exception, common.MissingDependencyException): sys.stderr.write('whipper: error: missing dependency "%s"\n' % e.exception.dependency) return 255 if isinstance(e.exception, common.EmptyError): logger.debug("EmptyError: %r", str(e.exception)) sys.stderr.write('whipper: error: Could not create encoded file.\n' ) # noqa: E501 return 255 # in python3 we can instead do `raise e.exception` as that would show # the exception's original context sys.stderr.write(e.exceptionMessage) return 255 return ret if ret else 0
def do(self): prog = program.Program(config.Config(), stdout=sys.stdout) runner = task.SyncRunner() for arg in self.options.cuefile: sys.stdout.write('Retagging image %r\n' % arg) arg = arg.decode('utf-8') cueImage = image.Image(arg) cueImage.setup(runner) mbdiscid = cueImage.table.getMusicBrainzDiscId() sys.stdout.write('MusicBrainz disc id is %s\n' % mbdiscid) sys.stdout.write("MusicBrainz lookup URL %s\n" % cueImage.table.getMusicBrainzSubmitURL()) prog.metadata = prog.getMusicBrainz( cueImage.table, mbdiscid, release=self.options.release_id, # noqa: E501 country=self.options.country, prompt=self.options.prompt) if not prog.metadata: print 'Not in MusicBrainz database, skipping' continue prog.metadata.discid = mbdiscid # FIXME: this feels like we're poking at internals. prog.cuePath = arg prog.result = result.RipResult() for track in cueImage.table.tracks: path = cueImage.getRealPath(track.indexes[1].path) taglist = prog.getTagList(track.number) logger.debug( 'possibly retagging %r from cue path %r with taglist %r', path, arg, taglist) t = encode.SafeRetagTask(path, taglist) runner.run(t) path = os.path.basename(path) if t.changed: print 'Retagged %s' % path else: print '%s already tagged correctly' % path print
def do(self): paths = drive.getAllDevicePaths() self.config = config.Config() if not paths: sys.stdout.write('No drives found.\n') sys.stdout.write('Create /dev/cdrom if you have a CD drive, \n') sys.stdout.write('or install pycdio for better detection.\n') return try: import cdio as _ except ImportError: sys.stdout.write( 'Install pycdio for vendor/model/release detection.\n') return for path in paths: vendor, model, release = drive.getDeviceInfo(path) sys.stdout.write( "drive: %s, vendor: %s, model: %s, release: %s\n" % ( path, vendor, model, release)) try: offset = self.config.getReadOffset( vendor, model, release) sys.stdout.write( " Configured read offset: %d\n" % offset) except KeyError: sys.stdout.write( " No read offset found. Run 'whipper offset find'\n") try: defeats = self.config.getDefeatsCache( vendor, model, release) sys.stdout.write( " Can defeat audio cache: %s\n" % defeats) except KeyError: sys.stdout.write( " Unknown whether audio cache can be defeated. " "Run 'whipper drive analyze'\n") if not paths: sys.stdout.write('No drives found.\n')
def testAddDisambiguationUnitTest(self): """Unit test for Program.addDisambiguation().""" prog = program.Program(config.Config()) md = mbngs.DiscMetadata() # No relevant disambiguation metadata self.assertEquals(prog.addDisambiguation(u'Test', md), u'Test') # Only barcode available md.barcode = '033651008927' self.assertEquals(prog.addDisambiguation(u'Test', md), u'Test (033651008927)') # Both catalog number and barcode available md.catalogNumber = 'RHR CD 89' self.assertEquals(prog.addDisambiguation(u'Test', md), u'Test (RHR CD 89)')
def testDisambiguateOnReleaseOnlyOnce(self): """Test that disambiguation gets added only once.""" prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = 'Guy Davis' md.sortName = 'Davis, Guy' md.title = 'Call Down the Thunder' md.release = '1996' md.catalogNumber = 'RHR CD 89' prog.metadata = md template = u'%A/%d - %y/%d/%d' path = prog.getPath(u'/tmp', template, 'mbdiscid', 0, disambiguate=True) # noqa: E501 self.assertEquals( path, u'/tmp/Guy Davis/Call Down the Thunder - 1996 (RHR CD 89)/Call Down the Thunder/Call Down the Thunder' ) # noqa: E501
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() for track in cueImage.table.tracks: tr = result.TrackResult() tr.number = track.number prog.result.tracks.append(tr) prog.verifyImage(runner, responses) print "\n".join(prog.getAccurateRipResults()) + "\n"
def testDisambiguateOnNoReleaseTitle(self): """Test that disambiguation gets added even if there's no release title in the template.""" prog = program.Program(config.Config()) md = mbngs.DiscMetadata() md.artist = 'Guy Davis' md.sortName = 'Davis, Guy' md.title = 'Call Down the Thunder' md.release = '1996' md.catalogNumber = 'RHR CD 89' prog.metadata = md templates = { u'%A/%y': u'Guy Davis/1996 (RHR CD 89)', u'%A - %y': u'Guy Davis - 1996 (RHR CD 89)', u'%y/%A': u'1996/Guy Davis (RHR CD 89)', } for template, expected_path in templates.iteritems(): path = prog.getPath(u'/tmp', template, 'mbdiscid', 0, disambiguate=True) # noqa: E501 self.assertEquals(path, u'/tmp/' + expected_path)
def do(self): runner = task.SyncRunner() t = cdparanoia.AnalyzeTask(self.options.device) runner.run(t) if t.defeatsCache is None: logger.critical('cannot analyze the drive: is there a CD in it?') return if not t.defeatsCache: logger.info('cdparanoia cannot defeat the audio cache ' 'on this drive') else: logger.info('cdparanoia can defeat the audio cache on this drive') info = drive.getDeviceInfo(self.options.device) if not info: logger.error('drive caching behaviour not saved: ' 'could not get device info') return logger.info('adding drive cache behaviour to configuration file') config.Config().setDefeatsCache(info[0], info[1], info[2], t.defeatsCache)
def do(self): self.config = config.Config() self.program = program.Program(self.config, record=self.options.record) self.runner = task.SyncRunner() # if the device is mounted (data session), unmount it self.device = self.options.device logger.info('checking device %s', self.device) if self.options.drive_auto_close is True: utils.load_device(self.device) utils.unmount_device(self.device) # Exit and inform the user if there's no CD in the disk drive if drive.get_cdrom_drive_status(self.device) == 1: # rc 1 -> no disc raise OSError("no CD detected, please insert one and retry") # first, read the normal TOC, which is fast self.ittoc = self.program.getFastToc(self.runner, self.device) # already show us some info based on this self.program.getRipResult() print("CDDB disc id: %s" % self.ittoc.getCDDBDiscId()) self.mbdiscid = self.ittoc.getMusicBrainzDiscId() print("MusicBrainz disc id %s" % self.mbdiscid) print("MusicBrainz lookup URL %s" % self.ittoc.getMusicBrainzSubmitURL()) self.program.metadata = ( self.program.getMusicBrainz(self.ittoc, self.mbdiscid, release=self.options.release_id, country=self.options.country, prompt=self.options.prompt) ) if not self.program.metadata: # fall back to FreeDB for lookup cddbid = self.ittoc.getCDDBValues() cddbmd = self.program.getCDDB(cddbid) if cddbmd: logger.info('FreeDB identifies disc as %s', cddbmd) # also used by rip cd info if not getattr(self.options, 'unknown', False): logger.critical("unable to retrieve disc metadata, " "--unknown argument not passed") return -1 self.program.result.isCdr = cdrdao.DetectCdr(self.device) if (self.program.result.isCdr and not getattr(self.options, 'cdr', False)): logger.critical("inserted disc seems to be a CD-R, " "--cdr not passed") return -1 # Change working directory before cdrdao's task if getattr(self.options, 'working_directory', False): os.chdir(os.path.expanduser(self.options.working_directory)) if hasattr(self.options, 'output_directory'): out_bpath = self.options.output_directory # Needed to preserve cdrdao's tocfile out_fpath = self.program.getPath(out_bpath, self.options.disc_template, self.mbdiscid, self.program.metadata) else: out_fpath = None # now, read the complete index table, which is slower offset = getattr(self.options, 'offset', 0) self.itable = self.program.getTable(self.runner, self.ittoc.getCDDBDiscId(), self.ittoc.getMusicBrainzDiscId(), self.device, offset, out_fpath) assert self.itable.getCDDBDiscId() == self.ittoc.getCDDBDiscId(), \ "full table's id %s differs from toc id %s" % ( self.itable.getCDDBDiscId(), self.ittoc.getCDDBDiscId()) assert self.itable.getMusicBrainzDiscId() == \ self.ittoc.getMusicBrainzDiscId(), \ "full table's mb id %s differs from toc id mb %s" % ( self.itable.getMusicBrainzDiscId(), self.ittoc.getMusicBrainzDiscId()) if self.program.metadata: self.program.metadata.discid = self.ittoc.getMusicBrainzDiscId() # result self.program.result.cdrdaoVersion = cdrdao.version() self.program.result.cdparanoiaVersion = \ cdparanoia.getCdParanoiaVersion() info = drive.getDeviceInfo(self.device) if info: try: self.program.result.cdparanoiaDefeatsCache = \ self.config.getDefeatsCache(*info) except KeyError as e: logger.debug('got key error: %r', (e, )) self.program.result.artist = self.program.metadata \ and self.program.metadata.artist \ or 'Unknown Artist' self.program.result.title = self.program.metadata \ and self.program.metadata.releaseTitle \ or 'Unknown Title' _, self.program.result.vendor, self.program.result.model, \ self.program.result.release = \ cdio.Device(self.device).get_hwinfo() self.program.result.metadata = self.program.metadata ret = self.doCommand() if (self.options.eject == 'success' and self.eject or self.options.eject == 'always'): utils.eject_device(self.device) return ret
def setUp(self): fd, self._path = tempfile.mkstemp(suffix='.whipper.test.config') os.close(fd) self._config = config.Config(self._path)
def add_arguments(self): loggers = list(result.getLoggers()) default_offset = None info = drive.getDeviceInfo(self.opts.device) if info: try: default_offset = config.Config().getReadOffset(*info) logger.info("using configured read offset %d", default_offset) except KeyError: pass _CD.add_arguments(self.parser) self.parser.add_argument('-L', '--logger', action="store", dest="logger", default='whipper', help=("logger to use (choose from: '%s" % "', '".join(loggers) + "')")) self.parser.add_argument('-o', '--offset', action="store", dest="offset", default=default_offset, help="sample read offset") self.parser.add_argument('-x', '--force-overread', action="store_true", dest="overread", default=False, help="Force overreading into the " "lead-out portion of the disc. Works only " "if the patched cdparanoia package is " "installed and the drive " "supports this feature. ") self.parser.add_argument('-O', '--output-directory', action="store", dest="output_directory", default=os.curdir, help="output directory; will be included " "in file paths in log") self.parser.add_argument('-W', '--working-directory', action="store", dest="working_directory", help="working directory; whipper will " "change to this directory " "and files will be created relative to " "it when not absolute") self.parser.add_argument('--track-template', action="store", dest="track_template", default=DEFAULT_TRACK_TEMPLATE, help="template for track file naming") self.parser.add_argument('--disc-template', action="store", dest="disc_template", default=DEFAULT_DISC_TEMPLATE, help="template for disc file naming") self.parser.add_argument('-U', '--unknown', action="store_true", dest="unknown", help="whether to continue ripping if " "the CD is unknown", default=False) self.parser.add_argument('--cdr', action="store_true", dest="cdr", help="whether to continue ripping if " "the disc is a CD-R", default=False) self.parser.add_argument('-C', '--cover-art', action="store", dest="cover_art", help="fetch cover art and save it as " "standalone file, embed into FLAC files " "or perform both actions: file, embed, " "complete option values respectively", choices=['file', 'embed', 'complete'], default=None) self.parser.add_argument('-r', '--max-retries', action="store", dest="max_retries", help="number of rip attempts before giving " "up if can't rip a track. This defaults to " "{}; 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 main(): server = config.Config().get_musicbrainz_server() https_enabled = server['scheme'] == 'https' try: musicbrainzngs.set_hostname(server['netloc'], https_enabled) # Parameter 'use_https' is missing in versions of musicbrainzngs < 0.7 except TypeError: logger.warning("Parameter 'use_https' is missing in versions of " "musicbrainzngs < 0.7. This means whipper will only " "be able to communicate with the configured " "MusicBrainz server ('%s') over plain HTTP. If a " "custom server which speaks HTTPS only has been " "declared, a suitable version of the " "musicbrainzngs module will be needed " "to make it work in whipper.", server['netloc']) musicbrainzngs.set_hostname(server['netloc']) # Find whipper's plugins paths (local paths have higher priority) plugins_p = [directory.data_path('plugins')] # local path (in $HOME) if hasattr(sys, 'real_prefix'): # no getsitepackages() in virtualenv plugins_p.append( get_python_lib(plat_specific=False, standard_lib=False, prefix='/usr/local') + '/whipper/plugins') plugins_p.append(get_python_lib(plat_specific=False, standard_lib=False) + '/whipper/plugins') else: plugins_p += [x + '/whipper/plugins' for x in site.getsitepackages()] # register plugins with pkg_resources distributions, _ = pkg_resources.working_set.find_plugins( pkg_resources.Environment(plugins_p) ) list(map(pkg_resources.working_set.add, distributions)) try: cmd = Whipper(sys.argv[1:], os.path.basename(sys.argv[0]), None) ret = cmd.do() except SystemError as e: logger.critical("SystemError: %s", e) if (isinstance(e, common.EjectError) and cmd.options.eject in ('failure', 'always')): # XXX: Pylint, instance of 'SystemError' has no 'device' member eject_device(e.device) return 255 except RuntimeError as e: print(e) return 1 except KeyboardInterrupt: return 2 except ImportError: raise except task.TaskException as e: if isinstance(e.exception, ImportError): raise ImportError(e.exception) elif isinstance(e.exception, common.MissingDependencyException): logger.critical('missing dependency "%s"', e.exception.dependency) return 255 if isinstance(e.exception, common.EmptyError): logger.debug("EmptyError: %s", e.exception) logger.critical('could not create encoded file') return 255 # in python3 we can instead do `raise e.exception` as that would show # the exception's original context logger.critical(e.exceptionMessage) return 255 return ret if ret else 0
def __init__(self, argv, prog_name, opts): self.opts = opts # for Rip.add_arguments() self.prog_name = prog_name self.init_parser() self.add_arguments() config_section = prog_name.replace(' ', '.') defaults = {} for action in self.parser._actions: val = None if isinstance(action, argparse._StoreAction): val = config.Config().get(config_section, action.dest) elif isinstance( action, (argparse._StoreTrueAction, argparse._StoreFalseAction)): val = config.Config().getboolean(config_section, action.dest) if val is not None: defaults[action.dest] = val self.parser.set_defaults(**defaults) if hasattr(self, 'subcommands'): self.parser.add_argument('remainder', nargs=argparse.REMAINDER, help=argparse.SUPPRESS) if self.device_option: # pick the first drive as default drives = drive.getAllDevicePaths() if not drives: msg = 'No CD-DA drives found!' logger.critical(msg) # whipper exited with return code 3 here raise IOError(msg) self.parser.add_argument('-d', '--device', action="store", dest="device", default=drives[0], help="CD-DA device") self.options = self.parser.parse_args(argv, namespace=opts) if self.device_option: # this can be a symlink to another device self.options.device = os.path.realpath(self.options.device) if not os.path.exists(self.options.device): msg = 'CD-DA device %s not found!' % self.options.device logger.critical(msg) raise IOError(msg) self.handle_arguments() if hasattr(self, 'subcommands'): if not self.options.remainder: self.parser.print_help() raise SystemExit() if not self.options.remainder[0] in self.subcommands: logger.critical("incorrect subcommand: %s", self.options.remainder[0]) raise SystemExit(1) self.cmd = self.subcommands[self.options.remainder[0]]( self.options.remainder[1:], prog_name + " " + self.options.remainder[0], self.options)
def do(self): self.config = config.Config() self.program = program.Program(self.config, record=self.options.record, stdout=sys.stdout) self.runner = task.SyncRunner() # if the device is mounted (data session), unmount it self.device = self.options.device sys.stdout.write('Checking device %s\n' % self.device) utils.load_device(self.device) utils.unmount_device(self.device) # first, read the normal TOC, which is fast self.ittoc = self.program.getFastToc(self.runner, self.options.toc_pickle, self.device) # already show us some info based on this self.program.getRipResult(self.ittoc.getCDDBDiscId()) sys.stdout.write("CDDB disc id: %s\n" % self.ittoc.getCDDBDiscId()) self.mbdiscid = self.ittoc.getMusicBrainzDiscId() sys.stdout.write("MusicBrainz disc id %s\n" % self.mbdiscid) sys.stdout.write("MusicBrainz lookup URL %s\n" % self.ittoc.getMusicBrainzSubmitURL()) self.program.metadata = ( self.program.getMusicBrainz(self.ittoc, self.mbdiscid, release=self.options.release_id, country=self.options.country, prompt=self.options.prompt) ) if not self.program.metadata: # fall back to FreeDB for lookup cddbid = self.ittoc.getCDDBValues() cddbmd = self.program.getCDDB(cddbid) if cddbmd: sys.stdout.write('FreeDB identifies disc as %s\n' % cddbmd) # also used by rip cd info if not getattr(self.options, 'unknown', False): logger.critical("unable to retrieve disc metadata, " "--unknown not passed") return -1 self.program.result.isCdr = cdrdao.DetectCdr(self.device) if (self.program.result.isCdr and not getattr(self.options, 'cdr', False)): logger.critical("inserted disc seems to be a CD-R, " "--cdr not passed") return -1 # now, read the complete index table, which is slower self.itable = self.program.getTable(self.runner, self.ittoc.getCDDBDiscId(), self.ittoc.getMusicBrainzDiscId(), self.device, self.options.offset) assert self.itable.getCDDBDiscId() == self.ittoc.getCDDBDiscId(), \ "full table's id %s differs from toc id %s" % ( self.itable.getCDDBDiscId(), self.ittoc.getCDDBDiscId()) assert self.itable.getMusicBrainzDiscId() == \ self.ittoc.getMusicBrainzDiscId(), \ "full table's mb id %s differs from toc id mb %s" % ( self.itable.getMusicBrainzDiscId(), self.ittoc.getMusicBrainzDiscId()) assert self.itable.accuraterip_path() == \ self.ittoc.accuraterip_path(), \ "full table's AR URL %s differs from toc AR URL %s" % ( self.itable.accuraterip_url(), self.ittoc.accuraterip_url()) if self.program.metadata: self.program.metadata.discid = self.ittoc.getMusicBrainzDiscId() # result self.program.result.cdrdaoVersion = cdrdao.getCDRDAOVersion() self.program.result.cdparanoiaVersion = \ cdparanoia.getCdParanoiaVersion() info = drive.getDeviceInfo(self.device) if info: try: self.program.result.cdparanoiaDefeatsCache = \ self.config.getDefeatsCache(*info) except KeyError as e: logger.debug('Got key error: %r' % (e, )) self.program.result.artist = self.program.metadata \ and self.program.metadata.artist \ or 'Unknown Artist' self.program.result.title = self.program.metadata \ and self.program.metadata.title \ or 'Unknown Title' _, self.program.result.vendor, self.program.result.model, \ self.program.result.release = \ cdio.Device(self.device).get_hwinfo() self.doCommand() if self.options.eject in ('success', 'always'): utils.eject_device(self.device)
def testGetAccurateRipResults(self): prog = program.Program(config.Config()) prog.result = result.RipResult() prog.result.tracks = self._tracks prog.getAccurateRipResults()