mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 09:10:30 +08:00
optional quality and resolution
This commit is contained in:
parent
07fb731b21
commit
77f3dab55c
@ -56,6 +56,7 @@ from ..validators.basic import valid_number
|
|||||||
from ..validators.basic import valid_int_f1
|
from ..validators.basic import valid_int_f1
|
||||||
from ..validators.basic import valid_float_f0
|
from ..validators.basic import valid_float_f0
|
||||||
from ..validators.basic import valid_float_f01
|
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_user
|
||||||
from ..validators.auth import valid_users_list
|
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_quality
|
||||||
from ..validators.kvm import valid_stream_fps
|
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_key
|
||||||
from ..validators.kvm import valid_hid_mouse_move
|
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),
|
"shutdown_delay": Option(10.0, type=valid_float_f01),
|
||||||
"state_poll": Option(1.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),
|
"desired_fps": Option(30, type=valid_stream_fps),
|
||||||
"max_fps": Option(60, 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),
|
"host": Option("localhost", type=valid_ip_or_host),
|
||||||
"port": Option(0, type=valid_port),
|
"port": Option(0, type=valid_port),
|
||||||
|
|||||||
@ -56,6 +56,7 @@ from ...validators.basic import valid_bool
|
|||||||
|
|
||||||
from ...validators.kvm import valid_stream_quality
|
from ...validators.kvm import valid_stream_quality
|
||||||
from ...validators.kvm import valid_stream_fps
|
from ...validators.kvm import valid_stream_fps
|
||||||
|
from ...validators.kvm import valid_stream_resolution
|
||||||
|
|
||||||
from ... import aiotools
|
from ... import aiotools
|
||||||
from ... import aioproc
|
from ... import aioproc
|
||||||
@ -193,6 +194,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
|
|||||||
for (name, validator) in [
|
for (name, validator) in [
|
||||||
("quality", valid_stream_quality),
|
("quality", valid_stream_quality),
|
||||||
("desired_fps", valid_stream_fps),
|
("desired_fps", valid_stream_fps),
|
||||||
|
("resolution", valid_stream_resolution),
|
||||||
]:
|
]:
|
||||||
if (value := request.query.get(name)):
|
if (value := request.query.get(name)):
|
||||||
value = validator(value)
|
value = validator(value)
|
||||||
|
|||||||
@ -54,6 +54,69 @@ class StreamerSnapshot:
|
|||||||
data: bytes
|
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
|
class Streamer: # pylint: disable=too-many-instance-attributes
|
||||||
def __init__( # pylint: disable=too-many-arguments,too-many-locals
|
def __init__( # pylint: disable=too-many-arguments,too-many-locals
|
||||||
self,
|
self,
|
||||||
@ -66,10 +129,6 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
shutdown_delay: float,
|
shutdown_delay: float,
|
||||||
state_poll: float,
|
state_poll: float,
|
||||||
|
|
||||||
quality: int,
|
|
||||||
desired_fps: int,
|
|
||||||
max_fps: int,
|
|
||||||
|
|
||||||
host: str,
|
host: str,
|
||||||
port: int,
|
port: int,
|
||||||
unix_path: str,
|
unix_path: str,
|
||||||
@ -78,6 +137,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
process_name_prefix: str,
|
process_name_prefix: str,
|
||||||
|
|
||||||
cmd: List[str],
|
cmd: List[str],
|
||||||
|
|
||||||
|
**params_kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.__cap_pin = (gpio.set_output(cap_pin) if cap_pin >= 0 else -1)
|
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.__shutdown_delay = shutdown_delay
|
||||||
self.__state_poll = state_poll
|
self.__state_poll = state_poll
|
||||||
|
|
||||||
self.__params = {
|
|
||||||
"quality": quality,
|
|
||||||
"desired_fps": desired_fps,
|
|
||||||
}
|
|
||||||
self.__max_fps = max_fps
|
|
||||||
|
|
||||||
assert port or unix_path
|
assert port or unix_path
|
||||||
self.__host = host
|
self.__host = host
|
||||||
self.__port = port
|
self.__port = port
|
||||||
@ -105,6 +160,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
self.__cmd = cmd
|
self.__cmd = cmd
|
||||||
|
|
||||||
|
self.__params = _StreamerParams(**params_kwargs)
|
||||||
|
|
||||||
self.__stop_task: Optional[asyncio.Task] = None
|
self.__stop_task: Optional[asyncio.Task] = None
|
||||||
self.__stop_wip = False
|
self.__stop_wip = False
|
||||||
|
|
||||||
@ -183,16 +240,10 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
def set_params(self, params: Dict) -> None:
|
def set_params(self, params: Dict) -> None:
|
||||||
assert not self.__streamer_task
|
assert not self.__streamer_task
|
||||||
self.__params = {
|
return self.__params.set_params(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),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_params(self) -> Dict:
|
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"]
|
del snapshot["data"]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"limits": {"max_fps": self.__max_fps},
|
"limits": self.__params.get_limits(),
|
||||||
"params": self.__params,
|
"params": self.__params.get_params(),
|
||||||
"snapshot": {"saved": snapshot},
|
"snapshot": {"saved": snapshot},
|
||||||
"state": state,
|
"state": state,
|
||||||
|
"features": self.__params.get_features(),
|
||||||
}
|
}
|
||||||
|
|
||||||
async def poll_state(self) -> AsyncGenerator[Dict, None]:
|
async def poll_state(self) -> AsyncGenerator[Dict, None]:
|
||||||
@ -370,11 +422,11 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception as err:
|
except Exception:
|
||||||
if self.__streamer_proc:
|
if self.__streamer_proc:
|
||||||
logger.exception("Unexpected streamer error: pid=%d", self.__streamer_proc.pid)
|
logger.exception("Unexpected streamer error: pid=%d", self.__streamer_proc.pid)
|
||||||
else:
|
else:
|
||||||
logger.exception("Can't start streamer: %s", err)
|
logger.exception("Can't start streamer")
|
||||||
await self.__kill_streamer_proc()
|
await self.__kill_streamer_proc()
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
@ -386,7 +438,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
|
|||||||
port=self.__port,
|
port=self.__port,
|
||||||
unix=self.__unix_path,
|
unix=self.__unix_path,
|
||||||
process_name_prefix=self.__process_name_prefix,
|
process_name_prefix=self.__process_name_prefix,
|
||||||
**self.__params,
|
**self.__params.get_params(),
|
||||||
)
|
)
|
||||||
for part in self.__cmd
|
for part in self.__cmd
|
||||||
]
|
]
|
||||||
|
|||||||
@ -24,8 +24,10 @@ from typing import Any
|
|||||||
|
|
||||||
from ..keyboard.mappings import KEYMAP
|
from ..keyboard.mappings import KEYMAP
|
||||||
|
|
||||||
|
from . import raise_error
|
||||||
from . import check_string_in_list
|
from . import check_string_in_list
|
||||||
|
|
||||||
|
from .basic import valid_stripped_string_not_empty
|
||||||
from .basic import valid_number
|
from .basic import valid_number
|
||||||
|
|
||||||
from .os import valid_printable_filename
|
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"))
|
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:
|
def valid_hid_key(arg: Any) -> str:
|
||||||
return check_string_in_list(arg, "HID key", KEYMAP, lower=False)
|
return check_string_in_list(arg, "HID key", KEYMAP, lower=False)
|
||||||
|
|||||||
@ -142,6 +142,7 @@ def test_fail__valid_float_f01(arg: Any) -> None:
|
|||||||
# =====
|
# =====
|
||||||
@pytest.mark.parametrize("arg, retval", [
|
@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"]),
|
("a b c", ["a", "b", "c"]),
|
||||||
(["a", "b", "c"], ["a", "b", "c"]),
|
(["a", "b", "c"], ["a", "b", "c"]),
|
||||||
("", []),
|
("", []),
|
||||||
|
|||||||
@ -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_log_seek
|
||||||
from kvmd.validators.kvm import valid_stream_quality
|
from kvmd.validators.kvm import valid_stream_quality
|
||||||
from kvmd.validators.kvm import valid_stream_fps
|
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_key
|
||||||
from kvmd.validators.kvm import valid_hid_mouse_move
|
from kvmd.validators.kvm import valid_hid_mouse_move
|
||||||
from kvmd.validators.kvm import valid_hid_mouse_button
|
from kvmd.validators.kvm import valid_hid_mouse_button
|
||||||
@ -104,6 +105,20 @@ def test_fail__valid_stream_fps(arg: Any) -> None:
|
|||||||
print(valid_stream_fps(arg))
|
print(valid_stream_fps(arg))
|
||||||
|
|
||||||
|
|
||||||
|
# =====
|
||||||
|
@pytest.mark.parametrize("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()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("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:
|
def test_ok__valid_hid_key() -> None:
|
||||||
for key in KEYMAP:
|
for key in KEYMAP:
|
||||||
|
|||||||
@ -142,11 +142,13 @@
|
|||||||
<button data-force-hide-menu id="show-keyboard-button">• Show keyboard</button>
|
<button data-force-hide-menu id="show-keyboard-button">• Show keyboard</button>
|
||||||
<button data-force-hide-menu id="show-about-button">• Show about</button>
|
<button data-force-hide-menu id="show-about-button">• Show about</button>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<div id="stream-quality" class="feature-disabled">
|
||||||
<div class="menu-item-content-text">
|
<hr>
|
||||||
Stream quality: <span id="stream-quality-value">80%</span>
|
<div class="menu-item-content-text">
|
||||||
<div class="stream-slider-box">
|
Stream quality: <span id="stream-quality-value">80%</span>
|
||||||
<input disabled type="range" id="stream-quality-slider" class="slider" />
|
<div class="stream-slider-box">
|
||||||
|
<input disabled type="range" id="stream-quality-slider" class="slider" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@ -67,6 +67,10 @@ export function Streamer() {
|
|||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
|
||||||
self.setState = function(state) {
|
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) {
|
if (state && state.state) {
|
||||||
let max_fps = state.limits.max_fps;
|
let max_fps = state.limits.max_fps;
|
||||||
state = state.state;
|
state = state.state;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user