cli.output: split up module into subpackage

- Move abstract `Output` base class into `streamlink_cli.output.abc`
  and properly declare abstract methods
- Move `FileOutput` into `streamlink_cli.output.file`
- Move `PlayerOutput` into `streamlink_cli.output.player`
- Update patched module paths in tests
This commit is contained in:
bastimeyer 2023-04-13 18:22:11 +02:00 committed by Forrest
parent e4691b8891
commit 61977d352a
8 changed files with 98 additions and 88 deletions

View File

@ -0,0 +1,3 @@
from streamlink_cli.output.abc import Output
from streamlink_cli.output.file import FileOutput
from streamlink_cli.output.player import PlayerOutput

View File

@ -0,0 +1,34 @@
from abc import ABCMeta, abstractmethod
class Output(metaclass=ABCMeta):
def __init__(self):
self.opened = False
def open(self):
self._open()
self.opened = True
def close(self):
if self.opened:
self._close()
self.opened = False
def write(self, data):
if not self.opened:
raise OSError("Output is not opened")
return self._write(data)
@abstractmethod
def _open(self):
raise NotImplementedError
@abstractmethod
def _close(self):
raise NotImplementedError
@abstractmethod
def _write(self, data):
raise NotImplementedError

View File

@ -0,0 +1,46 @@
from pathlib import Path
from typing import BinaryIO, Optional
from streamlink.compat import is_win32
from streamlink_cli.compat import stdout
from streamlink_cli.output.abc import Output
if is_win32:
import msvcrt
from os import O_BINARY
class FileOutput(Output):
def __init__(
self,
filename: Optional[Path] = None,
fd: Optional[BinaryIO] = None,
record: Optional["FileOutput"] = None,
):
super().__init__()
self.filename = filename
self.fd = fd
self.record = record
def _open(self):
if self.filename:
self.filename.parent.mkdir(parents=True, exist_ok=True)
self.fd = open(self.filename, "wb")
if self.record:
self.record.open()
if is_win32:
msvcrt.setmode(self.fd.fileno(), O_BINARY)
def _close(self):
if self.fd is not stdout:
self.fd.close()
if self.record:
self.record.close()
def _write(self, data):
self.fd.write(data)
if self.record:
self.record.write(data)

View File

@ -5,87 +5,17 @@ import shlex
import subprocess
import sys
from contextlib import suppress
from pathlib import Path
from time import sleep
from typing import BinaryIO, Optional
from streamlink.compat import is_win32
from streamlink_cli.compat import stdout
from streamlink_cli.constants import PLAYER_ARGS_INPUT_DEFAULT, PLAYER_ARGS_INPUT_FALLBACK, SUPPORTED_PLAYERS
from streamlink_cli.output.abc import Output
from streamlink_cli.utils import Formatter
if is_win32:
import msvcrt
log = logging.getLogger("streamlink.cli.output")
class Output:
def __init__(self):
self.opened = False
def open(self):
self._open()
self.opened = True
def close(self):
if self.opened:
self._close()
self.opened = False
def write(self, data):
if not self.opened:
raise OSError("Output is not opened")
return self._write(data)
def _open(self):
pass
def _close(self):
pass
def _write(self, data):
pass
class FileOutput(Output):
def __init__(
self,
filename: Optional[Path] = None,
fd: Optional[BinaryIO] = None,
record: Optional["FileOutput"] = None,
):
super().__init__()
self.filename = filename
self.fd = fd
self.record = record
def _open(self):
if self.filename:
self.filename.parent.mkdir(parents=True, exist_ok=True)
self.fd = open(self.filename, "wb")
if self.record:
self.record.open()
if is_win32:
msvcrt.setmode(self.fd.fileno(), os.O_BINARY)
def _close(self):
if self.fd is not stdout:
self.fd.close()
if self.record:
self.record.close()
def _write(self, data):
self.fd.write(data)
if self.record:
self.record.write(data)
class PlayerOutput(Output):
PLAYER_TERMINATE_TIMEOUT = 10.0
@ -281,6 +211,3 @@ class PlayerOutput(Output):
self.http.write(data)
else:
self.player.stdin.write(data)
__all__ = ["PlayerOutput", "FileOutput"]

View File

