Skip to content

cli: optional player-args input variable #3313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions src/streamlink_cli/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
boolean, comma_list, comma_list_filter, filesize, keyvalue, num
)
from streamlink.utils.times import hours_minutes_seconds
from streamlink_cli.constants import (DEFAULT_PLAYER_ARGUMENTS, DEFAULT_STREAM_METADATA, STREAM_PASSTHROUGH, SUPPORTED_PLAYERS)
from streamlink_cli.constants import (
DEFAULT_STREAM_METADATA, PLAYER_ARGS_INPUT_DEFAULT, PLAYER_ARGS_INPUT_FALLBACK, STREAM_PASSTHROUGH, SUPPORTED_PLAYERS
)
from streamlink_cli.utils import find_default_player

_printable_re = re.compile(r"[{0}]".format(printable))
Expand Down Expand Up @@ -310,33 +312,36 @@ def build_parser():
player.add_argument(
"-a", "--player-args",
metavar="ARGUMENTS",
default=DEFAULT_PLAYER_ARGUMENTS,
help="""
default="",
help=f"""
This option allows you to customize the default arguments which are put
together with the value of --player to create a command to execute.
Unlike the --player parameter, custom player arguments will not be logged.

This value can contain formatting variables surrounded by curly braces,
It's usually enough to only use --player instead of this unless you need
to add arguments after the player's input argument or if you don't want
any of the player arguments to be logged.

The value can contain formatting variables surrounded by curly braces,
{{ and }}. If you need to include a brace character, it can be escaped
by doubling, e.g. {{{{ and }}}}.

Formatting variables available:

{{filename}}
This is the filename that the player will use. It's usually "-"
(stdin), but can also be a URL or a file depending on the options
used.

It's usually enough to use --player instead of this unless you need to
add arguments after the filename.
{{{PLAYER_ARGS_INPUT_DEFAULT}}}
This is the input that the player will use. For standard input (stdin),
it is ``-``, but it can also be a URL, depending on the options used.

Default is "{0}".
{{{PLAYER_ARGS_INPUT_FALLBACK}}}
The old fallback variable name with the same functionality.

Example:

%(prog)s -p vlc -a "--play-and-exit {{filename}}" <url> [stream]
%(prog)s -p vlc -a "--play-and-exit {{{PLAYER_ARGS_INPUT_DEFAULT}}}" <url> [stream]

""".format(DEFAULT_PLAYER_ARGUMENTS)
Note: When neither of the variables are found, ``{{{PLAYER_ARGS_INPUT_DEFAULT}}}``
will be appended to the whole parameter value, to ensure that the player
always receives an input argument.
"""
)
player.add_argument(
"-v", "--verbose-player",
Expand Down
9 changes: 6 additions & 3 deletions src/streamlink_cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from streamlink_cli.compat import is_win32

DEFAULT_PLAYER_ARGUMENTS = "{filename}"
PLAYER_ARGS_INPUT_DEFAULT = "playerinput"
PLAYER_ARGS_INPUT_FALLBACK = "filename"

DEFAULT_STREAM_METADATA = {
"title": "Unknown Title",
"author": "Unknown Author",
Expand Down Expand Up @@ -34,6 +36,7 @@
STREAM_PASSTHROUGH = ["hls", "http", "rtmp"]

__all__ = [
"CONFIG_FILES", "DEFAULT_PLAYER_ARGUMENTS",
"PLUGINS_DIR", "STREAM_SYNONYMS", "STREAM_PASSTHROUGH"
"PLAYER_ARGS_INPUT_DEFAULT", "PLAYER_ARGS_INPUT_FALLBACK",
"DEFAULT_STREAM_METADATA", "SUPPORTED_PLAYERS",
"CONFIG_FILES", "PLUGINS_DIR", "STREAM_SYNONYMS", "STREAM_PASSTHROUGH"
]
15 changes: 12 additions & 3 deletions src/streamlink_cli/output.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import logging
import os
import re
import shlex
import subprocess
import sys
from time import sleep

from streamlink_cli.compat import is_win32, stdout
from streamlink_cli.constants import DEFAULT_PLAYER_ARGUMENTS, SUPPORTED_PLAYERS
from streamlink_cli.constants import PLAYER_ARGS_INPUT_DEFAULT, PLAYER_ARGS_INPUT_FALLBACK, SUPPORTED_PLAYERS
from streamlink_cli.utils import ignored

if is_win32:
Expand Down Expand Up @@ -77,7 +78,12 @@ def _write(self, data):
class PlayerOutput(Output):
PLAYER_TERMINATE_TIMEOUT = 10.0

def __init__(self, cmd, args=DEFAULT_PLAYER_ARGUMENTS, filename=None, quiet=True, kill=True,
_re_player_args_input = re.compile("|".join(map(
lambda const: re.escape(f"{{{const}}}"),
[PLAYER_ARGS_INPUT_DEFAULT, PLAYER_ARGS_INPUT_FALLBACK]
)))

def __init__(self, cmd, args="", filename=None, quiet=True, kill=True,
call=False, http=None, namedpipe=None, record=None, title=None):
super().__init__()
self.cmd = cmd
Expand Down Expand Up @@ -106,6 +112,9 @@ def __init__(self, cmd, args=DEFAULT_PLAYER_ARGUMENTS, filename=None, quiet=True
self.stdout = sys.stdout
self.stderr = sys.stderr

if not self._re_player_args_input.search(self.args):
self.args += f"{' ' if self.args else ''}{{{PLAYER_ARGS_INPUT_DEFAULT}}}"

@property
def running(self):
sleep(0.5)
Expand Down Expand Up @@ -201,7 +210,7 @@ def _create_arguments(self):
self.title = self.title.replace('"', '')
filename = filename[:-1] + '\\' + self.title + filename[-1]

args = self.args.format(filename=filename)
args = self.args.format(**{PLAYER_ARGS_INPUT_DEFAULT: filename, PLAYER_ARGS_INPUT_FALLBACK: filename})
cmd = self.cmd

# player command
Expand Down
2 changes: 2 additions & 0 deletions tests/test_cli_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def test_create_output_no_file_output_options(self):
args.record_and_pipe = None
args.title = None
args.player = "mpv"
args.player_args = ""
self.assertIsInstance(create_output(FakePlugin), PlayerOutput)

def test_create_output_file_output(self):
Expand Down Expand Up @@ -182,6 +183,7 @@ def test_create_output_record(self):
args.record_and_pipe = None
args.title = None
args.player = "mpv"
args.player_args = ""
args.player_fifo = None
self.assertIsInstance(create_output(FakePlugin), PlayerOutput)
finally:
Expand Down
48 changes: 41 additions & 7 deletions tests/test_cli_playerout.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import ANY, patch
from unittest.mock import ANY, Mock, patch

from streamlink_cli.output import PlayerOutput
from tests import posix_only, windows_only
Expand All @@ -7,27 +7,30 @@


@posix_only
@patch('subprocess.Popen')
@patch("streamlink_cli.output.sleep", Mock())
@patch("subprocess.Popen")
def test_output_mpv_unicode_title_posix(popen):
po = PlayerOutput("mpv", title=UNICODE_TITLE)
popen().poll.side_effect = lambda: None
po.open()
popen.assert_called_with(['mpv', f'--title={UNICODE_TITLE}', '-'],
popen.assert_called_with(["mpv", f"--title={UNICODE_TITLE}", "-"],
bufsize=ANY, stderr=ANY, stdout=ANY, stdin=ANY)


@posix_only
@patch('subprocess.Popen')
@patch("streamlink_cli.output.sleep", Mock())
@patch("subprocess.Popen")
def test_output_vlc_unicode_title_posix(popen):
po = PlayerOutput("vlc", title=UNICODE_TITLE)
popen().poll.side_effect = lambda: None
po.open()
popen.assert_called_with(['vlc', '--input-title-format', UNICODE_TITLE, '-'],
popen.assert_called_with(["vlc", "--input-title-format", UNICODE_TITLE, "-"],
bufsize=ANY, stderr=ANY, stdout=ANY, stdin=ANY)


@windows_only
@patch('subprocess.Popen')
@patch("streamlink_cli.output.sleep", Mock())
@patch("subprocess.Popen")
def test_output_mpv_unicode_title_windows_py3(popen):
po = PlayerOutput("mpv.exe", title=UNICODE_TITLE)
popen().poll.side_effect = lambda: None
Expand All @@ -37,10 +40,41 @@ def test_output_mpv_unicode_title_windows_py3(popen):


@windows_only
@patch('subprocess.Popen')
@patch("streamlink_cli.output.sleep", Mock())
@patch("subprocess.Popen")
def test_output_vlc_unicode_title_windows_py3(popen):
po = PlayerOutput("vlc.exe", title=UNICODE_TITLE)
popen().poll.side_effect = lambda: None
po.open()
popen.assert_called_with(f"vlc.exe --input-title-format \"{UNICODE_TITLE}\" -",
bufsize=ANY, stderr=ANY, stdout=ANY, stdin=ANY)


@posix_only
def test_output_args_posix():
po_none = PlayerOutput("foo")
assert po_none._create_arguments() == ["foo", "-"]

po_implicit = PlayerOutput("foo", args="--bar")
assert po_implicit._create_arguments() == ["foo", "--bar", "-"]

po_explicit = PlayerOutput("foo", args="--bar {playerinput}")
assert po_explicit._create_arguments() == ["foo", "--bar", "-"]

po_fallback = PlayerOutput("foo", args="--bar {filename}")
assert po_fallback._create_arguments() == ["foo", "--bar", "-"]


@windows_only
def test_output_args_windows():
po_none = PlayerOutput("foo")
assert po_none._create_arguments() == "foo -"

po_implicit = PlayerOutput("foo", args="--bar")
assert po_implicit._create_arguments() == "foo --bar -"

po_explicit = PlayerOutput("foo", args="--bar {playerinput}")
assert po_explicit._create_arguments() == "foo --bar -"

po_fallback = PlayerOutput("foo", args="--bar {filename}")
assert po_fallback._create_arguments() == "foo --bar -"