From 1275ccf8c1e6fcd54217ee82059fb493ee8b9129 Mon Sep 17 00:00:00 2001 From: cvx35isl <127420554+cvx35isl@users.noreply.github.com> Date: Sun, 19 Oct 2025 08:38:20 +0200 Subject: [PATCH] =?UTF-8?q?play=20plugin:=20$playlist=20marker=20for=20pre?= =?UTF-8?q?cise=20control=20where=20the=20playlist=20=E2=80=A6=20(#4728)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …file is placed in the command ## Description see included doc; placing the playlist filename at the end of command just isn't working for all players I have this in use with `mpv` Co-authored-by: cvx35isl Co-authored-by: J0J0 Todos <2733783+JOJ0@users.noreply.github.com> --- beetsplug/play.py | 21 +++++++++++++++++++++ docs/changelog.rst | 4 ++++ docs/plugins/play.rst | 9 +++++++++ test/plugins/test_play.py | 13 +++++++++++++ 4 files changed, 47 insertions(+) diff --git a/beetsplug/play.py b/beetsplug/play.py index 35b4b1f76..8fb146213 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -28,6 +28,11 @@ from beets.util import get_temp_filename # If this is missing, they're placed at the end. ARGS_MARKER = "$args" +# Indicate where the playlist file (with absolute path) should be inserted into +# the command string. If this is missing, its placed at the end, but before +# arguments. +PLS_MARKER = "$playlist" + def play( command_str, @@ -132,8 +137,23 @@ class PlayPlugin(BeetsPlugin): return open_args = self._playlist_or_paths(paths) + open_args_str = [ + p.decode("utf-8") for p in self._playlist_or_paths(paths) + ] command_str = self._command_str(opts.args) + if PLS_MARKER in command_str: + if not config["play"]["raw"]: + command_str = command_str.replace( + PLS_MARKER, "".join(open_args_str) + ) + self._log.debug( + "command altered by PLS_MARKER to: {}", command_str + ) + open_args = [] + else: + command_str = command_str.replace(PLS_MARKER, " ") + # Check if the selection exceeds configured threshold. If True, # cancel, otherwise proceed with play command. if opts.yes or not self._exceeds_threshold( @@ -162,6 +182,7 @@ class PlayPlugin(BeetsPlugin): return paths else: return [self._create_tmp_playlist(paths)] + return [shlex.quote(self._create_tmp_playlist(paths))] def _exceeds_threshold( self, selection, command_str, open_args, item_type="track" diff --git a/docs/changelog.rst b/docs/changelog.rst index 0fc0ee477..5c6224de9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,8 @@ Unreleased New features: - :doc:`plugins/ftintitle`: Added argument for custom feat. words in ftintitle. +- :doc: `/plugins/play`: Added `$playlist` marker to precisely edit the playlist + filepath into the command calling the player program. Bug fixes: @@ -71,6 +73,8 @@ New features: :bug:`3354` - :doc:`plugins/discogs` Support for name variations and config options to specify where the variations are written. :bug:`3354` +- :doc: `/plugins/play`: Added `$playlist` marker to precisely edit the playlist + filepath into the command calling the player program. Bug fixes: diff --git a/docs/plugins/play.rst b/docs/plugins/play.rst index f4b07ac52..f06eb4cb3 100644 --- a/docs/plugins/play.rst +++ b/docs/plugins/play.rst @@ -107,6 +107,15 @@ string, use ``$args`` to indicate where to insert them. For example: indicates that you need to insert extra arguments before specifying the playlist. +Some players require a different syntax. For example, with ``mpv`` the optional +``$playlist`` variable can be used to match the syntax of the ``--playlist`` +option: + +:: + + play: + command: mpv $args --playlist=$playlist + The ``--yes`` (or ``-y``) flag to the ``play`` command will skip the warning message if you choose to play more items than the **warning_threshold** value usually allows. diff --git a/test/plugins/test_play.py b/test/plugins/test_play.py index 293a50a20..b184db63f 100644 --- a/test/plugins/test_play.py +++ b/test/plugins/test_play.py @@ -105,6 +105,19 @@ class PlayPluginTest(CleanupModulesMixin, PluginTestCase): open_mock.assert_called_once_with([self.item.path], "echo") + def test_pls_marker(self, open_mock): + self.config["play"]["command"] = ( + "echo --some params --playlist=$playlist --some-more params" + ) + + self.run_command("play", "nice") + + open_mock.assert_called_once + + commandstr = open_mock.call_args_list[0][0][1] + assert commandstr.startswith("echo --some params --playlist=") + assert commandstr.endswith(" --some-more params") + def test_not_found(self, open_mock): self.run_command("play", "not found")