diff --git a/beetsplug/play.py b/beetsplug/play.py index 4a2174909..2f708e5cb 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -19,17 +19,35 @@ from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets.ui import Subcommand +from beets.ui.commands import PromptChoice from beets import config from beets import ui from beets import util from os.path import relpath from tempfile import NamedTemporaryFile +import subprocess # Indicate where arguments should be inserted into the command string. # If this is missing, they're placed at the end. ARGS_MARKER = '$args' +def play(command_str, paths, open_args, keep_open=False): + """Play items in paths with command_str and optional arguments. If + keep_open, return to beets, otherwise exit once command runs. + """ + try: + if keep_open: + command = command_str.split() + command = command + open_args + subprocess.call(command) + else: + util.interactive_open(open_args, command_str) + except OSError as exc: + raise ui.UserError( + "Could not play the query: {0}".format(exc)) + + class PlayPlugin(BeetsPlugin): def __init__(self): @@ -40,11 +58,14 @@ class PlayPlugin(BeetsPlugin): 'use_folders': False, 'relative_to': None, 'raw': False, - # Backwards compatibility. See #1803 and line 74 + # Backwards compatibility. See #1803 and line 155 'warning_threshold': -2, 'warning_treshold': 100, }) + self.register_listener('before_choose_candidate', + self.before_choose_candidate_listener) + def commands(self): play_command = Subcommand( 'play', @@ -56,44 +77,17 @@ class PlayPlugin(BeetsPlugin): action='store', help=u'add additional arguments to the command', ) - play_command.func = self.play_music + play_command.func = self._play_command return [play_command] - def play_music(self, lib, opts, args): - """Execute query, create temporary playlist and execute player - command passing that playlist, at request insert optional arguments. + def _play_command(self, lib, opts, args): + """The CLI command function for `beet play`. Creates a list of paths + from query, determines if tracks or albums are to be played. """ - command_str = config['play']['command'].get() - if not command_str: - command_str = util.open_anything() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() - raw = config['play']['raw'].get(bool) - warning_threshold = config['play']['warning_threshold'].get(int) - # We use -2 as a default value for warning_threshold to detect if it is - # set or not. We can't use a falsey value because it would have an - # actual meaning in the configuration of this plugin, and we do not use - # -1 because some people might use it as a value to obtain no warning, - # which wouldn't be that bad of a practice. - if warning_threshold == -2: - # if warning_threshold has not been set by user, look for - # warning_treshold, to preserve backwards compatibility. See #1803. - # warning_treshold has the correct default value of 100. - warning_threshold = config['play']['warning_treshold'].get(int) - if relative_to: relative_to = util.normpath(relative_to) - - # Add optional arguments to the player command. - if opts.args: - if ARGS_MARKER in command_str: - command_str = command_str.replace(ARGS_MARKER, opts.args) - else: - command_str = u"{} {}".format(command_str, opts.args) - else: - # Don't include the marker in the command. - command_str = command_str.replace(" " + ARGS_MARKER, "") - # Perform search by album and add folders rather than tracks to # playlist. if opts.album: @@ -117,14 +111,64 @@ class PlayPlugin(BeetsPlugin): paths = [relpath(path, relative_to) for path in paths] item_type = 'track' - item_type += 's' if len(selection) > 1 else '' - if not selection: ui.print_(ui.colorize('text_warning', u'No {0} to play.'.format(item_type))) return + open_args = self._playlist_or_paths(paths) + command_str = self._create_command_str(opts.args) + + # If user aborts due to long playlist: + cancel = self._print_info(selection, command_str, open_args, item_type) + + # Otherwise proceed with play command. + if not cancel: + play(command_str, paths, open_args) + + def _create_command_str(self, args=None): + """Creates a command string from the config command and optional args. + """ + command_str = config['play']['command'].get() + if not command_str: + return util.open_anything() + # Add optional arguments to the player command. + if args: + if ARGS_MARKER in command_str: + return command_str.replace(ARGS_MARKER, args) + else: + return u"{} {}".format(command_str, args) + else: + # Don't include the marker in the command. + return command_str.replace(" " + ARGS_MARKER, "") + + def _playlist_or_paths(self, paths): + """Returns either the raw paths of items or a playlist of the items. + """ + raw = config['play']['raw'].get(bool) + if raw: + return paths + else: + return [self._create_tmp_playlist(paths)] + + def _print_info(self, selection, command_str, open_args, + item_type='track'): + """Prompts user whether to continue if playlist exceeds threshold. + """ + warning_threshold = config['play']['warning_threshold'].get(int) + # We use -2 as a default value for warning_threshold to detect if it is + # set or not. We can't use a falsey value because it would have an + # actual meaning in the configuration of this plugin, and we do not use + # -1 because some people might use it as a value to obtain no warning, + # which wouldn't be that bad of a practice. + if warning_threshold == -2: + # if warning_threshold has not been set by user, look for + # warning_treshold, to preserve backwards compatibility. See #1803. + # warning_treshold has the correct default value of 100. + warning_threshold = config['play']['warning_treshold'].get(int) + # Warn user before playing any huge playlists. + item_type += 's' if len(selection) > 1 else '' if warning_threshold and len(selection) > warning_threshold: ui.print_(ui.colorize( 'text_warning', @@ -132,20 +176,11 @@ class PlayPlugin(BeetsPlugin): len(selection), item_type))) if ui.input_options((u'Continue', u'Abort')) == 'a': - return + return True + # Print number of tracks or albums to be played, log command to be run. ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) - if raw: - open_args = paths - else: - open_args = [self._create_tmp_playlist(paths)] - self._log.debug(u'executing command: {} {!r}', command_str, open_args) - try: - util.interactive_open(open_args, command_str) - except OSError as exc: - raise ui.UserError( - "Could not play the query: {0}".format(exc)) def _create_tmp_playlist(self, paths_list): """Create a temporary .m3u file. Return the filename. @@ -155,3 +190,21 @@ class PlayPlugin(BeetsPlugin): m3u.write(item + b'\n') m3u.close() return m3u.name + + def before_choose_candidate_listener(self, session, task): + """Append a "Play" choice to the interactive importer prompt. + """ + return [PromptChoice('y', 'plaY', self.importer_play)] + + def importer_play(self, session, task): + """Get items from current import task and send to play function. + """ + selection = task.items + paths = [item.path for item in selection] + + open_args = self._playlist_or_paths(paths) + command_str = self._create_command_str() + cancel = self._print_info(selection, command_str, open_args) + + if not cancel: + play(command_str, paths, open_args, keep_open=True)