diff options
author | Devaev Maxim <[email protected]> | 2020-07-13 04:10:26 +0300 |
---|---|---|
committer | Devaev Maxim <[email protected]> | 2020-07-14 09:04:52 +0300 |
commit | 77f3dab55c52859490619825ace4620a5d134901 (patch) | |
tree | 90ac48b6cb5108543f9f3bad423c8a05de0e1ace | |
parent | 07fb731b215a560bdc0a89a1fd5c199d910034a3 (diff) |
optional quality and resolution
-rw-r--r-- | kvmd/apps/__init__.py | 6 | ||||
-rw-r--r-- | kvmd/apps/kvmd/server.py | 2 | ||||
-rw-r--r-- | kvmd/apps/kvmd/streamer.py | 98 | ||||
-rw-r--r-- | kvmd/validators/kvm.py | 13 | ||||
-rw-r--r-- | testenv/tests/validators/test_basic.py | 1 | ||||
-rw-r--r-- | testenv/tests/validators/test_kvm.py | 15 | ||||
-rw-r--r-- | web/kvm/index.html | 12 | ||||
-rw-r--r-- | web/share/js/kvm/stream.js | 4 |
8 files changed, 122 insertions, 29 deletions
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py index b3bc98fc..a4264faf 100644 --- a/kvmd/apps/__init__.py +++ b/kvmd/apps/__init__.py @@ -56,6 +56,7 @@ from ..validators.basic import valid_number from ..validators.basic import valid_int_f1 from ..validators.basic import valid_float_f0 from ..validators.basic import valid_float_f01 +from ..validators.basic import valid_string_list from ..validators.auth import valid_user from ..validators.auth import valid_users_list @@ -74,6 +75,7 @@ from ..validators.net import valid_ssl_ciphers from ..validators.kvm import valid_stream_quality from ..validators.kvm import valid_stream_fps +from ..validators.kvm import valid_stream_resolution from ..validators.kvm import valid_hid_key from ..validators.kvm import valid_hid_mouse_move @@ -259,9 +261,11 @@ def _get_config_scheme() -> Dict: "shutdown_delay": Option(10.0, type=valid_float_f01), "state_poll": Option(1.0, type=valid_float_f01), - "quality": Option(80, type=valid_stream_quality), + "quality": Option(80, type=(lambda arg: (valid_stream_quality(arg) if arg else 0))), # 0 for disabled feature "desired_fps": Option(30, type=valid_stream_fps), "max_fps": Option(60, type=valid_stream_fps), + "resolution": Option("", type=(lambda arg: (valid_stream_resolution(arg) if arg else ""))), + "available_resolutions": Option([], type=(lambda arg: valid_string_list(arg, subval=valid_stream_resolution))), "host": Option("localhost", type=valid_ip_or_host), "port": Option(0, type=valid_port), diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py index 9e4c1122..51589a16 100644 --- a/kvmd/apps/kvmd/server.py +++ b/kvmd/apps/kvmd/server.py @@ -56,6 +56,7 @@ from ...validators.basic import valid_bool from ...validators.kvm import valid_stream_quality from ...validators.kvm import valid_stream_fps +from ...validators.kvm import valid_stream_resolution from ... import aiotools from ... import aioproc @@ -193,6 +194,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins for (name, validator) in [ ("quality", valid_stream_quality), ("desired_fps", valid_stream_fps), + ("resolution", valid_stream_resolution), ]: if (value := request.query.get(name)): value = validator(value) diff --git a/kvmd/apps/kvmd/streamer.py b/kvmd/apps/kvmd/streamer.py index 5b239572..59277db8 100644 --- a/kvmd/apps/kvmd/streamer.py +++ b/kvmd/apps/kvmd/streamer.py @@ -54,6 +54,69 @@ class StreamerSnapshot: data: bytes +class _StreamerParams: + __DESIRED_FPS = "desired_fps" + __MAX_FPS = "max_fps" + + __QUALITY = "quality" + + __RESOLUTION = "resolution" + __AVAILABLE_RESOLUTIONS = "available_resolutions" + + def __init__( + self, + desired_fps: int, + max_fps: int, + quality: int, + resolution: str, + available_resolutions: List[str], + ) -> None: + + self.__has_quality = bool(quality) + self.__has_resolution = bool(resolution) + + self.__params: Dict = { + self.__DESIRED_FPS: desired_fps, + **({self.__QUALITY: quality} if self.__has_quality else {}), + **({self.__RESOLUTION: resolution} if self.__has_resolution else {}), + } + + self.__limits: Dict = { + self.__MAX_FPS: max_fps, + **({self.__AVAILABLE_RESOLUTIONS: available_resolutions} if self.__has_resolution else {}), + } + + def get_features(self) -> Dict: + return { + self.__QUALITY: self.__has_quality, + self.__RESOLUTION: self.__has_resolution, + } + + def get_limits(self) -> Dict: + limits = dict(self.__limits) + if self.__has_resolution: + limits[self.__AVAILABLE_RESOLUTIONS] = list(limits[self.__AVAILABLE_RESOLUTIONS]) + return limits + + def get_params(self) -> Dict: + return dict(self.__params) + + def set_params(self, params: Dict) -> None: + new_params = dict(self.__params) + + if self.__DESIRED_FPS in params: + new_params[self.__DESIRED_FPS] = min(max(params[self.__DESIRED_FPS], 0), self.__limits[self.__MAX_FPS]) + + if self.__QUALITY in params and self.__has_quality: + new_params[self.__QUALITY] = min(max(params[self.__QUALITY], 1), 100) + + if self.__RESOLUTION in params and self.__has_resolution: + if params[self.__RESOLUTION] in self.__limits[self.__AVAILABLE_RESOLUTIONS]: + new_params[self.__RESOLUTION] = params[self.__RESOLUTION] + + self.__params = new_params + + class Streamer: # pylint: disable=too-many-instance-attributes def __init__( # pylint: disable=too-many-arguments,too-many-locals self, @@ -66,10 +129,6 @@ class Streamer: # pylint: disable=too-many-instance-attributes shutdown_delay: float, state_poll: float, - quality: int, - desired_fps: int, - max_fps: int, - host: str, port: int, unix_path: str, @@ -78,6 +137,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes process_name_prefix: str, cmd: List[str], + + **params_kwargs: Any, ) -> None: self.__cap_pin = (gpio.set_output(cap_pin) if cap_pin >= 0 else -1) @@ -89,12 +150,6 @@ class Streamer: # pylint: disable=too-many-instance-attributes self.__shutdown_delay = shutdown_delay self.__state_poll = state_poll - self.__params = { - "quality": quality, - "desired_fps": desired_fps, - } - self.__max_fps = max_fps - assert port or unix_path self.__host = host self.__port = port @@ -105,6 +160,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes self.__cmd = cmd + self.__params = _StreamerParams(**params_kwargs) + self.__stop_task: Optional[asyncio.Task] = None self.__stop_wip = False @@ -183,16 +240,10 @@ class Streamer: # pylint: disable=too-many-instance-attributes def set_params(self, params: Dict) -> None: assert not self.__streamer_task - self.__params = { - key: min(max(params.get(key, self.__params[key]), a), b) - for (key, a, b) in [ - ("quality", 0, 100), - ("desired_fps", 0, self.__max_fps), - ] - } + return self.__params.set_params(params) def get_params(self) -> Dict: - return dict(self.__params) + return self.__params.get_params() # ===== @@ -216,10 +267,11 @@ class Streamer: # pylint: disable=too-many-instance-attributes del snapshot["data"] return { - "limits": {"max_fps": self.__max_fps}, - "params": self.__params, + "limits": self.__params.get_limits(), + "params": self.__params.get_params(), "snapshot": {"saved": snapshot}, "state": state, + "features": self.__params.get_features(), } async def poll_state(self) -> AsyncGenerator[Dict, None]: @@ -370,11 +422,11 @@ class Streamer: # pylint: disable=too-many-instance-attributes except asyncio.CancelledError: break - except Exception as err: + except Exception: if self.__streamer_proc: logger.exception("Unexpected streamer error: pid=%d", self.__streamer_proc.pid) else: - logger.exception("Can't start streamer: %s", err) + logger.exception("Can't start streamer") await self.__kill_streamer_proc() await asyncio.sleep(1) @@ -386,7 +438,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes port=self.__port, unix=self.__unix_path, process_name_prefix=self.__process_name_prefix, - **self.__params, + **self.__params.get_params(), ) for part in self.__cmd ] diff --git a/kvmd/validators/kvm.py b/kvmd/validators/kvm.py index cf63b97e..b32a785e 100644 --- a/kvmd/validators/kvm.py +++ b/kvmd/validators/kvm.py @@ -24,8 +24,10 @@ from typing import Any from ..keyboard.mappings import KEYMAP +from . import raise_error from . import check_string_in_list +from .basic import valid_stripped_string_not_empty from .basic import valid_number from .os import valid_printable_filename @@ -56,6 +58,17 @@ def valid_stream_fps(arg: Any) -> int: return int(valid_number(arg, min=0, max=120, name="stream FPS")) +def valid_stream_resolution(arg: Any) -> str: + name = "stream resolution" + arg = valid_stripped_string_not_empty(arg, name) + parts = arg.split("x") + if len(parts) != 2: + raise_error(arg, name) + width = int(valid_number(parts[0], min=1, name=f"{name} (width)")) + height = int(valid_number(parts[1], min=1, name=f"{name} (height)")) + return f"{width}x{height}" + + # ===== def valid_hid_key(arg: Any) -> str: return check_string_in_list(arg, "HID key", KEYMAP, lower=False) diff --git a/testenv/tests/validators/test_basic.py b/testenv/tests/validators/test_basic.py index 85a84d5d..1e8feb0c 100644 --- a/testenv/tests/validators/test_basic.py +++ b/testenv/tests/validators/test_basic.py @@ -142,6 +142,7 @@ def test_fail__valid_float_f01(arg: Any) -> None: # ===== @pytest.mark.parametrize("arg, retval", [ ("a, b, c", ["a", "b", "c"]), + ("a, b,, c", ["a", "b", "c"]), ("a b c", ["a", "b", "c"]), (["a", "b", "c"], ["a", "b", "c"]), ("", []), diff --git a/testenv/tests/validators/test_kvm.py b/testenv/tests/validators/test_kvm.py index ccafdbce..71ec1b26 100644 --- a/testenv/tests/validators/test_kvm.py +++ b/testenv/tests/validators/test_kvm.py @@ -32,6 +32,7 @@ from kvmd.validators.kvm import valid_atx_button from kvmd.validators.kvm import valid_log_seek from kvmd.validators.kvm import valid_stream_quality from kvmd.validators.kvm import valid_stream_fps +from kvmd.validators.kvm import valid_stream_resolution from kvmd.validators.kvm import valid_hid_key from kvmd.validators.kvm import valid_hid_mouse_move from kvmd.validators.kvm import valid_hid_mouse_button @@ -105,6 +106,20 @@ def test_fail__valid_stream_fps(arg: Any) -> None: # ===== [email protected]("arg", ["1280x720 ", "1x1"]) +def test_ok__valid_stream_resolution(arg: Any) -> None: + value = valid_stream_resolution(arg) + assert type(value) == str # pylint: disable=unidiomatic-typecheck + assert value == str(arg).strip() + + [email protected]("arg", ["x", None, "0x0", "0x1", "1x0", "1280", "1280x", "1280x720x"]) +def test_fail__valid_stream_resolution(arg: Any) -> None: + with pytest.raises(ValidatorError): + print(valid_stream_resolution(arg)) + + +# ===== def test_ok__valid_hid_key() -> None: for key in KEYMAP: print(valid_hid_key(key)) diff --git a/web/kvm/index.html b/web/kvm/index.html index 17959783..5ff339b4 100644 --- a/web/kvm/index.html +++ b/web/kvm/index.html @@ -142,11 +142,13 @@ <button data-force-hide-menu id="show-keyboard-button">• Show keyboard</button> <button data-force-hide-menu id="show-about-button">• Show about</button> </div> - <hr> - <div class="menu-item-content-text"> - Stream quality: <span id="stream-quality-value">80%</span> - <div class="stream-slider-box"> - <input disabled type="range" id="stream-quality-slider" class="slider" /> + <div id="stream-quality" class="feature-disabled"> + <hr> + <div class="menu-item-content-text"> + Stream quality: <span id="stream-quality-value">80%</span> + <div class="stream-slider-box"> + <input disabled type="range" id="stream-quality-slider" class="slider" /> + </div> </div> </div> <hr> diff --git a/web/share/js/kvm/stream.js b/web/share/js/kvm/stream.js index 7ea7d722..80832c7f 100644 --- a/web/share/js/kvm/stream.js +++ b/web/share/js/kvm/stream.js @@ -67,6 +67,10 @@ export function Streamer() { /************************************************************************/ self.setState = function(state) { + if (state) { + tools.setFeatureEnabled($("stream-quality"), state.features.quality && (state.state === null || state.state.encoder.quality > 0)); + } + if (state && state.state) { let max_fps = state.limits.max_fps; state = state.state; |