diff --git a/beets/__init__.py b/beets/__init__.py index 7475146e2..974c62b80 100644 --- a/beets/__init__.py +++ b/beets/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index 039ceb730..e316d7efd 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -93,7 +93,7 @@ def albums_in_dir(path, ignore=()): collapse_root = root collapse_items = [] continue - + # If it's nonempty, yield it. if items: yield root, items diff --git a/beets/autotag/art.py b/beets/autotag/art.py index 682f22a98..7bf9ab360 100644 --- a/beets/autotag/art.py +++ b/beets/autotag/art.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 2d33516ea..91d698fc4 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beets/autotag/match.py b/beets/autotag/match.py index b88b76a42..b7ab0bacb 100644 --- a/beets/autotag/match.py +++ b/beets/autotag/match.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -112,7 +112,7 @@ def string_dist(str1, str2): """ str1 = str1.lower() str2 = str2.lower() - + # Don't penalize strings that move certain words to the end. For # example, "the something" should be considered equal to # "something, the". @@ -126,7 +126,7 @@ def string_dist(str1, str2): for pat, repl in SD_REPLACE: str1 = re.sub(pat, repl, str1) str2 = re.sub(pat, repl, str2) - + # Change the weight for certain string portions matched by a set # of regular expressions. We gradually change the strings and build # up penalties associated with parts of the string that were @@ -137,7 +137,7 @@ def string_dist(str1, str2): # Get strings that drop the pattern. case_str1 = re.sub(pat, '', str1) case_str2 = re.sub(pat, '', str2) - + if case_str1 != str1 or case_str2 != str2: # If the pattern was present (i.e., it is deleted in the # the current case), recalculate the distances for the @@ -146,7 +146,7 @@ def string_dist(str1, str2): case_delta = max(0.0, base_dist - case_dist) if case_delta == 0.0: continue - + # Shift our baseline strings down (to avoid rematching the # same part of the string) and add a scaled distance # amount to the penalties. @@ -155,7 +155,7 @@ def string_dist(str1, str2): base_dist = case_dist penalty += weight * case_delta dist = base_dist + penalty - + return dist def current_metadata(items): @@ -191,7 +191,7 @@ def order_items(items, trackinfo): for i, canon_item in enumerate(trackinfo): row.append(track_distance(cur_item, canon_item, i+1)) costs.append(row) - + # Find a minimum-cost bipartite matching. matching = Munkres().compute(costs) @@ -221,7 +221,7 @@ def track_distance(item, track_info, track_index=None, incl_artist=False): diff = min(diff, TRACK_LENGTH_MAX) dist += (diff / TRACK_LENGTH_MAX) * TRACK_LENGTH_WEIGHT dist_max += TRACK_LENGTH_WEIGHT - + # Track title. dist += string_dist(item.title, track_info.title) * TRACK_TITLE_WEIGHT dist_max += TRACK_TITLE_WEIGHT @@ -241,7 +241,7 @@ def track_distance(item, track_info, track_index=None, incl_artist=False): if item.track not in (track_index, track_info.medium_index): dist += TRACK_INDEX_WEIGHT dist_max += TRACK_INDEX_WEIGHT - + # MusicBrainz track ID. if item.mb_trackid: if item.mb_trackid != track_info.track_id: @@ -262,19 +262,19 @@ def distance(items, album_info): cur_artist, cur_album, _ = current_metadata(items) cur_artist = cur_artist or '' cur_album = cur_album or '' - + # These accumulate the possible distance components. The final # distance will be dist/dist_max. dist = 0.0 dist_max = 0.0 - + # Artist/album metadata. if not album_info.va: dist += string_dist(cur_artist, album_info.artist) * ARTIST_WEIGHT dist_max += ARTIST_WEIGHT dist += string_dist(cur_album, album_info.album) * ALBUM_WEIGHT dist_max += ALBUM_WEIGHT - + # Track distances. for i, (item, track_info) in enumerate(zip(items, album_info.tracks)): if item: @@ -305,7 +305,7 @@ def match_by_id(items): if not albumids: log.debug('No album IDs found.') return None - + # If all album IDs are equal, look up the album. if bool(reduce(lambda x,y: x if x==y else (), albumids)): albumid = albumids[0] @@ -314,7 +314,7 @@ def match_by_id(items): else: log.debug('No album ID consensus.') return None - + #fixme In the future, at the expense of performance, we could use # other IDs (i.e., track and artist) in case the album tag isn't # present, but that event seems very unlikely. @@ -398,11 +398,11 @@ def tag_album(items, timid=False, search_artist=None, search_album=None, # Get current metadata. cur_artist, cur_album, artist_consensus = current_metadata(items) log.debug('Tagging %s - %s' % (cur_artist, cur_album)) - + # The output result (distance, AlbumInfo) tuples (keyed by MB album # ID). candidates = {} - + # Try to find album indicated by MusicBrainz IDs. if search_id: log.debug('Searching for album ID: ' + search_id) @@ -427,13 +427,13 @@ def tag_album(items, timid=False, search_artist=None, search_album=None, return cur_artist, cur_album, candidates.values(), rec else: return cur_artist, cur_album, [], RECOMMEND_NONE - + # Search terms. if not (search_artist and search_album): # No explicit search terms -- use current metadata. search_artist, search_album = cur_artist, cur_album log.debug(u'Search terms: %s - %s' % (search_artist, search_album)) - + # Is this album likely to be a "various artist" release? va_likely = ((not artist_consensus) or (search_artist.lower() in VA_ARTISTS) or @@ -446,7 +446,7 @@ def tag_album(items, timid=False, search_artist=None, search_album=None, log.debug(u'Evaluating %i candidates.' % len(search_cands)) for info in search_cands: validate_candidate(items, candidates, info) - + # Sort and get the recommendation. candidates = sorted(candidates.itervalues()) rec = recommendation(candidates) @@ -456,7 +456,7 @@ def tag_item(item, timid=False, search_artist=None, search_title=None, search_id=None): """Attempts to find metadata for a single track. Returns a `(candidates, recommendation)` pair where `candidates` is a list - of `(distance, track_info)` pairs. `search_artist` and + of `(distance, track_info)` pairs. `search_artist` and `search_title` may be used to override the current metadata for the purposes of the MusicBrainz title; likewise `search_id`. """ @@ -484,7 +484,7 @@ def tag_item(item, timid=False, search_artist=None, search_title=None, return candidates.values(), rec else: return [], RECOMMEND_NONE - + # Search terms. if not (search_artist and search_title): search_artist, search_title = item.artist, item.title diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 92400bfd1..8217926ee 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beets/importer.py b/beets/importer.py index 4af83dcd8..c83410da9 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -502,14 +502,14 @@ def read_tasks(config): if config.incremental: incremental_skipped = 0 history_dirs = history_get() - + for toppath in config.paths: # Check whether the path is to a file. if config.singletons and not os.path.isdir(syspath(toppath)): item = library.Item.from_path(toppath) yield ImportTask.item_task(item) continue - + # Produce paths under this directory. if progress: resume_dir = resume_dirs.get(toppath) @@ -599,7 +599,7 @@ def user_query(config): task = yield task if task.sentinel: continue - + # Ask the user for a choice. choice = config.choose_match_func(task, config) task.set_choice(choice) @@ -617,7 +617,7 @@ def user_query(config): while True: item_task = yield item_tasks.append(item_task) - ipl = pipeline.Pipeline((emitter(), item_lookup(config), + ipl = pipeline.Pipeline((emitter(), item_lookup(config), item_query(config), collector())) ipl.run_sequential() task = pipeline.multiple(item_tasks) @@ -650,14 +650,14 @@ def show_progress(config): # Behave as if ASIS were selected. task.set_null_match() task.set_choice(action.ASIS) - + def apply_choices(config): """A coroutine for applying changes to albums during the autotag process. """ lib = _reopen_lib(config.lib) task = None - while True: + while True: task = yield task if task.should_skip(): continue @@ -894,7 +894,7 @@ def run_import(**kwargs): ImportConfig. """ config = ImportConfig(**kwargs) - + # Set up the pipeline. if config.query is None: stages = [read_tasks(config)] diff --git a/beets/plugins.py b/beets/plugins.py index a9b2bb839..9c35faa33 100755 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index e19e005f1..f19baf14d 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -157,7 +157,7 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, # Mark the option's shortcut letter for display. if (default is None and not numrange and first) \ - or (isinstance(default, basestring) and + or (isinstance(default, basestring) and found_letter.lower() == default.lower()): # The first option is the default; mark it. show_letter = '[%s]' % found_letter.upper() @@ -187,7 +187,7 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, default = numrange[0] else: default = display_letters[0].lower() - + # Make a prompt if one is not provided. if not prompt: prompt_parts = [] @@ -249,11 +249,11 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, resp = raw_input(prompt + ' ') while True: resp = resp.strip().lower() - + # Try default option. if default is not None and not resp: resp = default - + # Try an integer input if available. if numrange: try: @@ -266,13 +266,13 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, return resp else: resp = None - + # Try a normal letter input. if resp: resp = resp[0] if resp in letters: return resp - + # Prompt for new input. resp = raw_input(fallback_prompt + ' ') @@ -293,7 +293,7 @@ def config_val(config, section, name, default, vtype=None): """ if not config.has_section(section): config.add_section(section) - + try: if vtype is bool: return config.getboolean(section, name) @@ -387,7 +387,7 @@ def colordiff(a, b, highlight='red'): a_out = [] b_out = [] - + matcher = SequenceMatcher(lambda x: False, a, b) for op, a_start, a_end, b_start, b_end in matcher.get_opcodes(): if op == 'equal': @@ -406,7 +406,7 @@ def colordiff(a, b, highlight='red'): b_out.append(colorize(highlight, b[b_start:b_end])) else: assert(False) - + return u''.join(a_out), u''.join(b_out) def default_paths(pathmod=None): @@ -524,7 +524,7 @@ class SubcommandsOptionParser(optparse.OptionParser): _HelpSubcommand = Subcommand('help', optparse.OptionParser(), help='give detailed help on a specific sub-command', aliases=('?',)) - + def __init__(self, *args, **kwargs): """Create a new subcommand-aware option parser. All of the options to OptionParser.__init__ are supported in addition @@ -533,41 +533,41 @@ class SubcommandsOptionParser(optparse.OptionParser): # The subcommand array, with the help command included. self.subcommands = list(kwargs.pop('subcommands', [])) self.subcommands.append(self._HelpSubcommand) - + # A more helpful default usage. if 'usage' not in kwargs: kwargs['usage'] = """ %prog COMMAND [ARGS...] %prog help COMMAND""" - + # Super constructor. optparse.OptionParser.__init__(self, *args, **kwargs) - + # Adjust the help-visible name of each subcommand. for subcommand in self.subcommands: subcommand.parser.prog = '%s %s' % \ (self.get_prog_name(), subcommand.name) - - # Our root parser needs to stop on the first unrecognized argument. + + # Our root parser needs to stop on the first unrecognized argument. self.disable_interspersed_args() - + def add_subcommand(self, cmd): """Adds a Subcommand object to the parser's list of commands. """ self.subcommands.append(cmd) - + # Add the list of subcommands to the help message. def format_help(self, formatter=None): # Get the original help message, to which we will append. out = optparse.OptionParser.format_help(self, formatter) if formatter is None: formatter = self.formatter - + # Subcommands header. result = ["\n"] result.append(formatter.format_heading('Commands')) formatter.indent() - + # Generate the display names (including aliases). # Also determine the help position. disp_names = [] @@ -577,12 +577,12 @@ class SubcommandsOptionParser(optparse.OptionParser): if subcommand.aliases: name += ' (%s)' % ', '.join(subcommand.aliases) disp_names.append(name) - + # Set the help position based on the max width. proposed_help_position = len(name) + formatter.current_indent + 2 if proposed_help_position <= formatter.max_help_position: - help_position = max(help_position, proposed_help_position) - + help_position = max(help_position, proposed_help_position) + # Add each subcommand to the output. for subcommand, name in zip(self.subcommands, disp_names): # Lifted directly from optparse.py. @@ -601,11 +601,11 @@ class SubcommandsOptionParser(optparse.OptionParser): result.extend(["%*s%s\n" % (help_position, "", line) for line in help_lines[1:]]) formatter.dedent() - + # Concatenate the original help message with the subcommand # list. return out + "".join(result) - + def _subcommand_for_name(self, name): """Return the subcommand in self.subcommands matching the given name. The name may either be the name of a subcommand or @@ -616,16 +616,16 @@ class SubcommandsOptionParser(optparse.OptionParser): name in subcommand.aliases: return subcommand return None - + def parse_args(self, a=None, v=None): """Like OptionParser.parse_args, but returns these four items: - options: the options passed to the root parser - subcommand: the Subcommand object that was invoked - suboptions: the options passed to the subcommand parser - subargs: the positional arguments passed to the subcommand - """ + """ options, args = optparse.OptionParser.parse_args(self, a, v) - + if not args: # No command given. self.print_help() @@ -635,7 +635,7 @@ class SubcommandsOptionParser(optparse.OptionParser): subcommand = self._subcommand_for_name(cmdname) if not subcommand: self.error('unknown command ' + cmdname) - + suboptions, subargs = subcommand.parser.parse_args(args) if subcommand is self._HelpSubcommand: @@ -649,7 +649,7 @@ class SubcommandsOptionParser(optparse.OptionParser): # general self.print_help() self.exit() - + return options, subcommand, suboptions, subargs @@ -701,10 +701,10 @@ def main(args=None, configfh=None): help="destination music directory") parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='print debugging information') - + # Parse the command-line! options, subcommand, suboptions, subargs = parser.parse_args(args) - + # Open library file. libpath = options.libpath or \ config_val(config, 'beets', 'library', default_libpath) @@ -729,7 +729,7 @@ def main(args=None, configfh=None): replacements) except sqlite3.OperationalError: raise UserError("database file %s could not be opened" % db_path) - + # Configure the logger. log = logging.getLogger('beets') if options.verbose: @@ -739,7 +739,7 @@ def main(args=None, configfh=None): log.debug(u'config file: %s' % util.displayable_path(configpath)) log.debug(u'library database: %s' % util.displayable_path(lib.path)) log.debug(u'library directory: %s' % util.displayable_path(lib.directory)) - + # Invoke the subcommand. try: subcommand.func(lib, config, suboptions, subargs) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index a30a00311..cbebdc9e3 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -64,7 +64,7 @@ def _do_query(lib, query, album, also_items=True): raise ui.UserError('No matching albums found.') elif not album and not items: raise ui.UserError('No matching items found.') - + return items, albums FLOAT_EPSILON = 0.01 @@ -212,7 +212,7 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True): if color: cur_length = ui.colorize('red', cur_length) new_length = ui.colorize('red', new_length) - + # Possibly colorize changes. if color: cur_title, new_title = ui.colordiff(cur_title, new_title) @@ -223,7 +223,7 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True): # Show filename (non-colorized) when title is not set. if not item.title.strip(): cur_title = displayable_path(os.path.basename(item.path)) - + if cur_title != new_title: lhs, rhs = cur_title, new_title if cur_track != new_track: @@ -345,7 +345,7 @@ def choose_candidate(candidates, singleton, rec, color, timid, else: dist, items, info = candidates[0] bypass_candidates = True - + while True: # Display and choose from candidates. if not bypass_candidates: @@ -387,7 +387,7 @@ def choose_candidate(candidates, singleton, rec, color, timid, line += u' %s' % warning print_(line) - + # Ask the user for a choice. if singleton: opts = ('Skip', 'Use as-is', 'Enter search', 'enter Id', @@ -416,20 +416,20 @@ def choose_candidate(candidates, singleton, rec, color, timid, else: dist, items, info = candidates[sel-1] bypass_candidates = False - + # Show what we're about to do. if singleton: show_item_change(item, info, dist, color) else: show_change(cur_artist, cur_album, items, info, dist, color) - + # Exact match => tag automatically if we're not in timid mode. if rec == autotag.RECOMMEND_STRONG and not timid: if singleton: return info else: return info, items - + # Ask for confirmation. if singleton: opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', @@ -507,10 +507,10 @@ def choose_match(task, config): candidates, rec = task.candidates, task.rec while True: # Ask for a choice from the user. - choice = choose_candidate(candidates, False, rec, config.color, + choice = choose_candidate(candidates, False, rec, config.color, config.timid, task.cur_artist, task.cur_album, itemcount=len(task.items)) - + # Choose which tags to use. if choice in (importer.action.SKIP, importer.action.ASIS, importer.action.TRACKS): @@ -687,7 +687,7 @@ def import_files(lib, paths, copy, move, write, autot, logpath, art, threaded, ignore = ignore, resolve_duplicate_func = resolve_duplicate, ) - + finally: # If we were logging, close the file. if logfile: diff --git a/beets/util/__init__.py b/beets/util/__init__.py index aaf8fc6d3..f5bf6c031 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -39,11 +39,11 @@ def ancestry(path, pathmod=None): last_path = None while path: path = pathmod.dirname(path) - + if path == last_path: break last_path = path - + if path: # don't yield '' out.insert(0, path) return out @@ -151,11 +151,11 @@ def components(path, pathmod=None): comps.append(comp) else: # root comps.append(anc) - + last = pathmod.basename(path) if last: comps.append(last) - + return comps def bytestring_path(path): @@ -361,7 +361,7 @@ def levenshtein(s1, s2): return levenshtein(s2, s1) if not s1: return len(s2) - + previous_row = xrange(len(s2) + 1) for i, c1 in enumerate(s1): current_row = [i + 1] @@ -371,7 +371,7 @@ def levenshtein(s1, s2): substitutions = previous_row[j] + (c1 != c2) current_row.append(min(insertions, deletions, substitutions)) previous_row = current_row - + return previous_row[-1] def plurality(objs): diff --git a/beets/util/enumeration.py b/beets/util/enumeration.py index 794a0624f..7c4c1939c 100644 --- a/beets/util/enumeration.py +++ b/beets/util/enumeration.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -35,7 +35,7 @@ how you would expect them to. 'west' >>> Direction.north < Direction.west True - + Enumerations are classes; their instances represent the possible values of the enumeration. Because Python classes must have names, you may provide a `name` parameter to `enum`; if you don't, a meaningless one @@ -45,31 +45,31 @@ import random class Enumeration(type): """A metaclass whose classes are enumerations. - + The `values` attribute of the class is used to populate the enumeration. Values may either be a list of enumerated names or a string containing a space-separated list of names. When the class is created, it is instantiated for each name value in `values`. Each such instance is the name of the enumerated item as the sole argument. - + The `Enumerated` class is a good choice for a superclass. """ - + def __init__(cls, name, bases, dic): super(Enumeration, cls).__init__(name, bases, dic) - + if 'values' not in dic: # Do nothing if no values are provided (i.e., with # Enumerated itself). return - + # May be called with a single string, in which case we split on # whitespace for convenience. values = dic['values'] if isinstance(values, basestring): values = values.split() - + # Create the Enumerated instances for each value. We have to use # super's __setattr__ here because we disallow setattr below. super(Enumeration, cls).__setattr__('_items_dict', {}) @@ -78,44 +78,44 @@ class Enumeration(type): item = cls(value, len(cls._items_list)) cls._items_dict[value] = item cls._items_list.append(item) - + def __getattr__(cls, key): try: return cls._items_dict[key] except KeyError: raise AttributeError("enumeration '" + cls.__name__ + "' has no item '" + key + "'") - + def __setattr__(cls, key, val): raise TypeError("enumerations do not support attribute assignment") - + def __getitem__(cls, key): if isinstance(key, int): return cls._items_list[key] else: return getattr(cls, key) - + def __len__(cls): return len(cls._items_list) - + def __iter__(cls): return iter(cls._items_list) - + def __nonzero__(cls): # Ensures that __len__ doesn't get called before __init__ by # pydoc. return True - + class Enumerated(object): """An item in an enumeration. - + Contains instance methods inherited by enumerated objects. The metaclass is preset to `Enumeration` for your convenience. - - Instance attributes: + + Instance attributes: name -- The name of the item. index -- The index of the item in its enumeration. - + >>> from enumeration import Enumerated >>> class Garment(Enumerated): ... values = 'hat glove belt poncho lederhosen suspenders' @@ -125,9 +125,9 @@ class Enumerated(object): >>> Garment.poncho.wear() now wearing a poncho """ - + __metaclass__ = Enumeration - + def __init__(self, name, index): self.name = name self.index = index @@ -149,18 +149,18 @@ class Enumerated(object): def enum(*values, **kwargs): """Shorthand for creating a new Enumeration class. - + Call with enumeration values as a list, a space-delimited string, or just an argument list. To give the class a name, pass it as the `name` keyword argument. Otherwise, a name will be chosen for you. - + The following are all equivalent: - + enum('pinkie ring middle index thumb') enum('pinkie', 'ring', 'middle', 'index', 'thumb') enum(['pinkie', 'ring', 'middle', 'index', 'thumb']) """ - + if ('name' not in kwargs) or kwargs['name'] is None: # Create a probably-unique name. It doesn't really have to be # unique, but getting distinct names each time helps with @@ -168,11 +168,11 @@ def enum(*values, **kwargs): name = 'Enumeration' + hex(random.randint(0,0xfffffff))[2:].upper() else: name = kwargs['name'] - + if len(values) == 1: # If there's only one value, we have a couple of alternate calling # styles. if isinstance(values[0], basestring) or hasattr(values[0], '__iter__'): values = values[0] - + return type(name, (Enumerated,), {'values': values}) diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 74ce74c37..2fca94673 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -409,7 +409,7 @@ class Parser(object): # No function name. self.parts.append(FUNC_DELIM) return - + if self.pos >= len(self.string): # Identifier terminates string. self.parts.append(self.string[start_pos:self.pos]) @@ -447,7 +447,7 @@ class Parser(object): # Extract and advance past the parsed expression. expressions.append(Expression(subparser.parts)) - self.pos += subparser.pos + self.pos += subparser.pos if self.pos >= len(self.string) or \ self.string[self.pos] == GROUP_CLOSE: diff --git a/beets/util/pipeline.py b/beets/util/pipeline.py index 6adbf160f..9f1d5e9ea 100644 --- a/beets/util/pipeline.py +++ b/beets/util/pipeline.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -177,23 +177,23 @@ class FirstPipelineThread(PipelineThread): self.coro = coro self.out_queue = out_queue self.out_queue.acquire() - + self.abort_lock = Lock() self.abort_flag = False - + def run(self): try: while True: with self.abort_lock: if self.abort_flag: return - + # Get the value from the generator. try: msg = self.coro.next() except StopIteration: break - + # Send messages to the next stage. for msg in _allmsgs(msg): with self.abort_lock: @@ -207,7 +207,7 @@ class FirstPipelineThread(PipelineThread): # Generator finished; shut down the pipeline. self.out_queue.release() - + class MiddlePipelineThread(PipelineThread): """A thread running any stage in the pipeline except the first or last. @@ -223,7 +223,7 @@ class MiddlePipelineThread(PipelineThread): try: # Prime the coroutine. self.coro.next() - + while True: with self.abort_lock: if self.abort_flag: @@ -233,14 +233,14 @@ class MiddlePipelineThread(PipelineThread): msg = self.in_queue.get() if msg is POISON: break - + with self.abort_lock: if self.abort_flag: return # Invoke the current stage. out = self.coro.send(msg) - + # Send messages to next stage. for msg in _allmsgs(out): with self.abort_lock: @@ -251,7 +251,7 @@ class MiddlePipelineThread(PipelineThread): except: self.abort_all(sys.exc_info()) return - + # Pipeline is shutting down normally. self.out_queue.release() @@ -273,12 +273,12 @@ class LastPipelineThread(PipelineThread): with self.abort_lock: if self.abort_flag: return - + # Get the message from the previous stage. msg = self.in_queue.get() if msg is POISON: break - + with self.abort_lock: if self.abort_flag: return @@ -308,7 +308,7 @@ class Pipeline(object): self.stages.append((stage,)) else: self.stages.append(stage) - + def run_sequential(self): """Run the pipeline sequentially in the current thread. The stages are run one after the other. Only the first coroutine @@ -319,7 +319,7 @@ class Pipeline(object): # "Prime" the coroutines. for coro in coros[1:]: coro.next() - + # Begin the pipeline. for out in coros[0]: msgs = _allmsgs(out) @@ -329,7 +329,7 @@ class Pipeline(object): out = coro.send(msg) next_msgs.extend(_allmsgs(out)) msgs = next_msgs - + def run_parallel(self, queue_size=DEFAULT_QUEUE_SIZE): """Run the pipeline in parallel using one thread per stage. The messages between the stages are stored in queues of the given @@ -354,11 +354,11 @@ class Pipeline(object): threads.append( LastPipelineThread(coro, queues[-1], threads) ) - + # Start threads. for thread in threads: thread.start() - + # Wait for termination. The final thread lasts the longest. try: # Using a timeout allows us to receive KeyboardInterrupt @@ -371,7 +371,7 @@ class Pipeline(object): for thread in threads: thread.abort() raise - + finally: # Make completely sure that all the threads have finished # before we return. They should already be either finished, @@ -388,7 +388,7 @@ class Pipeline(object): # Smoke test. if __name__ == '__main__': import time - + # Test a normally-terminating pipeline both in sequence and # in parallel. def produce(): diff --git a/beets/vfs.py b/beets/vfs.py index 09f865752..ad11c92e5 100644 --- a/beets/vfs.py +++ b/beets/vfs.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/__init__.py b/beetsplug/__init__.py index 30f263982..2faacc8e3 100644 --- a/beetsplug/__init__.py +++ b/beetsplug/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index 681331a85..2170445a8 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -89,7 +89,7 @@ class BPDError(Exception): self.message = message self.cmd_name = cmd_name 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 @@ -120,7 +120,7 @@ ArgumentNotFoundError = make_bpd_error(ERROR_NO_EXIST, 'argument not found') def cast_arg(t, val): """Attempts to call t on val, raising a CommandArgumentError on ValueError. - + If 't' is the special string 'intbool', attempts to cast first to an int and then to a bool (i.e., 1=True, 0=False). """ @@ -142,17 +142,17 @@ class BPDClose(Exception): class BaseServer(object): """A MPD-compatible music player server. - + The functions with the `cmd_` prefix are invoked in response to client commands. For instance, if the client says `status`, `cmd_status` will be invoked. The arguments to the client's commands are used as function arguments following the connection issuing the command. The functions may send data on the connection. They may also raise BPDError exceptions to report errors. - + This is a generic superclass and doesn't support many commands. """ - + def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, password=DEFAULT_PASSWORD): """Create a new server bound to address `host` and listening @@ -160,7 +160,7 @@ class BaseServer(object): anything significant on the server. """ self.host, self.port, self.password = host, port, password - + # Default server values. self.random = False self.repeat = False @@ -174,7 +174,7 @@ class BaseServer(object): # Object for random numbers generation self.random_obj = random.Random() - + def run(self): """Block and start listening for connections from clients. An interrupt (^C) closes the server. @@ -188,7 +188,7 @@ class BaseServer(object): single song's metadata. """ raise NotImplementedError - + def _item_id(self, item): """An abstract method returning the integer id for an item. """ @@ -242,16 +242,16 @@ class BaseServer(object): def cmd_ping(self, conn): """Succeeds.""" pass - + def cmd_kill(self, conn): """Exits the server process.""" self.listener.close() exit(0) - + def cmd_close(self, conn): """Closes the connection.""" raise BPDClose() - + def cmd_password(self, conn, password): """Attempts password authentication.""" if password == self.password: @@ -259,20 +259,20 @@ class BaseServer(object): else: conn.authenticated = False raise BPDError(ERROR_PASSWORD, 'incorrect password') - + def cmd_commands(self, conn): """Lists the commands available to the user.""" if self.password and not conn.authenticated: # Not authenticated. Show limited list of commands. for cmd in SAFE_COMMANDS: yield u'command: ' + cmd - + else: # Authenticated. Show all commands. for func in dir(self): if func.startswith('cmd_'): yield u'command: ' + func[4:] - + def cmd_notcommands(self, conn): """Lists all unavailable commands.""" if self.password and not conn.authenticated: @@ -282,15 +282,15 @@ class BaseServer(object): cmd = func[4:] if cmd not in SAFE_COMMANDS: yield u'command: ' + cmd - + else: # Authenticated. No commands are unavailable. pass - + def cmd_status(self, conn): """Returns some status information for use with an implementation of cmd_status. - + Gives a list of response-lines for: volume, repeat, random, playlist, playlistlength, and xfade. """ @@ -301,7 +301,7 @@ class BaseServer(object): u'playlistlength: ' + unicode(len(self.playlist)), u'xfade: ' + unicode(self.crossfade), ) - + if self.current_index == -1: state = u'stop' elif self.paused: @@ -309,7 +309,7 @@ class BaseServer(object): else: state = u'play' yield u'state: ' + state - + 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) @@ -324,34 +324,34 @@ class BaseServer(object): command (for instance, when playing a file). """ self.error = None - + def cmd_random(self, conn, state): """Set or unset random (shuffle) mode.""" self.random = cast_arg('intbool', state) - + def cmd_repeat(self, conn, state): """Set or unset repeat mode.""" self.repeat = cast_arg('intbool', state) - + def cmd_setvol(self, conn, vol): """Set the player's volume level (0-100).""" vol = cast_arg(int, vol) if vol < VOLUME_MIN or vol > VOLUME_MAX: raise BPDError(ERROR_ARG, u'volume out of range') self.volume = vol - + def cmd_crossfade(self, conn, crossfade): """Set the number of seconds of crossfading.""" crossfade = cast_arg(int, crossfade) - if crossfade < 0: + if crossfade < 0: raise BPDError(ERROR_ARG, u'crossfade time must be nonnegative') - + def cmd_clear(self, conn): """Clear the playlist.""" self.playlist = [] self.playlist_version += 1 self.cmd_stop(conn) - + def cmd_delete(self, conn, index): """Remove the song at index from the playlist.""" index = cast_arg(int, index) @@ -369,7 +369,7 @@ class BaseServer(object): def cmd_deleteid(self, conn, track_id): self.cmd_delete(conn, self._id_to_index(track_id)) - + def cmd_move(self, conn, idx_from, idx_to): """Move a track in the playlist.""" idx_from = cast_arg(int, idx_from) @@ -379,7 +379,7 @@ class BaseServer(object): self.playlist.insert(idx_to, track) except IndexError: raise ArgumentIndexError() - + # Update currently-playing song. if idx_from == self.current_index: self.current_index = idx_to @@ -387,13 +387,13 @@ class BaseServer(object): self.current_index -= 1 elif idx_from > self.current_index >= idx_to: self.current_index += 1 - + self.playlist_version += 1 - + def cmd_moveid(self, conn, id_from, idx_to): idx_from = self._id_to_index(idx_from) return self.cmd_move(conn, idx_from, idx_to) - + def cmd_swap(self, conn, i, j): """Swaps two tracks in the playlist.""" i = cast_arg(int, i) @@ -403,27 +403,27 @@ class BaseServer(object): track_j = self.playlist[j] except IndexError: raise ArgumentIndexError() - + self.playlist[j] = track_i self.playlist[i] = track_j - + # Update currently-playing song. if self.current_index == i: self.current_index = j elif self.current_index == j: self.current_index = i - + self.playlist_version += 1 - + def cmd_swapid(self, conn, i_id, j_id): i = self._id_to_index(i_id) j = self._id_to_index(j_id) return self.cmd_swap(conn, i, j) - + def cmd_urlhandlers(self, conn): """Indicates supported URL schemes. None by default.""" pass - + def cmd_playlistinfo(self, conn, index=-1): """Gives metadata information about the entire playlist or a single track, given by its index. @@ -440,25 +440,25 @@ class BaseServer(object): yield self._item_info(track) def cmd_playlistid(self, conn, track_id=-1): return self.cmd_playlistinfo(conn, self._id_to_index(track_id)) - + def cmd_plchanges(self, conn, version): """Sends playlist changes since the given version. - + This is a "fake" implementation that ignores the version and just returns the entire playlist (rather like version=0). This seems to satisfy many clients. """ return self.cmd_playlistinfo(conn) - + def cmd_plchangesposid(self, conn, version): """Like plchanges, but only sends position and id. - + Also a dummy implementation. """ for idx, track in enumerate(self.playlist): yield u'cpos: ' + unicode(idx) yield u'Id: ' + unicode(track.id) - + def cmd_currentsong(self, conn): """Sends information about the currently-playing song. """ @@ -474,7 +474,7 @@ class BaseServer(object): return self.cmd_stop(conn) else: return self.cmd_play(conn) - + def cmd_previous(self, conn): """Step back to the last song.""" self.current_index = self._prev_idx() @@ -482,33 +482,33 @@ class BaseServer(object): return self.cmd_stop(conn) else: return self.cmd_play(conn) - + def cmd_pause(self, conn, state=None): """Set the pause state playback.""" if state is None: self.paused = not self.paused # Toggle. else: self.paused = cast_arg('intbool', state) - + def cmd_play(self, conn, index=-1): """Begin playback, possibly at a specified playlist index.""" index = cast_arg(int, index) - + 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. return self.cmd_stop(conn) 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. self.current_index = index - + self.paused = False - + def cmd_playid(self, conn, track_id=0): track_id = cast_arg(int, track_id) if track_id == -1: @@ -516,12 +516,12 @@ class BaseServer(object): else: index = self._id_to_index(track_id) return self.cmd_play(conn, index) - + def cmd_stop(self, conn): """Stop playback.""" self.current_index = -1 self.paused = False - + def cmd_seek(self, conn, index, pos): """Seek to a specified point in a specified song.""" index = cast_arg(int, index) @@ -548,7 +548,7 @@ class Connection(object): self.server = server self.sock = sock self.authenticated = False - + def send(self, lines): """Send lines, which which is either a single string or an iterable consisting of strings, to the client. A newline is @@ -562,7 +562,7 @@ class Connection(object): if isinstance(out, unicode): out = out.encode('utf8') return self.sock.sendall(out) - + def do_command(self, command): """A coroutine that runs the given command and sends an appropriate response.""" @@ -574,20 +574,20 @@ class Connection(object): else: # Send success code. yield self.send(RESP_OK) - + def run(self): """Send a greeting to the client and begin processing commands as they arrive. """ yield self.send(HELLO) - + clist = None # Initially, no command list is being constructed. while True: line = (yield self.sock.readline()).strip() if not line: break log.debug(line) - + if clist is not None: # Command list already opened. if line == CLIST_END: @@ -595,11 +595,11 @@ class Connection(object): clist = None # Clear the command list. else: clist.append(Command(line)) - + elif line == CLIST_BEGIN or line == CLIST_VERBOSE_BEGIN: # Begin a command list. clist = CommandList([], line == CLIST_VERBOSE_BEGIN) - + else: # Ordinary command. try: @@ -608,7 +608,7 @@ class Connection(object): # Command indicates that the conn should close. self.sock.close() return - + @classmethod def handler(cls, server): def _handle(sock): @@ -620,10 +620,10 @@ class Connection(object): class Command(object): """A command issued by the client for processing by the server. """ - + command_re = re.compile(r'^([^ \t]+)[ \t]*') arg_re = re.compile(r'"((?:\\"|[^"])+)"|([^ \t"]+)') - + def __init__(self, s): """Creates a new `Command` from the given string, `s`, parsing the string for command name and arguments. @@ -643,7 +643,7 @@ class Command(object): arg = match[1] arg = arg.decode('utf8') self.args.append(arg) - + def run(self, conn): """A coroutine that executes the command on the given connection. @@ -653,36 +653,36 @@ class Command(object): if not hasattr(conn.server, func_name): raise BPDError(ERROR_UNKNOWN, u'unknown command', self.name) func = getattr(conn.server, func_name) - + # Ensure we have permission for this command. if conn.server.password and \ not conn.authenticated and \ self.name not in SAFE_COMMANDS: raise BPDError(ERROR_PERMISSION, u'insufficient privileges') - + try: args = [conn] + self.args results = func(*args) if results: for data in results: yield conn.send(data) - + except BPDError, e: # An exposed error. Set the command name and then let # the Connection handle it. e.cmd_name = self.name raise e - + except BPDClose: # An indication that the connection should close. Send # it on the Connection. raise - + except Exception, e: # An "unintentional" error. Hide it from the client. log.error(traceback.format_exc(e)) raise BPDError(ERROR_SYSTEM, u'server error', self.name) - + class CommandList(list): """A list of commands issued by the client for processing by the @@ -737,7 +737,7 @@ class Server(BaseServer): self.lib = library self.player = gstplayer.GstPlayer(self.play_finished) self.cmd_update(None) - + def run(self): self.player.run() super(Server, self).run() @@ -747,10 +747,10 @@ class Server(BaseServer): track. """ self.cmd_next(None) - - + + # Metadata helper functions. - + def _item_info(self, item): info_lines = [u'file: ' + self.lib.destination(item, fragment=True), u'Time: ' + unicode(int(item.length)), @@ -759,25 +759,25 @@ class Server(BaseServer): u'Album: ' + item.album, u'Genre: ' + item.genre, ] - + track = unicode(item.track) if item.tracktotal: track += u'/' + unicode(item.tracktotal) info_lines.append(u'Track: ' + track) - + info_lines.append(u'Date: ' + unicode(item.year)) - + try: pos = self._id_to_index(item.id) info_lines.append(u'Pos: ' + unicode(pos)) except ArgumentNotFoundError: # Don't include position if not in playlist. pass - + info_lines.append(u'Id: ' + unicode(item.id)) - + return info_lines - + def _item_id(self, item): return item.id @@ -799,7 +799,7 @@ class Server(BaseServer): def _resolve_path(self, path): """Returns a VFS node or an item ID located at the path given. - If the path does not exist, raises a + If the path does not exist, raises a """ components = path.split(u'/') node = self.tree @@ -820,12 +820,12 @@ class Server(BaseServer): raise ArgumentNotFoundError() return node - + def _path_join(self, p1, p2): """Smashes together two BPD paths.""" out = p1 + u'/' + p2 return out.replace(u'//', u'/').replace(u'//', u'/') - + def cmd_lsinfo(self, conn, path=u"/"): """Sends info on all the items in the path.""" node = self._resolve_path(path) @@ -842,7 +842,7 @@ class Server(BaseServer): # Strip leading slash (libmpc rejects this). dirpath = dirpath[1:] yield u'directory: %s' % dirpath - + def _listall(self, basepath, node, info=False): """Helper function for recursive listing. If info, show tracks' complete info; otherwise, just show items' paths. @@ -864,15 +864,15 @@ class Server(BaseServer): newpath = self._path_join(basepath, name) yield u'directory: ' + newpath 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): @@ -889,7 +889,7 @@ class Server(BaseServer): 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 - + def _add(self, path, send_id=False): """Adds a track or directory to the playlist, specified by the path. If `send_id`, write each item's id to the client. @@ -899,13 +899,13 @@ class Server(BaseServer): if send_id: yield u'Id: ' + unicode(item.id) self.playlist_version += 1 - + def cmd_add(self, conn, path): """Adds a track or directory to the playlist, specified by a path. """ return self._add(path, False) - + def cmd_addid(self, conn, path): """Same as `cmd_add` but sends an id back to the client.""" return self._add(path, True) @@ -918,13 +918,13 @@ class Server(BaseServer): yield line if self.current_index > -1: item = self.playlist[self.current_index] - + yield u'bitrate: ' + unicode(item.bitrate/1000) #fixme: missing 'audio' - + (pos, total) = self.player.time() yield u'time: ' + unicode(pos) + u':' + unicode(total) - + #fixme: also missing 'updating_db' @@ -970,7 +970,7 @@ class Server(BaseServer): """ for tag in self.tagtype_map: yield u'tagtype: ' + tag - + def _tagtype_lookup(self, tag): """Uses `tagtype_map` to look up the beets column name for an MPD tagtype (or throw an appropriate exception). Returns both @@ -1005,7 +1005,7 @@ class Server(BaseServer): return beets.library.AndQuery(queries) else: # No key-value pairs. return beets.library.TrueQuery() - + def cmd_search(self, conn, *kv): """Perform a substring match for items.""" query = self._metadata_query(beets.library.SubstringQuery, @@ -1013,7 +1013,7 @@ class Server(BaseServer): kv) for item in self.lib.items(query): yield self._item_info(item) - + def cmd_find(self, conn, *kv): """Perform an exact match for items.""" query = self._metadata_query(beets.library.MatchQuery, @@ -1021,24 +1021,24 @@ class Server(BaseServer): kv) for item in self.lib.items(query): yield self._item_info(item) - + def cmd_list(self, conn, show_tag, *kv): """List distinct metadata values for show_tag, possibly filtered by matching match_tag to match_term. """ show_tag_canon, show_key = self._tagtype_lookup(show_tag) query = self._metadata_query(beets.library.MatchQuery, None, kv) - + clause, subvals = query.clause() statement = 'SELECT DISTINCT ' + show_key + \ ' FROM items WHERE ' + clause + \ ' ORDER BY ' + show_key with self.lib.transaction() as tx: rows = tx.query(statement, subvals) - + for row in rows: yield show_tag_canon + u': ' + unicode(row[0]) - + def cmd_count(self, conn, tag, value): """Returns the number and total time of songs matching the tag/value query. @@ -1048,31 +1048,31 @@ class Server(BaseServer): songs, playtime = query.count(self.lib) yield u'songs: ' + unicode(songs) yield u'playtime: ' + unicode(int(playtime)) - - + + # "Outputs." Just a dummy implementation because we don't control # any outputs. - + def cmd_outputs(self, conn): """List the available outputs.""" yield (u'outputid: 0', u'outputname: gstreamer', u'outputenabled: 1', ) - + def cmd_enableoutput(self, conn, output_id): output_id = cast_arg(int, output_id) if output_id != 0: raise ArgumentIndexError() - + def cmd_disableoutput(self, conn, output_id): output_id = cast_arg(int, output_id) if output_id == 0: raise BPDError(ERROR_ARG, u'cannot disable this output') 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. @@ -1081,7 +1081,7 @@ class Server(BaseServer): new_index = index != -1 and index != self.current_index was_paused = self.paused super(Server, self).cmd_play(conn, index) - + if self.current_index > -1: # Not stopped. if was_paused and not new_index: # Just unpause. @@ -1099,7 +1099,7 @@ class Server(BaseServer): def cmd_stop(self, conn): super(Server, self).cmd_stop(conn) self.player.stop() - + def cmd_seek(self, conn, index, pos): """Seeks to the specified position in the specified song.""" index = cast_arg(int, index) @@ -1152,6 +1152,6 @@ class BPDPlugin(BeetsPlugin): DEFAULT_PASSWORD) debug = opts.debug or False self.start_bpd(lib, host, int(port), password, debug) - + cmd.func = func return [cmd] diff --git a/beetsplug/bpd/bluelet.py b/beetsplug/bpd/bluelet.py index 545d87800..43b62d2a5 100644 --- a/beetsplug/bpd/bluelet.py +++ b/beetsplug/bpd/bluelet.py @@ -123,7 +123,7 @@ class ThreadException(Exception): self.exc_info = exc_info def reraise(self): raise self.exc_info[0], self.exc_info[1], self.exc_info[2] - + def run(root_coro): """Schedules a coroutine, running it to completion. This encapsulates the Bluelet scheduler, which the root coroutine can @@ -138,7 +138,7 @@ def run(root_coro): # suspended until the delegate completes. This dictionary keeps # track of each running delegate's delegator. delegators = {} - + def advance_thread(coro, value, is_exc=False): """After an event is fired, run a given coroutine associated with it in the threads dict until it yields again. If the coroutine @@ -222,7 +222,7 @@ def run(root_coro): threads[event2coro[event]] = ReturnEvent(None) else: advance_thread(event2coro[event], value) - + except ThreadException, te: # Exception raised from inside a thread. event = ExceptionEvent(te.exc_info) @@ -235,7 +235,7 @@ def run(root_coro): # The thread is root-level. Raise in client code. exit_te = te break - + except: # For instance, KeyboardInterrupt during select(). Raise # into root thread and terminate others. @@ -414,7 +414,7 @@ def server(host, port, func): yield func(conn) finally: conn.close() - + listener = Listener(host, port) try: while True: diff --git a/beetsplug/bpd/gstplayer.py b/beetsplug/bpd/gstplayer.py index 6094f2c5e..2317a4300 100644 --- a/beetsplug/bpd/gstplayer.py +++ b/beetsplug/bpd/gstplayer.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -27,27 +27,27 @@ import urllib class GstPlayer(object): """A music player abstracting GStreamer's Playbin element. - + Create a player object, then call run() to start a thread with a runloop. Then call play_file to play music. Use player.playing to check whether music is currently playing. - + A basic play queue is also implemented (just a Python list, player.queue, whose last element is next to play). To use it, just call enqueue() and then play(). When a track finishes and another is available on the queue, it is played automatically. """ - + def __init__(self, finished_callback=None): """Initialize a player. - + If a finished_callback is provided, it is called every time a track started with play_file finishes. Once the player has been created, call run() to begin the main runloop in a separate thread. """ - + # Set up the Gstreamer player. From the pygst tutorial: # http://pygstdocs.berlios.de/pygst-tutorial/playbin.html self.player = gst.element_factory_make("playbin2", "player") @@ -56,7 +56,7 @@ class GstPlayer(object): bus = self.player.get_bus() bus.add_signal_watch() bus.connect("message", self._handle_message) - + # Set up our own stuff. self.playing = False self.finished_callback = finished_callback @@ -68,7 +68,7 @@ class GstPlayer(object): # gst's get_state function returns a 3-tuple; we just want the # status flag in position 1. return self.player.get_state()[1] - + def _handle_message(self, bus, message): """Callback for status updates from GStreamer.""" if message.type == gst.MESSAGE_EOS: @@ -115,7 +115,7 @@ class GstPlayer(object): if self._get_state() == gst.STATE_PAUSED: self.player.set_state(gst.STATE_PLAYING) self.playing = True - + def pause(self): """Pause playback.""" self.player.set_state(gst.STATE_PAUSED) @@ -128,7 +128,7 @@ class GstPlayer(object): def run(self): """Start a new thread for the player. - + Call this function before trying to play any music with play_file() or play(). """ @@ -150,7 +150,7 @@ class GstPlayer(object): length = self.player.query_duration(fmt, None)[0]/(10**9) self.cached_time = (pos, length) return (pos, length) - + except gst.QueryError: # Stream not ready. For small gaps of time, for instance # after seeking, the time values are unavailable. For this @@ -166,14 +166,14 @@ class GstPlayer(object): if position > cur_len: self.stop() return - + fmt = gst.Format(gst.FORMAT_TIME) ns = position * 10**9 # convert to nanoseconds self.player.seek_simple(fmt, gst.SEEK_FLAG_FLUSH, ns) - + # save new cached time self.cached_time = (position, cur_len) - + def block(self): """Block until playing finishes.""" while self.playing: diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py index 3e38a308d..56ed0098b 100644 --- a/beetsplug/chroma.py +++ b/beetsplug/chroma.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -63,11 +63,11 @@ def acoustid_match(path): res = acoustid.lookup(API_KEY, fp, duration, meta='recordings releases') except acoustid.AcoustidError, exc: - log.debug('fingerprint matching %s failed: %s' % + log.debug('fingerprint matching %s failed: %s' % (repr(path), str(exc))) return None log.debug('chroma: fingerprinted %s' % repr(path)) - + # Ensure the response is usable and parse it. if res['status'] != 'ok' or not res.get('results'): log.debug('chroma: no match found') diff --git a/beetsplug/importfeeds.py b/beetsplug/importfeeds.py index 449cd9ad7..170b6c4b5 100644 --- a/beetsplug/importfeeds.py +++ b/beetsplug/importfeeds.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -38,9 +38,9 @@ class ImportFeedsPlugin(BeetsPlugin): _feeds_dir = ui.config_val(config, 'importfeeds', 'feeds_dir', None) _feeds_dir = os.path.expanduser(_feeds_dir) - _m3u_name = ui.config_val(config, 'importfeeds', 'm3u_name', + _m3u_name = ui.config_val(config, 'importfeeds', 'm3u_name', M3U_DEFAULT_NAME) - + if _feeds_dir and not os.path.exists(_feeds_dir): os.makedirs(_feeds_dir) @@ -58,7 +58,7 @@ def _get_feeds_dir(lib): return dirpath def _build_m3u_filename(basename): - """Builds unique m3u filename by appending given basename to current + """Builds unique m3u filename by appending given basename to current date.""" basename = re.sub(r"[\s,'\"]", '_', basename) @@ -81,15 +81,15 @@ def _record_items(lib, basename, items): _feeds_dir = _get_feeds_dir(lib) paths = [] - for item in items: + for item in items: paths.append(os.path.relpath(item.path, _feeds_dir)) if 'm3u' in _feeds_formats: - m3u_path = os.path.join(_feeds_dir, _m3u_name) + m3u_path = os.path.join(_feeds_dir, _m3u_name) _write_m3u(m3u_path, paths) - + if 'm3u_multi' in _feeds_formats: - m3u_path = _build_m3u_filename(basename) + m3u_path = _build_m3u_filename(basename) _write_m3u(m3u_path, paths) if 'link' in _feeds_formats: diff --git a/beetsplug/info.py b/beetsplug/info.py index bacc42e3e..b9fe393e7 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/inline.py b/beetsplug/inline.py index 4e4dfa00c..5bc77658d 100644 --- a/beetsplug/inline.py +++ b/beetsplug/inline.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 0ca4cb919..f1beb4b38 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -67,13 +67,13 @@ def _tags_for(obj): def _tags_to_genre(tags): """Given a tag list, returns a genre. Returns the first tag that is - present in the genre whitelist or None if no tag is suitable. + present in the genre whitelist or None if no tag is suitable. """ if not tags: return None elif not options['whitelist']: return tags[0].title() - + if options.get('c14n'): # Use the canonicalization tree. for tag in tags: @@ -157,13 +157,13 @@ class LastGenrePlugin(plugins.BeetsPlugin): from yaml import load genres_tree = load(open(c14n_filename, 'r')) branches = [] - flatten_tree(genres_tree, [], branches) - options['branches'] = branches + flatten_tree(genres_tree, [], branches) + options['branches'] = branches options['c14n'] = True fallback_str = ui.config_val(config, 'lastgenre', 'fallback_str', None) - + @LastGenrePlugin.listen('album_imported') def album_imported(lib, album, config): global fallback_str diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index bfa602195..c6ce30055 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/mpdupdate.py b/beetsplug/mpdupdate.py index c010d068b..80c58e3bf 100644 --- a/beetsplug/mpdupdate.py +++ b/beetsplug/mpdupdate.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. @@ -68,7 +68,7 @@ def update_mpd(host='localhost', port=6600, password=None): if 'OK MPD' not in resp: print 'MPD connection failed:', repr(resp) return - + if password: s.send('password "%s"\n' % password) resp = s.readline() diff --git a/beetsplug/rdm.py b/beetsplug/rdm.py index 272cb3f46..51c1583a3 100644 --- a/beetsplug/rdm.py +++ b/beetsplug/rdm.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/rewrite.py b/beetsplug/rewrite.py index f97ec58f6..3cbc8bc64 100644 --- a/beetsplug/rewrite.py +++ b/beetsplug/rewrite.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py index d6194e14e..cf8dcc859 100644 --- a/beetsplug/web/__init__.py +++ b/beetsplug/web/__init__.py @@ -8,7 +8,7 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. diff --git a/beetsplug/web/static/beets.css b/beetsplug/web/static/beets.css index d1c84d21a..7476502da 100644 --- a/beetsplug/web/static/beets.css +++ b/beetsplug/web/static/beets.css @@ -8,9 +8,9 @@ body { right: 0; top: 0; height: 36px; - + color: white; - + cursor: default; /* shadowy border */ @@ -33,15 +33,15 @@ body { #entities { width: 17em; - + position: fixed; top: 36px; left: 0; bottom: 0; margin: 0; - + z-index: 1; - background: #dde4eb; + background: #dde4eb; /* shadowy border */ box-shadow: 0 0 20px #666; @@ -59,7 +59,7 @@ body { } #entities ul { width: 17em; - + position: fixed; top: 36px; left: 0; diff --git a/beetsplug/web/static/beets.js b/beetsplug/web/static/beets.js index 6305a6ecf..44062a41b 100644 --- a/beetsplug/web/static/beets.js +++ b/beetsplug/web/static/beets.js @@ -133,7 +133,7 @@ $.fn.player = function(debug) { $.fn.disableSelection = function() { $(this).attr('unselectable', 'on') .css('-moz-user-select', 'none') - .each(function() { + .each(function() { this.onselectstart = function() { return false; }; }); }; diff --git a/beetsplug/web/templates/index.html b/beetsplug/web/templates/index.html index 63a0b152a..7a1b3e02e 100644 --- a/beetsplug/web/templates/index.html +++ b/beetsplug/web/templates/index.html @@ -4,7 +4,7 @@