@ -10,7 +10,7 @@ import pytest
from streamlink_cli.output import FileOutput, PlayerOutput
@patch("streamlink_cli.output.stdout")
@patch("streamlink_cli.output.file.stdout")
class TestFileOutput(unittest.TestCase):
@staticmethod
def subject(filename, fd):
@ -75,7 +75,7 @@ class TestFileOutput(unittest.TestCase):
self._test_open(mock_open, mock_stdout)
@pytest.mark.windows_only()
@patch("streamlink_cli.output.msvcrt")
@patch("streamlink_cli.output.file.msvcrt")
@patch("builtins.open")
def test_open_windows(self, mock_open: Mock, mock_msvcrt: Mock, mock_stdout: Mock):
mock_path = self._test_open(mock_open, mock_stdout)
@ -91,34 +91,34 @@ class TestPlayerOutput(unittest.TestCase):
assert PlayerOutput.supported_player("mpv") == "mpv"
assert PlayerOutput.supported_player("potplayermini.exe") == "potplayer"
@patch("streamlink_cli.output.os.path.basename", new=ntpath.basename)
@patch("streamlink_cli.output.player.os.path.basename", new=ntpath.basename)
def test_supported_player_win32(self):
assert PlayerOutput.supported_player("C:\\MPV\\mpv.exe") == "mpv"
assert PlayerOutput.supported_player("C:\\VLC\\vlc.exe") == "vlc"
assert PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe") == "potplayer"
@patch("streamlink_cli.output.os.path.basename", new=posixpath.basename)
@patch("streamlink_cli.output.player.os.path.basename", new=posixpath.basename)
def test_supported_player_posix(self):
assert PlayerOutput.supported_player("/usr/bin/mpv") == "mpv"
assert PlayerOutput.supported_player("/usr/bin/vlc") == "vlc"
@patch("streamlink_cli.output.os.path.basename", new=ntpath.basename)
@patch("streamlink_cli.output.player.os.path.basename", new=ntpath.basename)
def test_supported_player_args_win32(self):
assert PlayerOutput.supported_player("C:\\MPV\\mpv.exe --argh") == "mpv"
assert PlayerOutput.supported_player("C:\\VLC\\vlc.exe --argh") == "vlc"
assert PlayerOutput.supported_player("C:\\PotPlayer\\PotPlayerMini64.exe --argh") == "potplayer"
@patch("streamlink_cli.output.os.path.basename", new=posixpath.basename)
@patch("streamlink_cli.output.player.os.path.basename", new=posixpath.basename)
def test_supported_player_args_posix(self):
assert PlayerOutput.supported_player("/usr/bin/mpv --argh") == "mpv"
assert PlayerOutput.supported_player("/usr/bin/vlc --argh") == "vlc"
@patch("streamlink_cli.output.os.path.basename", new=posixpath.basename)
@patch("streamlink_cli.output.player.os.path.basename", new=posixpath.basename)
def test_supported_player_negative_posix(self):
assert PlayerOutput.supported_player("/usr/bin/xmpvideo") is None
assert PlayerOutput.supported_player("/usr/bin/echo") is None
@patch("streamlink_cli.output.os.path.basename", new=ntpath.basename)
@patch("streamlink_cli.output.player.os.path.basename", new=ntpath.basename)
def test_supported_player_negative_win32(self):
assert PlayerOutput.supported_player("C:\\mpc\\mpc-hd.exe") is None
assert PlayerOutput.supported_player("C:\\mplayer\\not-vlc.exe") is None

View File

@ -7,7 +7,7 @@ from streamlink_cli.output import PlayerOutput
@pytest.fixture()
def playeroutput(request: pytest.FixtureRequest):
with patch("streamlink_cli.output.sleep"):
with patch("streamlink_cli.output.player.sleep"):
playeroutput = PlayerOutput(**getattr(request, "param", {}))
yield playeroutput
playeroutput.close()
@ -16,7 +16,7 @@ def playeroutput(request: pytest.FixtureRequest):
@pytest.fixture()
def mock_popen(playeroutput: PlayerOutput):
mock_popen = Mock(return_value=Mock(poll=Mock(side_effect=Mock(return_value=None))))
with patch("streamlink_cli.output.sleep"), \
with patch("streamlink_cli.output.player.sleep"), \
patch("subprocess.Popen", mock_popen):
yield mock_popen

View File

@ -34,9 +34,9 @@ class CommandLineTestCase(unittest.TestCase):
patch("streamlink_cli.main.setup_plugins"), \
patch("streamlink_cli.main.setup_streamlink") as mock_setup_streamlink, \
patch("streamlink_cli.main.streamlink", session), \
patch("streamlink_cli.output.subprocess.Popen") as mock_popen, \
patch("streamlink_cli.output.subprocess.call") as mock_call, \
patch("streamlink_cli.output.sleep"):
patch("streamlink_cli.output.player.subprocess.Popen") as mock_popen, \
patch("streamlink_cli.output.player.subprocess.call") as mock_call, \
patch("streamlink_cli.output.player.sleep"):
mock_argv.__getitem__.side_effect = lambda x: args[x]
mock_popen.return_value = Mock(poll=Mock(side_effect=poll_factory([None, 0])))
try:

View File

@ -164,7 +164,7 @@ class TestPlayerOutput:
@pytest.fixture()
def output(self, player_process: Mock):
with patch("subprocess.Popen") as mock_popen, \
patch("streamlink_cli.output.sleep"):
patch("streamlink_cli.output.player.sleep"):
mock_popen.return_value = player_process
output = FakePlayerOutput("mocked")
output.open()