class Itunes(MetaSource): item_types = { 'itunes_rating': types.INTEGER, # 0..100 scale 'itunes_playcount': types.INTEGER, 'itunes_skipcount': types.INTEGER, 'itunes_lastplayed': DateType(), 'itunes_lastskipped': DateType(), } def __init__(self, config, log): super(Itunes, self).__init__(config, log) config.add({'itunes': { 'library': '~/Music/iTunes/iTunes Library.xml' }}) # Load the iTunes library, which has to be the .xml one (not the .itl) library_path = config['itunes']['library'].as_filename() try: self._log.debug( u'loading iTunes library from {0}'.format(library_path)) with create_temporary_copy(library_path) as library_copy: raw_library = plistlib.readPlist(library_copy) except IOError as e: raise ConfigValueError(u'invalid iTunes library: ' + e.strerror) except Exception: # It's likely the user configured their '.itl' library (<> xml) if os.path.splitext(library_path)[1].lower() != '.xml': hint = u': please ensure that the configured path' \ u' points to the .XML library' else: hint = '' raise ConfigValueError(u'invalid iTunes library' + hint) # Make the iTunes library queryable using the path self.collection = {_norm_itunes_path(track['Location']): track for track in raw_library['Tracks'].values() if 'Location' in track} def sync_from_source(self, item): result = self.collection.get(util.bytestring_path(item.path).lower()) if not result: self._log.warning(u'no iTunes match found for {0}'.format(item)) return item.itunes_rating = result.get('Rating') item.itunes_playcount = result.get('Play Count') item.itunes_skipcount = result.get('Skip Count') if result.get('Play Date UTC'): item.itunes_lastplayed = mktime( result.get('Play Date UTC').timetuple()) if result.get('Skip Date'): item.itunes_lastskipped = mktime( result.get('Skip Date').timetuple())
class MetaSyncPlugin(BeetsPlugin): item_types = { 'amarok_rating': types.INTEGER, 'amarok_score': types.FLOAT, 'amarok_uid': types.STRING, 'amarok_playcount': types.INTEGER, 'amarok_firstplayed': DateType(), 'amarok_lastplayed': DateType() } def __init__(self): super(MetaSyncPlugin, self).__init__() def commands(self): cmd = ui.Subcommand('metasync', help='update metadata from music player libraries') cmd.parser.add_option('-p', '--pretend', action='store_true', help='show all changes but do nothing') cmd.parser.add_option('-s', '--source', action='store_false', default=self.config['source'].as_str_seq(), help="select specific sources to import from") cmd.parser.add_format_option() cmd.func = self.func return [cmd] def func(self, lib, opts, args): """Command handler for the metasync function. """ pretend = opts.pretend source = opts.source query = ui.decargs(args) sources = {} for player in source: __import__('beetsplug.metasync', fromlist=[str(player)]) module = 'beetsplug.metasync.' + player if module not in modules.keys(): log.error(u'Unknown metadata source \'' + player + '\'') continue classes = inspect.getmembers(modules[module], inspect.isclass) for entry in classes: if entry[0].lower() == player: sources[player] = entry[1]() else: continue for item in lib.items(query): for player in sources.values(): player.get_data(item) changed = ui.show_model_changes(item) if changed and not pretend: item.store()
class Amarok(MetaSource): item_types = { 'amarok_rating': types.INTEGER, 'amarok_score': types.FLOAT, 'amarok_uid': types.STRING, 'amarok_playcount': types.INTEGER, 'amarok_firstplayed': DateType(), 'amarok_lastplayed': DateType(), } queryXML = u'<query version="1.0"> \ <filters> \ <and><include field="filename" value="%s" /></and> \ </filters> \ </query>' def __init__(self, config, log): super(Amarok, self).__init__(config, log) if not dbus: raise ImportError('failed to import dbus') self.collection = \ dbus.SessionBus().get_object('org.kde.amarok', '/Collection') def sync_from_source(self, item): path = displayable_path(item.path) # amarok unfortunately doesn't allow searching for the full path, only # for the patch relative to the mount point. But the full path is part # of the result set. So query for the filename and then try to match # the correct item from the results we get back results = self.collection.Query(self.queryXML % escape(basename(path))) for result in results: if result['xesam:url'] != path: continue item.amarok_rating = result['xesam:userRating'] item.amarok_score = result['xesam:autoRating'] item.amarok_playcount = result['xesam:useCount'] item.amarok_uid = \ result['xesam:id'].replace('amarok-sqltrackuid://', '') if result['xesam:firstUsed'][0][0] != 0: # These dates are stored as timestamps in amarok's db, but # exposed over dbus as fixed integers in the current timezone. first_played = datetime(result['xesam:firstUsed'][0][0], result['xesam:firstUsed'][0][1], result['xesam:firstUsed'][0][2], result['xesam:firstUsed'][1][0], result['xesam:firstUsed'][1][1], result['xesam:firstUsed'][1][2]) if result['xesam:lastUsed'][0][0] != 0: last_played = datetime(result['xesam:lastUsed'][0][0], result['xesam:lastUsed'][0][1], result['xesam:lastUsed'][0][2], result['xesam:lastUsed'][1][0], result['xesam:lastUsed'][1][1], result['xesam:lastUsed'][1][2]) else: last_played = first_played item.amarok_firstplayed = mktime(first_played.timetuple()) item.amarok_lastplayed = mktime(last_played.timetuple())
class RadioStreamPlugin(BeetsPlugin): item_types = { 'rating': types.INTEGER, # 0..100 scale 'playcount': types.INTEGER, 'skipcount': types.INTEGER, 'lastplayed': DateType(), 'lastskipped': DateType(), } def __init__(self): super(RadioStreamPlugin, self).__init__() def preview_playlist_command(self, lib, opts, args): name_column_length = 60 count = 10 self._log.info(config.user_config_path()) if opts.count: count = int(opts.count) if opts.playlist: if opts.playlist not in _settings.playlists: self._log.error(u'Playlist not defined: {}'.format( opts.playlist)) return query = _settings.playlists[opts.playlist].query.split(u" ") else: query = decargs(args) query = u" ".join(query) query = ignore_deleted_in_query(query) items = playlist_generator.generate_playlist(lib, _settings.rules, count, opts.shuffle, query) for item in items: score_string = ", ".join([ '%s: %s' % (key.replace("rule_", ""), value) for (key, value) in sorted(item.scores.items()) ]) score_sum = round(sum(item.scores.values()), 2) item_name = unicode(item) if len(item_name) > name_column_length - 5: item_name = item_name[:name_column_length - 5] + "..." item_string = item_name.ljust(name_column_length) print_(u"Track: {0} Scores: {1}=[{2}]".format( item_string, score_sum, score_string)) def start_server_command(self, lib, opts, args): global _lastFmNetwork args = ui.decargs(args) if lastFmUsername and lastFmPassword: try: _lastFmNetwork = pylast.LastFMNetwork( api_key=LAST_FM_API_KEY, api_secret=LAST_FM_API_SECRET, username=str(lastFmUsername), password_hash=pylast.md5(str(lastFmPassword))) print("Last.fm scrobbling enabled") except Exception as e: print("ERROR: Failed to initialize LastFm service: " + str(e)) else: _lastFmNetwork = None print("NOTE: LastFm not configured") self.config.add({ 'host': u'0.0.0.0', 'port': 5000, 'cors': '', }) if args: self.config['host'] = args.pop(0) if args: self.config['port'] = int(args.pop(0)) app.config['lib'] = lib # Enable CORS if required. if self.config['cors']: self._log.info(u'Enabling CORS with origin: {0}', self.config['cors']) from flask.ext.cors import CORS app.config['CORS_ALLOW_HEADERS'] = "Content-Type" app.config['CORS_RESOURCES'] = { r"/*": { "origins": self.config['cors'].get(str) } } CORS(app) # Start the web application. app.run(host=self.config['host'].get(unicode), port=self.config['port'].get(int), debug=opts.debug, threaded=True) def commands(self): server_command = ui.Subcommand('radio', help=u'start the sever') server_command.parser.add_option(u'-d', u'--debug', action='store_true', default=False, help=u'debug mode') server_command.func = self.start_server_command preview_command = ui.Subcommand( 'radio-preview', help= u'preview generated playlists, e.g: radio-preview -c 10 genre:metal' ) preview_command.parser.add_option(u'-c', u'--count', dest='count', default=30, help="generated track count") preview_command.parser.add_option(u'-s', u'--shuffle', action='store_true', help="shuffle the result") preview_command.parser.add_option(u'-p', u'--playlist', dest='playlist', help="preview specified playlist") preview_command.func = self.preview_playlist_command return [server_command, preview_command]