diff --git a/xpra/codecs/codec_constants.py b/xpra/codecs/codec_constants.py index dcab4ab289..6210f52f28 100644 --- a/xpra/codecs/codec_constants.py +++ b/xpra/codecs/codec_constants.py @@ -6,6 +6,7 @@ import os from weakref import WeakSet +from dataclasses import dataclass, field from typing import Any from xpra.util import envint, typedict @@ -130,46 +131,29 @@ class CodecStateException(Exception): pass +@dataclass(kw_only=True) class _codec_spec: - def __init__(self, codec_class, codec_type : str="", - quality:int=50, speed:int=50, - size_efficiency:int=50, - setup_cost:int=50, cpu_cost:int=100, gpu_cost:int=0, - min_w:int=1, min_h:int=1, max_w:int=4*1024, max_h:int=4*1024, - can_scale:bool=False, - score_boost:int=0, - width_mask:int=0xFFFF, height_mask:int=0xFFFF): - self.codec_class = codec_class #ie: xpra.codecs.x264.encoder.Encoder - self.codec_type : str = codec_type #ie: "nvenc" - self.quality : int = quality - self.speed : int = speed - self.size_efficiency : int = size_efficiency - self.setup_cost : int = setup_cost - self.cpu_cost : int = cpu_cost - self.gpu_cost : int = gpu_cost - self.score_boost : int = score_boost - self.min_w : int = min_w - self.min_h : int = min_h - self.max_w : int = max_w - self.max_h : int = max_h - self.width_mask : int = width_mask - self.height_mask : int = height_mask - self.can_scale : bool = can_scale - self.max_instances : int = 0 - self._exported_fields = [ - "codec_class", "codec_type", - "quality", "speed", - "setup_cost", "cpu_cost", "gpu_cost", "score_boost", - "min_w", "min_h", "max_w", "max_h", - "width_mask", "height_mask", - "can_scale", - "max_instances", - ] - #not exported: - self.instances : WeakSet[Any] = WeakSet() - self._all_fields = list(self._exported_fields)+["instances"] - + codec_class : callable + codec_type : str + quality : int = 50 + speed : int = 50 + size_efficiency : int = 50 + setup_cost : int = 50 + cpu_cost : int = 100 + gpu_cost : int = 0 + min_w : int = 1 + min_h : int = 1 + max_w : int = 4 * 1024 + max_h : int = 4 * 1024 + can_scale : bool = False + score_boost : int = 0 + width_mask : int = 0xFFFF + height_mask : int = 0xFFFF + max_instances : int = 0 + skipped_fields : tuple[str] = ("instances", "skipped_fields", ) + # not exported: + instances : WeakSet[Any] = field(default_factory=WeakSet) def make_instance(self) -> object: # pylint: disable=import-outside-toplevel @@ -200,9 +184,9 @@ def get_instance_count(self) -> int: return len(self.instances) def to_dict(self) -> dict[str,Any]: - d = {} - for k in self._exported_fields: - d[k] = getattr(self, k) + v = self.asdict() + for k in self.skipped_fields: + v.pop(k, None) return d def get_runtime_factor(self) -> float: @@ -220,28 +204,23 @@ def get_runtime_factor(self) -> float: return 1.0 +@dataclass(kw_only=True) class video_spec(_codec_spec): - def __init__(self, encoding : str, input_colorspace : str, output_colorspaces : tuple, has_lossless_mode : bool, - codec_class, codec_type : str, min_w:int=2, min_h:int=2, **kwargs): - self.encoding : str = encoding #ie: "h264" - self.input_colorspace : str = input_colorspace - self.output_colorspaces = output_colorspaces #ie: ["YUV420P" : "YUV420P", ...] - self.has_lossless_mode : bool = has_lossless_mode - super().__init__(codec_class, codec_type, min_w=min_w, min_h=min_h, **kwargs) - self._exported_fields += ["encoding", "input_colorspace", "output_colorspaces", "has_lossless_mode"] + encoding : str + input_colorspace : str + output_colorspaces : tuple[str,...] # ie: ["YUV420P" : "YUV420P", ...] + has_lossless_mode : bool = False def __repr__(self): return f"{self.codec_type}({self.input_colorspace} to {self.encoding}" +@dataclass(kw_only=True) class csc_spec(_codec_spec): - def __init__(self, input_colorspace:str, output_colorspace:str, codec_class, codec_type:str, **kwargs): - self.input_colorspace : str = input_colorspace - self.output_colorspace : str = output_colorspace - super().__init__(codec_class, codec_type, **kwargs) - self._exported_fields += ["input_colorspace", "output_colorspace"] + input_colorspace: str + output_colorspace: str def __repr__(self): return f"{self.codec_type}({self.input_colorspace} to {self.output_colorspace})" diff --git a/xpra/codecs/csc_cython/colorspace_converter.pyx b/xpra/codecs/csc_cython/colorspace_converter.pyx index 3bc8ac62f0..3eada9d19f 100644 --- a/xpra/codecs/csc_cython/colorspace_converter.pyx +++ b/xpra/codecs/csc_cython/colorspace_converter.pyx @@ -120,8 +120,8 @@ def get_spec(in_colorspace:str, out_colorspace:str): #safer not to try to handle odd dimensions as input: width_mask = height_mask = 0xFFFE #low score as this should be used as fallback only: - return csc_spec(in_colorspace, out_colorspace, - ColorspaceConverter, codec_type=get_type(), + return csc_spec(input_colorspace=in_colorspace, output_colorspace=out_colorspace, + codec_class=ColorspaceConverter, codec_type=get_type(), quality=50, speed=0, setup_cost=0, min_w=2, min_h=2, max_w=16*1024, max_h=16*1024, can_scale=can_scale, diff --git a/xpra/codecs/jpeg/encoder.pyx b/xpra/codecs/jpeg/encoder.pyx index 5533e648dd..af78f50962 100644 --- a/xpra/codecs/jpeg/encoder.pyx +++ b/xpra/codecs/jpeg/encoder.pyx @@ -142,7 +142,8 @@ def get_specs(encoding, colorspace): height_mask=0xFFFE return ( video_spec( - encoding, input_colorspace=colorspace, output_colorspaces=(colorspace, ), has_lossless_mode=False, + encoding=encoding, input_colorspace=colorspace, output_colorspaces=(colorspace, ), + has_lossless_mode=False, codec_class=Encoder, codec_type="jpeg", setup_cost=0, cpu_cost=100, gpu_cost=0, min_w=16, min_h=16, max_w=16*1024, max_h=16*1024, diff --git a/xpra/codecs/libyuv/colorspace_converter.pyx b/xpra/codecs/libyuv/colorspace_converter.pyx index 03934f2505..838cf556d5 100644 --- a/xpra/codecs/libyuv/colorspace_converter.pyx +++ b/xpra/codecs/libyuv/colorspace_converter.pyx @@ -157,8 +157,8 @@ def get_output_colorspaces(input_colorspace): def get_spec(in_colorspace, out_colorspace): assert in_colorspace in COLORSPACES, "invalid input colorspace: %s (must be one of %s)" % (in_colorspace, COLORSPACES) assert out_colorspace in COLORSPACES[in_colorspace], "invalid output colorspace: %s (must be one of %s)" % (out_colorspace, COLORSPACES[in_colorspace]) - return csc_spec(in_colorspace, out_colorspace, - ColorspaceConverter, codec_type=get_type(), + return csc_spec(input_colorspace=in_colorspace, output_colorspace=out_colorspace, + codec_class=ColorspaceConverter, codec_type=get_type(), quality=100, speed=100, setup_cost=0, min_w=8, min_h=2, can_scale=in_colorspace!="NV12", max_w=MAX_WIDTH, max_h=MAX_HEIGHT) diff --git a/xpra/codecs/nvidia/nvjpeg/encoder.pyx b/xpra/codecs/nvidia/nvjpeg/encoder.pyx index fe8fa49f69..a8047d22db 100644 --- a/xpra/codecs/nvidia/nvjpeg/encoder.pyx +++ b/xpra/codecs/nvidia/nvjpeg/encoder.pyx @@ -152,7 +152,7 @@ def get_specs(encoding, colorspace): from xpra.codecs.codec_constants import video_spec return ( video_spec( - "jpeg", input_colorspace=colorspace, output_colorspaces=(colorspace, ), + encoding="jpeg", input_colorspace=colorspace, output_colorspaces=(colorspace, ), has_lossless_mode=False, codec_class=Encoder, codec_type="nvjpeg", setup_cost=20, cpu_cost=0, gpu_cost=100, diff --git a/xpra/codecs/webp/encoder.pyx b/xpra/codecs/webp/encoder.pyx index 247f3c8f61..fc00bfb824 100644 --- a/xpra/codecs/webp/encoder.pyx +++ b/xpra/codecs/webp/encoder.pyx @@ -380,7 +380,7 @@ def get_specs(encoding, colorspace): from xpra.codecs.codec_constants import video_spec return ( video_spec( - encoding, input_colorspace=colorspace, output_colorspaces=(colorspace, ), has_lossless_mode=False, + encoding=encoding, input_colorspace=colorspace, output_colorspaces=(colorspace, ), has_lossless_mode=False, codec_class=Encoder, codec_type="webp", setup_cost=0, cpu_cost=100, gpu_cost=0, min_w=16, min_h=16, max_w=4*1024, max_h=4*1024, diff --git a/xpra/server/window/window_source.py b/xpra/server/window/window_source.py index aa4d070ba7..b32368f27d 100644 --- a/xpra/server/window/window_source.py +++ b/xpra/server/window/window_source.py @@ -11,6 +11,7 @@ import threading from math import sqrt, ceil from collections import deque +from dataclasses import dataclass from time import monotonic from contextlib import nullcontext from typing import Callable, Iterable, ContextManager, Any @@ -121,13 +122,13 @@ def get_env_encodings(etype:str, valid_options:Iterable[str]=()) -> tuple[str,.. ui_context = xlog +@dataclass class DelayedRegions: - def __init__(self, damage_time:float, regions:list[rectangle], encoding:str, options:dict|None): - self.expired : bool = False - self.damage_time : float = damage_time - self.regions = regions - self.encoding : str = encoding - self.options : dict = options or {} + damage_time : float + encoding : str + options : dict + regions : list[rectangle] + expired : bool = False def __repr__(self): return "DelayedRegion(time=%i, expired=%s, encoding=%s, regions=%s, options=%s)" % ( @@ -1604,7 +1605,7 @@ def do_damage(self, ww : int, wh : int, x : int, y : int, w : int, h : int, opti target_delay = delay delay = max(0, delay-elapsed) actual_encoding = options.get("encoding", self.encoding) - self._damage_delayed = DelayedRegions(now, regions, actual_encoding, options) + self._damage_delayed = DelayedRegions(damage_time=now, regions=regions, encoding=actual_encoding, options=options) lad = (now, delay) self.batch_config.last_delays.append(lad) self.batch_config.last_delay = lad @@ -2400,7 +2401,7 @@ def full_quality_refresh(self, damage_options) -> None: #just refresh the whole window: regions = [rectangle(0, 0, w, h)] now = monotonic() - damage = DelayedRegions(now, regions, encoding, new_options) + damage = DelayedRegions(damage_time=now, regions=regions, encoding=encoding, options=new_options) self.send_delayed_regions(damage) def get_refresh_options(self) -> dict[str,Any]: