From 18d5c3b0a035b6b1659f711cd784a95b23cff95a Mon Sep 17 00:00:00 2001 From: "nath@home" Date: Mon, 24 Aug 2015 01:42:54 +0200 Subject: [PATCH 1/6] play-raw: Add the option to play the raw queried pathes I slightly rewrote the play plugin in order to improve the readability and to introduce the "raw" play config option which makes beet simply pass a list of pathes to the play command rather than a playlist. --- beetsplug/play.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index e6611ad3f..38264d9c0 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -39,6 +39,7 @@ class PlayPlugin(BeetsPlugin): 'command': None, 'use_folders': False, 'relative_to': None, + 'raw': False, }) def commands(self): @@ -62,6 +63,7 @@ class PlayPlugin(BeetsPlugin): command_str = config['play']['command'].get() use_folders = config['play']['use_folders'].get(bool) relative_to = config['play']['relative_to'].get() + raw = config['play']['raw'].get(bool) if relative_to: relative_to = util.normpath(relative_to) @@ -91,6 +93,8 @@ class PlayPlugin(BeetsPlugin): else: selection = lib.items(ui.decargs(args)) paths = [item.path for item in selection] + if relative_to: + paths = [relpath(path, relative_to) for path in paths] item_type = 'track' item_type += 's' if len(selection) > 1 else '' @@ -111,22 +115,31 @@ class PlayPlugin(BeetsPlugin): if ui.input_options(('Continue', 'Abort')) == 'a': return - # Create temporary m3u file to hold our playlist. - m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) - for item in paths: - if relative_to: - m3u.write(relpath(item, relative_to) + b'\n') - else: - m3u.write(item + b'\n') - m3u.close() - ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) + if raw: + passedToCommand = self._concatenatePaths(paths) + else: + passedToCommand, m3u = self._createTmpPlaylist(paths) - self._log.debug('executing command: {} {}', command_str, m3u.name) + self._log.debug('executing command: {} {}', command_str, + passedToCommand) try: - util.interactive_open(m3u.name, command_str) + util.interactive_open(passedToCommand, command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) finally: - util.remove(m3u.name) + if not raw: + util.remove(m3u.name) + + def _createTmpPlaylist(self, pathsList): + # Create temporary m3u file to hold our playlist. + m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) + for item in pathsList: + m3u.write(item + b'\n') + m3u.close() + return m3u.name, m3u + + def _concatenatePaths(self, pathsList): + concatenatedPaths = b'"' + b'" "'.join(pathsList) + b'"' + return concatenatedPaths From a23c5d4f67824d405ed165c6d31bf4f46ea14db0 Mon Sep 17 00:00:00 2001 From: "nath@home" Date: Mon, 24 Aug 2015 20:53:42 +0200 Subject: [PATCH 2/6] play-raw: Call vlc with one file par arg --- beets/util/__init__.py | 8 +++++--- beetsplug/play.py | 11 ++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 61a95baa2..2b2a13a25 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -735,7 +735,7 @@ def open_anything(): return base_cmd -def interactive_open(target, command=None): +def interactive_open(target=None, command=None, multiple_targets=[]): """Open the file `target` by `exec`ing a new command. (The new program takes over, and Python execution ends: this does not fork a subprocess.) @@ -756,6 +756,8 @@ def interactive_open(target, command=None): else: base_cmd = open_anything() command = [base_cmd, base_cmd] - - command.append(target) + if multiple_targets: + command = command + multiple_targets + else: + command.append(target) return os.execlp(*command) diff --git a/beetsplug/play.py b/beetsplug/play.py index 38264d9c0..b277349d0 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -117,14 +117,15 @@ class PlayPlugin(BeetsPlugin): ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) if raw: - passedToCommand = self._concatenatePaths(paths) + passedToCommand = paths else: passedToCommand, m3u = self._createTmpPlaylist(paths) self._log.debug('executing command: {} {}', command_str, passedToCommand) try: - util.interactive_open(passedToCommand, command_str) + util.interactive_open(multiple_targets=passedToCommand, + command=command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) @@ -138,8 +139,4 @@ class PlayPlugin(BeetsPlugin): for item in pathsList: m3u.write(item + b'\n') m3u.close() - return m3u.name, m3u - - def _concatenatePaths(self, pathsList): - concatenatedPaths = b'"' + b'" "'.join(pathsList) + b'"' - return concatenatedPaths + return [m3u.name], m3u From 4eb563a08c58f38bd828deca4dbb633856d9f224 Mon Sep 17 00:00:00 2001 From: "nath@home" Date: Tue, 25 Aug 2015 12:52:08 +0200 Subject: [PATCH 3/6] pep8: Correct camelCase to snake_case --- beetsplug/play.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index b277349d0..66d70ce16 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -117,14 +117,14 @@ class PlayPlugin(BeetsPlugin): ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) if raw: - passedToCommand = paths + passed_to_command = paths else: - passedToCommand, m3u = self._createTmpPlaylist(paths) + passed_to_command, m3u = self._create_tmp_playlist(paths) self._log.debug('executing command: {} {}', command_str, - passedToCommand) + passed_to_command) try: - util.interactive_open(multiple_targets=passedToCommand, + util.interactive_open(multiple_targets=passed_to_command, command=command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " @@ -133,10 +133,10 @@ class PlayPlugin(BeetsPlugin): if not raw: util.remove(m3u.name) - def _createTmpPlaylist(self, pathsList): + def _create_tmp_playlist(self, paths_list): # Create temporary m3u file to hold our playlist. m3u = NamedTemporaryFile('w', suffix='.m3u', delete=False) - for item in pathsList: + for item in paths_list: m3u.write(item + b'\n') m3u.close() return [m3u.name], m3u From 9c663432bdaa5439128f78da6b11352b39bee942 Mon Sep 17 00:00:00 2001 From: "nath@home" Date: Tue, 25 Aug 2015 15:05:54 +0200 Subject: [PATCH 4/6] Refactor util/interactive_open: multiple targets interactive_open should now be invoked with at least the list of targets and optionally the command to open the targets with. This allows beets-play to pass multiple file paths directly to the configured command. The changes to the existing invocations are pretty trivial in order to comply to this refactor. --- beets/ui/commands.py | 2 +- beets/util/__init__.py | 9 ++++----- beetsplug/play.py | 13 +++++++------ test/test_util.py | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 2a37a113d..f3386cf71 100644 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1485,7 +1485,7 @@ def config_edit(): try: if not os.path.isfile(path): open(path, 'w+').close() - util.interactive_open(path, editor) + util.interactive_open([path], editor) except OSError as exc: message = "Could not edit configuration: {0}".format(exc) if not editor: diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 2b2a13a25..261edd587 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -735,7 +735,7 @@ def open_anything(): return base_cmd -def interactive_open(target=None, command=None, multiple_targets=[]): +def interactive_open(targets, command=None): """Open the file `target` by `exec`ing a new command. (The new program takes over, and Python execution ends: this does not fork a subprocess.) @@ -756,8 +756,7 @@ def interactive_open(target=None, command=None, multiple_targets=[]): else: base_cmd = open_anything() command = [base_cmd, base_cmd] - if multiple_targets: - command = command + multiple_targets - else: - command.append(target) + + command += targets + return os.execlp(*command) diff --git a/beetsplug/play.py b/beetsplug/play.py index 66d70ce16..fc2e8e1b3 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -119,19 +119,20 @@ class PlayPlugin(BeetsPlugin): if raw: passed_to_command = paths else: - passed_to_command, m3u = self._create_tmp_playlist(paths) + passed_to_command = self._create_tmp_playlist(paths) self._log.debug('executing command: {} {}', command_str, - passed_to_command) + b'"' + b' '.join(passed_to_command) + b'"') try: - util.interactive_open(multiple_targets=passed_to_command, - command=command_str) + util.interactive_open(passed_to_command, command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) finally: if not raw: - util.remove(m3u.name) + self._log.debug('Removing temporary playlist: {}', + passed_to_command[0]) + util.remove(passed_to_command[0]) def _create_tmp_playlist(self, paths_list): # Create temporary m3u file to hold our playlist. @@ -139,4 +140,4 @@ class PlayPlugin(BeetsPlugin): for item in paths_list: m3u.write(item + b'\n') m3u.close() - return [m3u.name], m3u + return [m3u.name] diff --git a/test/test_util.py b/test/test_util.py index 464c7e678..324a4d589 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -43,11 +43,11 @@ class UtilTest(unittest.TestCase): @patch('beets.util.open_anything') def test_interactive_open(self, mock_open, mock_execlp): mock_open.return_value = 'tagada' - util.interactive_open('foo') + util.interactive_open(['foo']) mock_execlp.assert_called_once_with('tagada', 'tagada', 'foo') mock_execlp.reset_mock() - util.interactive_open('foo', 'bar') + util.interactive_open(['foo'], 'bar') mock_execlp.assert_called_once_with('bar', 'bar', 'foo') def test_sanitize_unix_replaces_leading_dot(self): From 7ed742b268a600df3b1ca547f5d50af334ba4bbf Mon Sep 17 00:00:00 2001 From: "nath@home" Date: Tue, 1 Sep 2015 23:38:25 +0200 Subject: [PATCH 5/6] play-raw: Fix a docstring and add doc --- beets/util/__init__.py | 2 +- docs/plugins/play.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 261edd587..158a6c8e1 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -736,7 +736,7 @@ def open_anything(): def interactive_open(targets, command=None): - """Open the file `target` by `exec`ing a new command. (The new + """Open the files in `targets` by `exec`ing a new command. (The new program takes over, and Python execution ends: this does not fork a subprocess.) diff --git a/docs/plugins/play.rst b/docs/plugins/play.rst index 08172c7c9..9af886c13 100644 --- a/docs/plugins/play.rst +++ b/docs/plugins/play.rst @@ -44,6 +44,9 @@ configuration file. The available options are: paths to each track on the matched albums. Enable this option to store paths to folders instead. Default: ``no``. +- **raw**: Instead of creating a temporary m3u playlist and then opening it, + simply call the command with the paths returned by the query as arguments. + Default: ``no``. Optional Arguments ------------------ From b9bc06d9d82c6021eafa877cb2f08ec7f686dd1f Mon Sep 17 00:00:00 2001 From: "nath@home" Date: Tue, 1 Sep 2015 23:39:35 +0200 Subject: [PATCH 6/6] play-raw: Rename the command target: passed_to_command -> open_args --- beetsplug/play.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/beetsplug/play.py b/beetsplug/play.py index fc2e8e1b3..038bfd42d 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -117,22 +117,22 @@ class PlayPlugin(BeetsPlugin): ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) if raw: - passed_to_command = paths + open_args = paths else: - passed_to_command = self._create_tmp_playlist(paths) + open_args = self._create_tmp_playlist(paths) self._log.debug('executing command: {} {}', command_str, - b'"' + b' '.join(passed_to_command) + b'"') + b'"' + b' '.join(open_args) + b'"') try: - util.interactive_open(passed_to_command, command_str) + util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError("Could not play the music playlist: " "{0}".format(exc)) finally: if not raw: self._log.debug('Removing temporary playlist: {}', - passed_to_command[0]) - util.remove(passed_to_command[0]) + open_args[0]) + util.remove(open_args[0]) def _create_tmp_playlist(self, paths_list): # Create temporary m3u file to hold our playlist.