summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDevaev Maxim <[email protected]>2020-07-13 04:10:26 +0300
committerDevaev Maxim <[email protected]>2020-07-14 09:04:52 +0300
commit77f3dab55c52859490619825ace4620a5d134901 (patch)
tree90ac48b6cb5108543f9f3bad423c8a05de0e1ace
parent07fb731b215a560bdc0a89a1fd5c199d910034a3 (diff)
optional quality and resolution
-rw-r--r--kvmd/apps/__init__.py6
-rw-r--r--kvmd/apps/kvmd/server.py2
-rw-r--r--kvmd/apps/kvmd/streamer.py98
-rw-r--r--kvmd/validators/kvm.py13
-rw-r--r--testenv/tests/validators/test_basic.py1
-rw-r--r--testenv/tests/validators/test_kvm.py15
-rw-r--r--web/kvm/index.html12
-rw-r--r--web/share/js/kvm/stream.js4
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">&bull; Show keyboard</button>
<button data-force-hide-menu id="show-about-button">&bull; 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;