diff --git a/beetsplug/beatport.py b/beetsplug/beatport.py index 1712900be..6990bd796 100644 --- a/beetsplug/beatport.py +++ b/beetsplug/beatport.py @@ -97,8 +97,8 @@ class BeatportRelease(BeatportObject): else: artist_str = "Various Artists" return u"".format(artist_str, - self.name, - self.catalog_number) + self.name, + self.catalog_number) def __init__(self, data): BeatportObject.__init__(self, data) @@ -129,7 +129,8 @@ class BeatportTrack(BeatportObject): def __unicode__(self): artist_str = ", ".join(x[1] for x in self.artists) - return u"".format(artist_str, self.name, + return u"".format(artist_str, + self.name, self.mix_name) def __init__(self, data): diff --git a/beetsplug/bench.py b/beetsplug/bench.py index c96bdcf34..386c365ed 100644 --- a/beetsplug/bench.py +++ b/beetsplug/bench.py @@ -81,6 +81,7 @@ def match_benchmark(lib, prof, query=None, album_id=None): interval = timeit.timeit(_run_match, number=1) print('match duration:', interval) + class BenchmarkPlugin(BeetsPlugin): """A plugin for performing some simple performance benchmarks. """ @@ -91,7 +92,7 @@ class BenchmarkPlugin(BeetsPlugin): action='store_true', default=False, help='performance profiling') aunique_bench_cmd.func = lambda lib, opts, args: \ - aunique_benchmark(lib, opts.profile) + aunique_benchmark(lib, opts.profile) match_bench_cmd = ui.Subcommand('bench_match', help='benchmark for track matching') @@ -101,6 +102,6 @@ class BenchmarkPlugin(BeetsPlugin): match_bench_cmd.parser.add_option('-i', '--id', default=None, help='album ID to match against') match_bench_cmd.func = lambda lib, opts, args: \ - match_benchmark(lib, opts.profile, ui.decargs(args), opts.id) + match_benchmark(lib, opts.profile, ui.decargs(args), opts.id) return [aunique_bench_cmd, match_bench_cmd] diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index d221c318c..488d56d44 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -76,7 +76,8 @@ global_log = logging.getLogger('beets') # Gstreamer import error. -class NoGstreamerError(Exception): pass +class NoGstreamerError(Exception): + pass # Error-handling, exceptions, parameter parsing. @@ -92,6 +93,7 @@ class BPDError(Exception): self.index = index template = Template(u'$resp [$code@$index] {$cmd_name} $message') + def response(self): """Returns a string to be used as the response code for the erring command. @@ -101,23 +103,28 @@ class BPDError(Exception): 'index': self.index, 'cmd_name': self.cmd_name, 'message': self.message - }) + }) + def make_bpd_error(s_code, s_message): """Create a BPDError subclass for a static code and message. """ + class NewBPDError(BPDError): code = s_code message = s_message cmd_name = '' index = 0 - def __init__(self): pass + + def __init__(self): + pass return NewBPDError ArgumentTypeError = make_bpd_error(ERROR_ARG, 'invalid type for argument') ArgumentIndexError = make_bpd_error(ERROR_ARG, 'argument out of range') ArgumentNotFoundError = make_bpd_error(ERROR_NO_EXIST, 'argument not found') + def cast_arg(t, val): """Attempts to call t on val, raising a ArgumentTypeError on ValueError. @@ -133,14 +140,15 @@ def cast_arg(t, val): except ValueError: raise ArgumentTypeError() + class BPDClose(Exception): """Raised by a command invocation to indicate that the connection should be closed. """ - # Generic server infrastructure, implementing the basic protocol. + class BaseServer(object): """A MPD-compatible music player server. @@ -211,10 +219,10 @@ class BaseServer(object): If there is only one song in the playlist it returns 0. """ if len(self.playlist) < 2: - return len(self.playlist)-1 - new_index = self.random_obj.randint(0, len(self.playlist)-1) + return len(self.playlist) - 1 + new_index = self.random_obj.randint(0, len(self.playlist) - 1) while new_index == self.current_index: - new_index = self.random_obj.randint(0, len(self.playlist)-1) + new_index = self.random_obj.randint(0, len(self.playlist) - 1) return new_index def _succ_idx(self): @@ -226,7 +234,7 @@ class BaseServer(object): return self.current_index if self.random: return self._random_idx() - return self.current_index+1 + return self.current_index + 1 def _prev_idx(self): """Returns the index for the previous song to play. @@ -237,7 +245,7 @@ class BaseServer(object): return self.current_index if self.random: return self._random_idx() - return self.current_index-1 + return self.current_index - 1 def cmd_ping(self, conn): """Succeeds.""" @@ -309,7 +317,7 @@ class BaseServer(object): state = u'play' yield u'state: ' + state - if self.current_index != -1: # i.e., paused or playing + if self.current_index != -1: # i.e., paused or playing current_id = self._item_id(self.playlist[self.current_index]) yield u'song: ' + unicode(self.current_index) yield u'songid: ' + unicode(current_id) @@ -360,9 +368,9 @@ class BaseServer(object): raise ArgumentIndexError() self.playlist_version += 1 - if self.current_index == index: # Deleted playing song. + if self.current_index == index: # Deleted playing song. self.cmd_stop(conn) - elif index < self.current_index: # Deleted before playing. + elif index < self.current_index: # Deleted before playing. # Shift playing index down. self.current_index -= 1 @@ -437,6 +445,7 @@ class BaseServer(object): except IndexError: raise ArgumentIndexError() yield self._item_info(track) + def cmd_playlistid(self, conn, track_id=-1): return self.cmd_playlistinfo(conn, self._id_to_index(track_id)) @@ -461,7 +470,7 @@ class BaseServer(object): def cmd_currentsong(self, conn): """Sends information about the currently-playing song. """ - if self.current_index != -1: # -1 means stopped. + if self.current_index != -1: # -1 means stopped. track = self.playlist[self.current_index] yield self._item_info(track) @@ -485,7 +494,7 @@ class BaseServer(object): def cmd_pause(self, conn, state=None): """Set the pause state playback.""" if state is None: - self.paused = not self.paused # Toggle. + self.paused = not self.paused # Toggle. else: self.paused = cast_arg('intbool', state) @@ -496,14 +505,14 @@ class BaseServer(object): if index < -1 or index > len(self.playlist): raise ArgumentIndexError() - if index == -1: # No index specified: start where we are. - if not self.playlist: # Empty playlist: stop immediately. + if index == -1: # No index specified: start where we are. + if not self.playlist: # Empty playlist: stop immediately. return self.cmd_stop(conn) - if self.current_index == -1: # No current song. - self.current_index = 0 # Start at the beginning. + if self.current_index == -1: # No current song. + self.current_index = 0 # Start at the beginning. # If we have a current song, just stay there. - else: # Start with the specified index. + else: # Start with the specified index. self.current_index = index self.paused = False @@ -527,6 +536,7 @@ class BaseServer(object): if index < 0 or index >= len(self.playlist): raise ArgumentIndexError() self.current_index = index + def cmd_seekid(self, conn, track_id, pos): index = self._id_to_index(track_id) return self.cmd_seek(conn, index, pos) @@ -537,6 +547,7 @@ class BaseServer(object): heap = hpy().heap() print(heap) + class Connection(object): """A connection between a client and the server. Handles input and output from and to the client. @@ -557,7 +568,7 @@ class Connection(object): if isinstance(lines, basestring): lines = [lines] out = NEWLINE.join(lines) + NEWLINE - log.debug(out[:-1]) # Don't log trailing newline. + log.debug(out[:-1]) # Don't log trailing newline. if isinstance(out, unicode): out = out.encode('utf8') return self.sock.sendall(out) @@ -580,9 +591,9 @@ class Connection(object): """ yield self.send(HELLO) - clist = None # Initially, no command list is being constructed. + clist = None # Initially, no command list is being constructed. while True: - line = yield self.sock.readline() + line = yield self.sock.readline() if not line: break line = line.strip() @@ -594,7 +605,7 @@ class Connection(object): # Command list already opened. if line == CLIST_END: yield bluelet.call(self.do_command(clist)) - clist = None # Clear the command list. + clist = None # Clear the command list. else: clist.append(Command(line)) @@ -619,6 +630,7 @@ class Connection(object): return cls(server, sock).run() return _handle + class Command(object): """A command issued by the client for processing by the server. """ @@ -691,6 +703,7 @@ class CommandList(list): server. May be verbose, in which case the response is delimited, or not. Should be a list of `Command` objects. """ + def __init__(self, sequence=None, verbose=False): """Create a new `CommandList` from the given sequence of `Command`s. If `verbose`, this is a verbose command list. @@ -708,7 +721,7 @@ class CommandList(list): yield bluelet.call(command.run(conn)) except BPDError as e: # If the command failed, stop executing. - e.index = i # Give the error the correct index. + e.index = i # Give the error the correct index. raise e # Otherwise, possibly send the output delimeter if we're in a @@ -717,7 +730,6 @@ class CommandList(list): yield conn.send(RESP_CLIST_VERBOSE) - # A subclass of the basic, protocol-handling server that actually plays # music. @@ -750,7 +762,6 @@ class Server(BaseServer): """ self.cmd_next(None) - # Metadata helper functions. def _item_info(self, item): @@ -783,7 +794,6 @@ class Server(BaseServer): def _item_id(self, item): return item.id - # Database updating. def cmd_update(self, conn, path=u'/'): @@ -796,7 +806,6 @@ class Server(BaseServer): print('... done.') self.updated_time = time.time() - # Path (directory tree) browsing. def _resolve_path(self, path): @@ -861,20 +870,22 @@ class Server(BaseServer): for name, itemid in sorted(node.files.iteritems()): newpath = self._path_join(basepath, name) # "yield from" - for v in self._listall(newpath, itemid, info): yield v + for v in self._listall(newpath, itemid, info): + yield v for name, subdir in sorted(node.dirs.iteritems()): newpath = self._path_join(basepath, name) yield u'directory: ' + newpath - for v in self._listall(newpath, subdir, info): yield v + for v in self._listall(newpath, subdir, info): + yield v def cmd_listall(self, conn, path=u"/"): """Send the paths all items in the directory, recursively.""" return self._listall(path, self._resolve_path(path), False) + def cmd_listallinfo(self, conn, path=u"/"): """Send info on all the items in the directory, recursively.""" return self._listall(path, self._resolve_path(path), True) - # Playlist manipulation. def _all_items(self, node): @@ -888,9 +899,11 @@ class Server(BaseServer): # Recurse into a directory. for name, itemid in sorted(node.files.iteritems()): # "yield from" - for v in self._all_items(itemid): yield v + for v in self._all_items(itemid): + yield v for name, subdir in sorted(node.dirs.iteritems()): - for v in self._all_items(subdir): yield v + for v in self._all_items(subdir): + yield v def _add(self, path, send_id=False): """Adds a track or directory to the playlist, specified by the @@ -912,7 +925,6 @@ class Server(BaseServer): """Same as `cmd_add` but sends an id back to the client.""" return self._add(path, True) - # Server info. def cmd_status(self, conn): @@ -921,7 +933,7 @@ class Server(BaseServer): if self.current_index > -1: item = self.playlist[self.current_index] - yield u'bitrate: ' + unicode(item.bitrate/1000) + yield u'bitrate: ' + unicode(item.bitrate / 1000) # Missing 'audio'. (pos, total) = self.player.time() @@ -929,7 +941,6 @@ class Server(BaseServer): # Also missing 'updating_db'. - def cmd_stats(self, conn): """Sends some statistics about the library.""" with self.lib.transaction() as tx: @@ -944,12 +955,11 @@ class Server(BaseServer): u'albums: ' + unicode(albums), u'songs: ' + unicode(songs), u'uptime: ' + unicode(int(time.time() - self.startup_time)), - u'playtime: ' + u'0', # Missing. + u'playtime: ' + u'0', # Missing. u'db_playtime: ' + unicode(int(totaltime)), u'db_update: ' + unicode(int(self.updated_time)), ) - # Searching. tagtype_map = { @@ -965,7 +975,7 @@ class Server(BaseServer): u'Composer': u'composer', # Performer? u'Disc': u'disc', - u'filename': u'path', # Suspect. + u'filename': u'path', # Suspect. } def cmd_tagtypes(self, conn): @@ -993,21 +1003,22 @@ class Server(BaseServer): pairs specified. The any_query_type is used for queries of type "any"; if None, then an error is thrown. """ - if kv: # At least one key-value pair. + if kv: # At least one key-value pair. queries = [] # Iterate pairwise over the arguments. it = iter(kv) for tag, value in zip(it, it): if tag.lower() == u'any': if any_query_type: - queries.append(any_query_type(value, ITEM_KEYS_WRITABLE, query_type)) + queries.append(any_query_type(value, + ITEM_KEYS_WRITABLE, query_type)) else: raise BPDError(ERROR_UNKNOWN, u'no such tagtype') else: _, key = self._tagtype_lookup(tag) queries.append(query_type(key, value)) return dbcore.query.AndQuery(queries) - else: # No key-value pairs. + else: # No key-value pairs. return dbcore.query.TrueQuery() def cmd_search(self, conn, *kv): @@ -1056,7 +1067,6 @@ class Server(BaseServer): yield u'songs: ' + unicode(songs) yield u'playtime: ' + unicode(int(playtime)) - # "Outputs." Just a dummy implementation because we don't control # any outputs. @@ -1079,7 +1089,6 @@ class Server(BaseServer): else: raise ArgumentIndexError() - # Playback control. The functions below hook into the # half-implementations provided by the base class. Together, they're # enough to implement all normal playback functionality. @@ -1089,7 +1098,7 @@ class Server(BaseServer): was_paused = self.paused super(Server, self).cmd_play(conn, index) - if self.current_index > -1: # Not stopped. + if self.current_index > -1: # Not stopped. if was_paused and not new_index: # Just unpause. self.player.play() @@ -1114,13 +1123,12 @@ class Server(BaseServer): super(Server, self).cmd_seek(conn, index, pos) self.player.seek(pos) - # Volume control. def cmd_setvol(self, conn, vol): vol = cast_arg(int, vol) super(Server, self).cmd_setvol(conn, vol) - self.player.volume = float(vol)/100 + self.player.volume = float(vol) / 100 # Beets plugin hooks. diff --git a/beetsplug/bpd/gstplayer.py b/beetsplug/bpd/gstplayer.py index 57ee564be..275f34b5c 100644 --- a/beetsplug/bpd/gstplayer.py +++ b/beetsplug/bpd/gstplayer.py @@ -29,6 +29,7 @@ import pygst pygst.require('0.10') import gst + class GstPlayer(object): """A music player abstracting GStreamer's Playbin element. @@ -138,6 +139,7 @@ class GstPlayer(object): """ # If we don't use the MainLoop, messages are never sent. gobject.threads_init() + def start(): loop = gobject.MainLoop() loop.run() @@ -150,8 +152,8 @@ class GstPlayer(object): """ fmt = gst.Format(gst.FORMAT_TIME) try: - pos = self.player.query_position(fmt, None)[0]/(10**9) - length = self.player.query_duration(fmt, None)[0]/(10**9) + pos = self.player.query_position(fmt, None)[0] / (10 ** 9) + length = self.player.query_duration(fmt, None)[0] / (10 ** 9) self.cached_time = (pos, length) return (pos, length) @@ -172,7 +174,7 @@ class GstPlayer(object): return fmt = gst.Format(gst.FORMAT_TIME) - ns = position * 10**9 # convert to nanoseconds + ns = position * 10 ** 9 # convert to nanoseconds self.player.seek_simple(fmt, gst.SEEK_FLAG_FLUSH, ns) # save new cached time @@ -194,11 +196,13 @@ def play_simple(paths): p.play_file(path) p.block() + def play_complicated(paths): """Play the files in the path one after the other by using the callback function to advance to the next song. """ my_paths = copy.copy(paths) + def next_song(): my_paths.pop(0) p.play_file(my_paths[0]) @@ -215,4 +219,3 @@ if __name__ == '__main__': for p in sys.argv[1:]] # play_simple(paths) play_complicated(paths) - diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py index 9221e107e..a51148a8b 100644 --- a/beetsplug/chroma.py +++ b/beetsplug/chroma.py @@ -93,6 +93,7 @@ def acoustid_match(path): # Plugin structure and autotagging logic. + def _all_releases(items): """Given an iterable of Items, determines (according to Acoustid) which releases the items have in common. Generates release IDs. @@ -111,6 +112,7 @@ def _all_releases(items): if float(count) / len(items) > COMMON_REL_THRESH: yield release_id + class AcoustidPlugin(plugins.BeetsPlugin): def track_distance(self, item, info): dist = hooks.Distance() @@ -148,6 +150,7 @@ class AcoustidPlugin(plugins.BeetsPlugin): def commands(self): submit_cmd = ui.Subcommand('submit', help='submit Acoustid fingerprints') + def submit_cmd_func(lib, opts, args): try: apikey = config['acoustid']['apikey'].get(unicode) @@ -157,7 +160,8 @@ class AcoustidPlugin(plugins.BeetsPlugin): submit_cmd.func = submit_cmd_func fingerprint_cmd = ui.Subcommand('fingerprint', - help='generate fingerprints for items without them') + help='generate fingerprints for items without them') + def fingerprint_cmd_func(lib, opts, args): for item in lib.items(ui.decargs(args)): fingerprint_item(item, @@ -169,6 +173,7 @@ class AcoustidPlugin(plugins.BeetsPlugin): # Hooks into import process. + @AcoustidPlugin.listen('import_task_start') def fingerprint_task(task, session): """Fingerprint each item in the task for later use during the @@ -178,6 +183,7 @@ def fingerprint_task(task, session): for item in items: acoustid_match(item.path) + @AcoustidPlugin.listen('import_task_apply') def apply_acoustid_metadata(task, session): """Apply Acoustid metadata (fingerprint and ID) to the task's items. @@ -191,10 +197,12 @@ def apply_acoustid_metadata(task, session): # UI commands. + def submit_items(userkey, items, chunksize=64): """Submit fingerprints for the items to the Acoustid server. """ data = [] # The running list of dictionaries to submit. + def submit_chunk(): """Submit the current accumulated fingerprint data.""" log.info('submitting {0} fingerprints'.format(len(data))) diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index e9e6477b1..d7639f83d 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -34,7 +34,9 @@ urllib3_logger.setLevel(logging.CRITICAL) discogs_client.user_agent = 'beets/%s +http://beets.radbox.org/' % \ beets.__version__ + class DiscogsPlugin(BeetsPlugin): + def __init__(self): super(DiscogsPlugin, self).__init__() self.config.add({ @@ -72,7 +74,7 @@ class DiscogsPlugin(BeetsPlugin): # of an input string as to avoid confusion with other metadata plugins. # An optional bracket can follow the integer, as this is how discogs # displays the release ID on its webpage. - match = re.search(r'(^|\[*r|discogs\.com/.+/release/)(\d+)($|\])', + match = re.search(r'(^|\[*r|discogs\.com/.+/release/)(\d+)($|\])', album_id) if not match: return None @@ -112,10 +114,10 @@ class DiscogsPlugin(BeetsPlugin): album = re.sub(r' +', ' ', result.title) album_id = result.data['id'] artist, artist_id = self.get_artist(result.data['artists']) - # Use `.data` to access the tracklist directly instead of the convenient - # `.tracklist` property, which will strip out useful artist information - # and leave us with skeleton `Artist` objects that will each make an API - # call just to get the same data back. + # Use `.data` to access the tracklist directly instead of the + # convenient `.tracklist` property, which will strip out useful artist + # information and leave us with skeleton `Artist` objects that will + # each make an API call just to get the same data back. tracks = self.get_tracks(result.data['tracklist']) albumtype = ', '.join( result.data['formats'][0].get('descriptions', [])) or None @@ -172,7 +174,7 @@ class DiscogsPlugin(BeetsPlugin): index += 1 tracks.append(self.get_track_info(track, index)) else: - index_tracks[index+1] = track['title'] + index_tracks[index + 1] = track['title'] # Fix up medium and medium_index for each track. Discogs position is # unreliable, but tracks are in order. @@ -217,8 +219,8 @@ class DiscogsPlugin(BeetsPlugin): artist, artist_id = self.get_artist(track.get('artists', [])) length = self.get_track_length(track['duration']) return TrackInfo(title, track_id, artist, artist_id, length, index, - medium, medium_index, artist_sort=None, disctitle=None, - artist_credit=None) + medium, medium_index, artist_sort=None, + disctitle=None, artist_credit=None) def get_track_index(self, position): """Returns the medium and medium index for a discogs track position. diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index 86244d7a0..195cd1d16 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -178,6 +178,7 @@ class DuplicatesPlugin(BeetsPlugin): ' attribute') def commands(self): + def _dup(lib, opts, args): self.config.set_args(opts) fmt = self.config['format'].get() diff --git a/beetsplug/echonest.py b/beetsplug/echonest.py index 8c92a7b8e..2a96619da 100644 --- a/beetsplug/echonest.py +++ b/beetsplug/echonest.py @@ -193,7 +193,6 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): values['id'] = song.id return values - # "Profile" (ID-based) lookup. def profile(self, item): @@ -221,7 +220,6 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): buckets=['id:musicbrainz', 'audio_summary']) return self._flatten_song(self._pick_song(songs, item)) - # "Search" (metadata-based) lookup. def search(self, item): @@ -233,7 +231,6 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): 'audio_summary']) return self._flatten_song(self._pick_song(songs, item)) - # "Identify" (fingerprinting) lookup. def fingerprint(self, item): @@ -276,7 +273,6 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): return self._flatten_song(max(songs, key=lambda s: s.score)) - # "Analyze" (upload the audio itself) method. def convert(self, item): @@ -370,7 +366,6 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): return self._flatten_song(pick) return from_track # Fall back to track metadata. - # Shared top-level logic. def fetch_song(self, item): @@ -427,7 +422,6 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): item.try_write() item.store() - # Automatic (on-import) metadata fetching. def imported(self, session, task): @@ -438,7 +432,6 @@ class EchonestMetadataPlugin(plugins.BeetsPlugin): if song: self.apply_metadata(item, song) - # Explicit command invocation. def requires_update(self, item): diff --git a/beetsplug/echonest_tempo.py b/beetsplug/echonest_tempo.py index 0e144fb4d..db67a05ee 100644 --- a/beetsplug/echonest_tempo.py +++ b/beetsplug/echonest_tempo.py @@ -136,6 +136,7 @@ class EchoNestTempoPlugin(BeetsPlugin): cmd.parser.add_option('-p', '--print', dest='printbpm', action='store_true', default=False, help='print tempo (bpm) to console') + def func(lib, opts, args): # The "write to files" option corresponds to the # import_write config value. diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py index 612fcc5b2..975431051 100644 --- a/beetsplug/embedart.py +++ b/beetsplug/embedart.py @@ -26,6 +26,7 @@ from beets import config log = logging.getLogger('beets') + def _embed(path, items, maxwidth=0): """Embed an image file, located at `path`, into each item. """ @@ -54,6 +55,7 @@ def _embed(path, items, maxwidth=0): f.images = [image] f.save(config['id3v23'].get(bool)) + class EmbedCoverArtPlugin(BeetsPlugin): """Allows albumart to be embedded into the actual files. """ @@ -75,6 +77,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): help='embed image files into file metadata') embed_cmd.parser.add_option('-f', '--file', metavar='PATH', help='the image file to embed') + def embed_func(lib, opts, args): if opts.file: imagepath = normpath(opts.file) @@ -88,6 +91,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): help='extract an image from file metadata') extract_cmd.parser.add_option('-o', dest='outpath', help='image output file') + def extract_func(lib, opts, args): outpath = normpath(opts.outpath or 'cover') extract(lib, outpath, decargs(args)) @@ -96,14 +100,17 @@ class EmbedCoverArtPlugin(BeetsPlugin): # Clear command. clear_cmd = ui.Subcommand('clearart', help='remove images from file metadata') + def clear_func(lib, opts, args): clear(lib, decargs(args)) clear_cmd.func = clear_func return [embed_cmd, extract_cmd, clear_cmd] -# "embedart" command with --file argument. + def embed(lib, imagepath, query): + """'embedart' command with --file argument. + """ albums = lib.albums(query) for i_album in albums: album = i_album @@ -118,8 +125,10 @@ def embed(lib, imagepath, query): _embed(imagepath, album.items(), config['embedart']['maxwidth'].get(int)) -# "embedart" command without explicit file. + def embed_current(lib, query): + """'embedart' command without explicit file. + """ albums = lib.albums(query) for album in albums: if not album.artpath: @@ -132,8 +141,10 @@ def embed_current(lib, query): _embed(album.artpath, album.items(), config['embedart']['maxwidth'].get(int)) -# "extractart" command. + def extract(lib, outpath, query): + """'extractart' command. + """ item = lib.items(query).get() if not item: log.error('No item matches query.') @@ -166,8 +177,10 @@ def extract(lib, outpath, query): with open(syspath(outpath), 'wb') as f: f.write(art) -# "clearart" command. + def clear(lib, query): + """'clearart' command. + """ log.info('Clearing album art from items:') for item in lib.items(query): log.info(u'%s - %s' % (item.artist, item.title)) @@ -181,9 +194,11 @@ def clear(lib, query): mf.art = None mf.save(config['id3v23'].get(bool)) -# Automatically embed art into imported albums. + @EmbedCoverArtPlugin.listen('album_imported') def album_imported(lib, album): + """Automatically embed art into imported albums. + """ if album.artpath and config['embedart']['auto']: _embed(album.artpath, album.items(), config['embedart']['maxwidth'].get(int)) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 9fe8e980b..224d5018f 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -72,11 +72,13 @@ def _fetch_image(url): CAA_URL = 'http://coverartarchive.org/release/{mbid}/front-500.jpg' CAA_GROUP_URL = 'http://coverartarchive.org/release-group/{mbid}/front-500.jpg' + def caa_art(release_id): """Return the Cover Art Archive URL given a MusicBrainz release ID. """ return CAA_URL.format(mbid=release_id) + def caa_group_art(release_group_id): """Return the Cover Art Archive release group URL given a MusicBrainz release group ID. @@ -89,6 +91,7 @@ def caa_group_art(release_group_id): AMAZON_URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' AMAZON_INDICES = (1, 2) + def art_for_asin(asin): """Generate URLs for an Amazon ID (ASIN) string.""" for index in AMAZON_INDICES: @@ -100,6 +103,7 @@ def art_for_asin(asin): AAO_URL = 'http://www.albumart.org/index_detail.php' AAO_PAT = r'href\s*=\s*"([^>"]*)"[^>]*title\s*=\s*"View larger image"' + def aao_art(asin): """Return art URL from AlbumArt.org given an ASIN.""" # Get the page from albumart.org. @@ -121,6 +125,7 @@ def aao_art(asin): # Art from the filesystem. + def art_in_path(path, cover_names, cautious): """Look for album art files in a specified directory.""" if not os.path.isdir(path): @@ -152,6 +157,7 @@ def art_in_path(path, cover_names, cautious): # Try each source in turn. + def _source_urls(album): """Generate possible source URLs for an album's art. The URLs are not guaranteed to work so they each need to be attempted in turn. @@ -173,6 +179,7 @@ def _source_urls(album): if url: yield url + def art_for_album(album, paths, maxwidth=None, local_only=False): """Given an Album object, returns a path to downloaded art for the album (or None if no art is found). If `maxwidth`, then images are @@ -209,6 +216,7 @@ def art_for_album(album, paths, maxwidth=None, local_only=False): # PLUGIN LOGIC ############################################################### + def batch_fetch_art(lib, albums, force, maxwidth=None): """Fetch album art for each of the albums. This implements the manual fetchart CLI command. @@ -233,6 +241,7 @@ def batch_fetch_art(lib, albums, force, maxwidth=None): log.info(u'{0} - {1}: {2}'.format(album.albumartist, album.album, message)) + class FetchArtPlugin(BeetsPlugin): def __init__(self): super(FetchArtPlugin, self).__init__() @@ -255,9 +264,10 @@ class FetchArtPlugin(BeetsPlugin): self.import_stages = [self.fetch_art] self.register_listener('import_task_files', self.assign_art) - # Asynchronous; after music is added to the library. def fetch_art(self, session, task): """Find art for the album being imported.""" + # Asynchronous; after music is added to the library. + if task.is_album: # Only fetch art for full albums. if task.choice_flag == importer.action.ASIS: # For as-is imports, don't search Web sources for art. @@ -275,9 +285,10 @@ class FetchArtPlugin(BeetsPlugin): if path: self.art_paths[task] = path - # Synchronous; after music files are put in place. def assign_art(self, session, task): """Place the discovered art in the filesystem.""" + # Synchronous; after music files are put in place. + if task in self.art_paths: path = self.art_paths.pop(task) @@ -289,12 +300,14 @@ class FetchArtPlugin(BeetsPlugin): if src_removed: task.prune(path) - # Manual album art fetching. def commands(self): + # Manual album art fetching. + cmd = ui.Subcommand('fetchart', help='download album art') cmd.parser.add_option('-f', '--force', dest='force', action='store_true', default=False, help='re-download art when already present') + def func(lib, opts, args): batch_fetch_art(lib, lib.albums(ui.decargs(args)), opts.force, self.maxwidth) diff --git a/beetsplug/fromfilename.py b/beetsplug/fromfilename.py index 7655ba552..f3884f556 100644 --- a/beetsplug/fromfilename.py +++ b/beetsplug/fromfilename.py @@ -139,6 +139,7 @@ def apply_matches(d): class FromFilenamePlugin(plugins.BeetsPlugin): pass + @FromFilenamePlugin.listen('import_task_start') def filename_task(task, session): """Examine each item in the task to see if we can extract a title diff --git a/beetsplug/ftintitle.py b/beetsplug/ftintitle.py index 731d8116c..243c1f004 100644 --- a/beetsplug/ftintitle.py +++ b/beetsplug/ftintitle.py @@ -115,6 +115,7 @@ class FtInTitlePlugin(BeetsPlugin): def commands(self): cmd = ui.Subcommand('ftintitle', help='move featured artists to the title field') + def func(lib, opts, args): write = config['import']['write'].get(bool) for item in lib.items(ui.decargs(args)): diff --git a/beetsplug/importfeeds.py b/beetsplug/importfeeds.py index 293cfc384..ca1b69b87 100644 --- a/beetsplug/importfeeds.py +++ b/beetsplug/importfeeds.py @@ -25,6 +25,7 @@ from beets import config M3U_DEFAULT_NAME = 'imported.m3u' + class ImportFeedsPlugin(BeetsPlugin): def __init__(self): super(ImportFeedsPlugin, self).__init__() @@ -50,6 +51,7 @@ class ImportFeedsPlugin(BeetsPlugin): else: self.config['relative_to'] = feeds_dir + def _get_feeds_dir(lib): """Given a Library object, return the path to the feeds directory to be used (either in the library directory or an explicitly configured @@ -63,6 +65,7 @@ def _get_feeds_dir(lib): os.makedirs(syspath(dirpath)) return dirpath + def _build_m3u_filename(basename): """Builds unique m3u filename by appending given basename to current date.""" @@ -75,6 +78,7 @@ def _build_m3u_filename(basename): )) return path + def _write_m3u(m3u_path, items_paths): """Append relative paths to items into m3u file. """ @@ -82,6 +86,7 @@ def _write_m3u(m3u_path, items_paths): for path in items_paths: f.write(path + '\n') + def _record_items(lib, basename, items): """Records relative paths to the given items for each feed format """ @@ -117,15 +122,18 @@ def _record_items(lib, basename, items): if not os.path.exists(syspath(dest)): os.symlink(syspath(path), syspath(dest)) + @ImportFeedsPlugin.listen('library_opened') def library_opened(lib): if config['importfeeds']['dir'].get() is None: config['importfeeds']['dir'] = _get_feeds_dir(lib) + @ImportFeedsPlugin.listen('album_imported') def album_imported(lib, album): _record_items(lib, album.album, album.items()) + @ImportFeedsPlugin.listen('item_imported') def item_imported(lib, item): _record_items(lib, item.title, [item]) diff --git a/beetsplug/info.py b/beetsplug/info.py index f5bb6a34a..11f605bdf 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -64,8 +64,10 @@ def info(paths): class InfoPlugin(BeetsPlugin): + def commands(self): cmd = ui.Subcommand('info', help='show file metadata') + def func(lib, opts, args): if not args: raise ui.UserError('no file specified') diff --git a/beetsplug/inline.py b/beetsplug/inline.py index dc42bd29f..d130e80e6 100644 --- a/beetsplug/inline.py +++ b/beetsplug/inline.py @@ -25,15 +25,17 @@ log = logging.getLogger('beets') FUNC_NAME = u'__INLINE_FUNC__' + class InlineError(Exception): """Raised when a runtime error occurs in an inline expression. """ def __init__(self, code, exc): super(InlineError, self).__init__( - (u"error in inline path field code:\n" \ + (u"error in inline path field code:\n" u"%s\n%s: %s") % (code, type(exc).__name__, unicode(exc)) ) + def _compile_func(body): """Given Python code for a function body, return a compiled callable that invokes that code. @@ -47,6 +49,7 @@ def _compile_func(body): eval(code, env) return env[FUNC_NAME] + def compile_inline(python_code, album): """Given a Python expression or function body, compile it as a path field function. The returned function takes a single argument, an @@ -95,6 +98,7 @@ def compile_inline(python_code, album): raise InlineError(python_code, exc) return _func_func + class InlinePlugin(BeetsPlugin): def __init__(self): super(InlinePlugin, self).__init__() @@ -118,4 +122,4 @@ class InlinePlugin(BeetsPlugin): log.debug(u'inline: adding album field %s' % key) func = compile_inline(view.get(unicode), True) if func is not None: - self.album_template_fields[key] = func + self.album_template_fields[key] = func \ No newline at end of file diff --git a/beetsplug/keyfinder.py b/beetsplug/keyfinder.py index 61bb9d9f5..193a1da86 100644 --- a/beetsplug/keyfinder.py +++ b/beetsplug/keyfinder.py @@ -69,4 +69,4 @@ class KeyFinderPlugin(BeetsPlugin): log.debug('added computed initial key {0} for {1}' .format(key, util.displayable_path(item.path))) item.try_write() - item.store() + item.store() \ No newline at end of file diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index b166af034..db79ee2c2 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -86,6 +86,7 @@ def _tags_for(obj): )) return tags + def _is_allowed(genre): """Determine whether the genre is present in the whitelist, returning a boolean. @@ -96,6 +97,7 @@ def _is_allowed(genre): return True return False + def _strings_to_genre(tags): """Given a list of strings, return a genre by joining them into a single string and (optionally) canonicalizing each. @@ -118,6 +120,7 @@ def _strings_to_genre(tags): tags[:config['lastgenre']['count'].get(int)] ) + def fetch_genre(lastfm_obj): """Return the genre for a pylast entity or None if no suitable genre can be found. Ex. 'Electronic, House, Dance' @@ -127,6 +130,7 @@ def fetch_genre(lastfm_obj): # Canonicalization tree processing. + def flatten_tree(elem, path, branches): """Flatten nested lists/dictionaries into lists of strings (branches). @@ -143,6 +147,7 @@ def flatten_tree(elem, path, branches): else: branches.append(path + [unicode(elem)]) + def find_parents(candidate, branches): """Find parents genre of a given genre, ordered from the closest to the further parent. @@ -160,6 +165,7 @@ def find_parents(candidate, branches): _genre_cache = {} + def _cached_lookup(entity, method, *args): """Get a genre based on the named entity using the callable `method` whose arguments are given in the sequence `args`. The genre lookup @@ -177,22 +183,26 @@ def _cached_lookup(entity, method, *args): _genre_cache[key] = genre return genre + def fetch_album_genre(obj): """Return the album genre for this Item or Album. """ return _cached_lookup(u'album', LASTFM.get_album, obj.albumartist, obj.album) + def fetch_album_artist_genre(obj): """Return the album artist genre for this Item or Album. """ return _cached_lookup(u'artist', LASTFM.get_artist, obj.albumartist) + def fetch_artist_genre(item): """Returns the track artist genre for this Item. """ return _cached_lookup(u'artist', LASTFM.get_artist, item.artist) + def fetch_track_genre(obj): """Returns the track genre for this Item. """ @@ -206,6 +216,8 @@ options = { 'branches': None, 'c14n': False, } + + class LastGenrePlugin(plugins.BeetsPlugin): def __init__(self): super(LastGenrePlugin, self).__init__() @@ -336,6 +348,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): lastgenre_cmd.parser.add_option('-s', '--source', dest='source', type='string', help='genre source: artist, album, or track') + def lastgenre_func(lib, opts, args): write = config['import']['write'].get(bool) self.config.set_args(opts) @@ -353,9 +366,8 @@ class LastGenrePlugin(plugins.BeetsPlugin): if 'track' in self.sources: item.genre, src = self._get_genre(item) item.store() - log.info(u'genre for track {0} - {1} ({2}): {3}'.format( - item.artist, item.title, src, item.genre - )) + log.info(u'genre for track {0} - {1} ({2}): {3}'. + format(item.artist, item.title, src, item.genre)) if write: item.try_write() diff --git a/beetsplug/mbcollection.py b/beetsplug/mbcollection.py index 4bce8b9b5..77a46b49b 100644 --- a/beetsplug/mbcollection.py +++ b/beetsplug/mbcollection.py @@ -1,16 +1,16 @@ -#Copyright (c) 2011, Jeffrey Aylesworth +# Copyright (c) 2011, Jeffrey Aylesworth # -#Permission to use, copy, modify, and/or distribute this software for any -#purpose with or without fee is hereby granted, provided that the above -#copyright notice and this permission notice appear in all copies. +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. # -#THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -#WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -#MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -#ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -#WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -#ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -#OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. from __future__ import print_function @@ -28,6 +28,7 @@ UUID_REGEX = r'^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$' log = logging.getLogger('beets.bpd') + def mb_call(func, *args, **kwargs): """Call a MusicBrainz API function and catch exceptions. """ @@ -40,6 +41,7 @@ def mb_call(func, *args, **kwargs): except musicbrainzngs.UsageError: raise ui.UserError('MusicBrainz credentials missing') + def submit_albums(collection_id, release_ids): """Add all of the release IDs to the indicated collection. Multiple requests are made if there are many release IDs to submit. @@ -51,6 +53,7 @@ def submit_albums(collection_id, release_ids): collection_id, chunk ) + def update_collection(lib, opts, args): # Get the collection to modify. collections = mb_call(musicbrainzngs.get_collections) @@ -77,6 +80,7 @@ update_mb_collection_cmd = Subcommand('mbupdate', help='Update MusicBrainz collection') update_mb_collection_cmd.func = update_collection + class MusicBrainzCollectionPlugin(BeetsPlugin): def __init__(self): super(MusicBrainzCollectionPlugin, self).__init__() @@ -86,4 +90,4 @@ class MusicBrainzCollectionPlugin(BeetsPlugin): ) def commands(self): - return [update_mb_collection_cmd] + return [update_mb_collection_cmd] \ No newline at end of file diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index d82fd3cb7..f484641cc 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -151,4 +151,4 @@ class MBSyncPlugin(BeetsPlugin): default=config['import']['write'], dest='write', help="don't write updated metadata to files") cmd.func = mbsync_func - return [cmd] + return [cmd] \ No newline at end of file diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 04355fb31..c522d4e1b 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -177,8 +177,8 @@ class MPDStats(object): @staticmethod def update_item(item, attribute, value=None, increment=None): """Update the beets item. Set attribute to value or increment the value - of attribute. If the increment argument is used the value is cast to the - corresponding type. + of attribute. If the increment argument is used the value is cast to + the corresponding type. """ if item is None: return @@ -297,7 +297,8 @@ class MPDStats(object): if handler: handler(status) else: - log.debug(u'mpdstats: unhandled status "{0}"'.format(status)) + log.debug(u'mpdstats: unhandled status "{0}"'. + format(status)) events = self.mpd.events() @@ -347,4 +348,4 @@ class MPDStatsPlugin(plugins.BeetsPlugin): pass cmd.func = func - return [cmd] + return [cmd] \ No newline at end of file diff --git a/beetsplug/mpdupdate.py b/beetsplug/mpdupdate.py index e11d7a7b1..b5137237a 100644 --- a/beetsplug/mpdupdate.py +++ b/beetsplug/mpdupdate.py @@ -31,6 +31,7 @@ from beets import config # once before beets exits. database_changed = False + # No need to introduce a dependency on an MPD library for such a # simple use case. Here's a simple socket abstraction to make things # easier. @@ -64,6 +65,7 @@ class BufferedSocket(object): def close(self): self.sock.close() + def update_mpd(host='localhost', port=6600, password=None): """Sends the "update" command to the MPD server indicated, possibly authenticating with a password first. @@ -94,6 +96,7 @@ def update_mpd(host='localhost', port=6600, password=None): s.close() print('... updated.') + class MPDUpdatePlugin(BeetsPlugin): def __init__(self): super(MPDUpdatePlugin, self).__init__() diff --git a/beetsplug/random.py b/beetsplug/random.py index 259cbe5f6..05b898390 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -23,6 +23,7 @@ from operator import attrgetter from itertools import groupby import collections + def random_item(lib, opts, args): query = decargs(args) if opts.path: @@ -82,6 +83,7 @@ random_cmd.parser.add_option('-e', '--equal-chance', action='store_true', help='each artist has the same chance') random_cmd.func = random_item + class Random(BeetsPlugin): def commands(self): return [random_cmd] diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index 6a0cf593d..3ef86c105 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -82,6 +82,7 @@ class Backend(object): # mpgain/aacgain CLI tool backend. + class CommandBackend(Backend): def __init__(self, config): config.add({ diff --git a/beetsplug/rewrite.py b/beetsplug/rewrite.py index 95a56608a..5a9dfba92 100644 --- a/beetsplug/rewrite.py +++ b/beetsplug/rewrite.py @@ -26,6 +26,7 @@ from beets import config log = logging.getLogger('beets') + def rewriter(field, rules): """Create a template field function that rewrites the given field with the given rewriting rules. ``rules`` must be a list of @@ -41,6 +42,7 @@ def rewriter(field, rules): return value return fieldfunc + class RewritePlugin(BeetsPlugin): def __init__(self): super(RewritePlugin, self).__init__() diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index 195f94632..065e99f63 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -79,7 +79,7 @@ def update_playlists(lib): item_path = item.path if relative_to: item_path = os.path.relpath(item.path, relative_to) - if not item_path in m3us[m3u_name]: + if item_path not in m3us[m3u_name]: m3us[m3u_name].append(item_path) # Now iterate through the m3us that we need to generate for m3u in m3us: @@ -104,7 +104,7 @@ class SmartPlaylistPlugin(BeetsPlugin): def update(lib, opts, args): update_playlists(lib) spl_update = ui.Subcommand('splupdate', - help='update the smart playlists') + help='update the smart playlists') spl_update.func = update return [spl_update] diff --git a/beetsplug/the.py b/beetsplug/the.py index fc397ac24..c4fb15045 100644 --- a/beetsplug/the.py +++ b/beetsplug/the.py @@ -25,6 +25,7 @@ PATTERN_THE = u'^[the]{3}\s' PATTERN_A = u'^[a][n]?\s' FORMAT = u'{0}, {1}' + class ThePlugin(BeetsPlugin): _instance = None @@ -67,14 +68,12 @@ class ThePlugin(BeetsPlugin): if not self.patterns: self._log.warn(u'[the] no patterns defined!') - def unthe(self, text, pattern): """Moves pattern in the path format string or strips it text -- text to handle pattern -- regexp pattern (case ignore is already on) strip -- if True, pattern will be removed - """ if text: r = re.compile(pattern, flags=re.IGNORECASE) @@ -103,4 +102,4 @@ class ThePlugin(BeetsPlugin): self._log.debug(u'[the] \"{0}\" -> \"{1}\"'.format(text, r)) return r else: - return u'' + return u'' \ No newline at end of file diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py index ce45eddce..2aa8c3359 100644 --- a/beetsplug/web/__init__.py +++ b/beetsplug/web/__init__.py @@ -51,6 +51,7 @@ def _rep(obj, expand=False): out['items'] = [_rep(item) for item in obj.items()] return out + def json_generator(items, root): """Generator that dumps list of beets Items or Albums as JSON @@ -68,6 +69,7 @@ def json_generator(items, root): yield json.dumps(_rep(item)) yield ']}' + def resource(name): """Decorates a function to handle RESTful HTTP requests for a resource. """ @@ -88,6 +90,7 @@ def resource(name): return responder return make_responder + def resource_query(name): """Decorates a function to handle RESTful HTTP queries for resources. """ @@ -100,6 +103,7 @@ def resource_query(name): return responder return make_responder + def resource_list(name): """Decorates a function to handle RESTful HTTP request for a list of resources. @@ -148,6 +152,7 @@ app = flask.Flask(__name__) app.url_map.converters['idlist'] = IdListConverter app.url_map.converters['query'] = QueryConverter + @app.before_request def before_request(): g.lib = app.config['lib'] @@ -167,6 +172,7 @@ def get_item(id): def all_items(): return g.lib.items() + @app.route('/item//file') def item_file(item_id): item = g.lib.get_item(item_id) @@ -175,6 +181,7 @@ def item_file(item_id): response.headers['Content-Length'] = os.path.getsize(item.path) return response + @app.route('/item/query/') @resource_query('items') def item_query(queries): @@ -188,17 +195,20 @@ def item_query(queries): def get_album(id): return g.lib.get_album(id) + @app.route('/album/') @app.route('/album/query/') @resource_list('albums') def all_albums(): return g.lib.albums() + @app.route('/album/query/') @resource_query('albums') def album_query(queries): return g.lib.albums(queries) + @app.route('/album//art') def album_art(album_id): album = g.lib.get_album(album_id) @@ -249,6 +259,7 @@ class WebPlugin(BeetsPlugin): cmd = ui.Subcommand('web', help='start a Web interface') cmd.parser.add_option('-d', '--debug', action='store_true', default=False, help='debug mode') + def func(lib, opts, args): args = ui.decargs(args) if args: diff --git a/beetsplug/zero.py b/beetsplug/zero.py index e0e277def..13d314261 100644 --- a/beetsplug/zero.py +++ b/beetsplug/zero.py @@ -12,7 +12,7 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -""" Clears tag fields in media files.""" +""" Clears tag fields in media files.""" import re import logging @@ -62,11 +62,11 @@ class ZeroPlugin(BeetsPlugin): if task.choice_flag == action.ASIS and not self.warned: log.warn(u'[zero] cannot zero in \"as-is\" mode') self.warned = True - # TODO request write in as-is mode + # TODO request write in as-is mode @classmethod def match_patterns(cls, field, patterns): - """Check if field (as string) is matching any of the patterns in + """Check if field (as string) is matching any of the patterns in the list. """ for p in patterns: @@ -89,4 +89,4 @@ class ZeroPlugin(BeetsPlugin): if self.match_patterns(value, patterns): log.debug(u'[zero] {0}: {1} -> None'.format(field, value)) - setattr(item, field, None) + setattr(item, field, None) \ No newline at end of file