From 04a2256a80d07837fb3a206edc7c4f0a670c969b Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Thu, 19 Oct 2023 18:04:59 +0200 Subject: [PATCH] Fix image --- axis/vapix/interfaces/param_cgi.py | 6 + axis/vapix/models/param_cgi.py | 136 ++++++++++- tests/test_param_cgi.py | 374 +++++++++++++++-------------- 3 files changed, 327 insertions(+), 189 deletions(-) diff --git a/axis/vapix/interfaces/param_cgi.py b/axis/vapix/interfaces/param_cgi.py index c87bd0aa..55b6e5e1 100644 --- a/axis/vapix/interfaces/param_cgi.py +++ b/axis/vapix/interfaces/param_cgi.py @@ -11,6 +11,7 @@ from ..models.param_cgi import ( BrandParam, BrandT, + ImageParam, Param, PropertyParam, StreamProfileParam, @@ -166,6 +167,11 @@ async def update_image(self) -> None: """Update image group of parameters.""" await self.update(IMAGE) + @property + def image_params(self) -> ImageParam: + """Provide brand parameters.""" + return ImageParam.decode(self[IMAGE].raw) + @property def image_sources(self) -> dict: """Image source information.""" diff --git a/axis/vapix/models/param_cgi.py b/axis/vapix/models/param_cgi.py index 63fffaeb..cf521df7 100644 --- a/axis/vapix/models/param_cgi.py +++ b/axis/vapix/models/param_cgi.py @@ -25,6 +25,54 @@ class BrandT(TypedDict): WebURL: str +class ImageParamT(TypedDict): + """Represent an image object.""" + + Enabled: str + Name: str + Source: str + Appearance_ColorEnabled: str + Appearance_Compression: str + Appearance_MirrorEnabled: str + Appearance_Resolution: str + Appearance_Rotation: str + MPEG_Complexity: str + MPEG_ConfigHeaderInterval: str + MPEG_FrameSkipMode: str + MPEG_ICount: str + MPEG_PCount: str + MPEG_UserDataEnabled: str + MPEG_UserDataInterval: str + MPEG_ZChromaQPMode: str + MPEG_ZFpsMode: str + MPEG_ZGopMode: str + MPEG_ZMaxGopLength: str + MPEG_ZMinFps: str + MPEG_ZStrength: str + MPEG_H264_Profile: str + MPEG_H264_PSEnabled: str + Overlay_Enabled: str + Overlay_XPos: str + Overlay_YPos: str + Overlay_MaskWindows_Color: str + RateControl_MaxBitrate: str + RateControl_Mode: str + RateControl_Priority: str + RateControl_TargetBitrate: str + SizeControl_MaxFrameSize: str + Stream_Duration: str + Stream_FPS: str + Stream_NbrOfFrames: str + Text_BGColor: str + Text_ClockEnabled: str + Text_Color: str + Text_DateEnabled: str + Text_Position: str + Text_String: str + Text_TextEnabled: str + Text_TextSize: str + + class PropertyT(TypedDict): """Represent a property object.""" @@ -51,7 +99,7 @@ def process_dynamic_group( prefix: str, attributes: tuple[str, ...], group_range: range, -) -> dict[str, dict[str, str]]: +) -> dict[int, dict[str, bool | int | str]]: """Convert raw dynamic groups to a proper dictionary. raw_group: self[group] @@ -61,7 +109,7 @@ def process_dynamic_group( """ dynamic_group = {} for index in group_range: - item = {} + item: dict[str, bool | int | str] = {} for attribute in attributes: parameter = f"{prefix}{index}.{attribute}" # Support.S0.AbsoluteZoom @@ -69,10 +117,21 @@ def process_dynamic_group( if parameter not in raw_group: continue - item[attribute] = raw_group[parameter] + parameter_value = raw_group[parameter] + + if parameter_value in ("true", "false", "yes", "no"): # Boolean values + item[attribute] = parameter_value in ("true", "yes") + + elif parameter_value.lstrip("-").isdigit(): # Positive/negative values + item[attribute] = int(parameter_value) + + else: + item[attribute] = parameter_value + + # item[attribute] = raw_group[parameter] if item: - dynamic_group[str(index)] = item + dynamic_group[index] = item return dynamic_group @@ -133,6 +192,69 @@ def decode(cls, data: BrandT) -> Self: ) +@dataclass +class ImageParam(ApiItem): + """Image parameters.""" + + data: dict[int, ImageParamT] + + @classmethod + def decode(cls, data: dict[str, str]) -> Self: + """Decode dictionary to class object.""" + attributes = ( + "Enabled", + "Name", + "Source", + "Appearance.ColorEnabled", + "Appearance.Compression", + "Appearance.MirrorEnabled", + "Appearance.Resolution", + "Appearance.Rotation", + "MPEG.Complexity", + "MPEG.ConfigHeaderInterval", + "MPEG.FrameSkipMode", + "MPEG.ICount", + "MPEG.PCount", + "MPEG.UserDataEnabled", + "MPEG.UserDataInterval", + "MPEG.ZChromaQPMode", + "MPEG.ZFpsMode", + "MPEG.ZGopMode", + "MPEG.ZMaxGopLength", + "MPEG.ZMinFps", + "MPEG.ZStrength", + "MPEG.H264.Profile", + "MPEG.H264.PSEnabled", + "Overlay.Enabled", + "Overlay.XPos", + "Overlay.YPos", + "Overlay.MaskWindows.Color", + "RateControl.MaxBitrate", + "RateControl.Mode", + "RateControl.Priority", + "RateControl.TargetBitrate", + "SizeControl.MaxFrameSize", + "Stream.Duration", + "Stream.FPS", + "Stream.NbrOfFrames", + "Text.BGColor", + "Text.ClockEnabled", + "Text.Color", + "Text.DateEnabled", + "Text.Position", + "Text.String", + "Text.TextEnabled", + "Text.TextSize", + ) + return cls( + id="image", + data=cast( + dict[int, ImageParamT], + process_dynamic_group(data, "I", attributes, range(20)), + ), + ) + + @dataclass class PropertyParam(ApiItem): """Property parameters.""" @@ -238,9 +360,9 @@ def decode(cls, data: dict[str, str]) -> Self: profiles = [ StreamProfile( - id=profile["Name"], - description=profile["Description"], - parameters=profile["Parameters"], + id=str(profile["Name"]), + description=str(profile["Description"]), + parameters=str(profile["Parameters"]), ) for profile in raw_profiles.values() ] diff --git a/tests/test_param_cgi.py b/tests/test_param_cgi.py index a6af102d..5db4a1ef 100644 --- a/tests/test_param_cgi.py +++ b/tests/test_param_cgi.py @@ -49,97 +49,102 @@ async def test_params(params): assert params.weburl == "http://www.axis.com" # Image - assert params.image_sources == { - 0: { - "Enabled": True, - "Name": "View Area 1", - "Source": 0, - "Appearance.ColorEnabled": True, - "Appearance.Compression": 30, - "Appearance.MirrorEnabled": False, - "Appearance.Resolution": "1920x1080", - "Appearance.Rotation": 0, - "MPEG.Complexity": 50, - "MPEG.ConfigHeaderInterval": 1, - "MPEG.FrameSkipMode": "drop", - "MPEG.ICount": 1, - "MPEG.PCount": 31, - "MPEG.UserDataEnabled": False, - "MPEG.UserDataInterval": 1, - "MPEG.ZChromaQPMode": "off", - "MPEG.ZFpsMode": "fixed", - "MPEG.ZGopMode": "fixed", - "MPEG.ZMaxGopLength": 300, - "MPEG.ZMinFps": 0, - "MPEG.ZStrength": 10, - "MPEG.H264.Profile": "high", - "MPEG.H264.PSEnabled": False, - "Overlay.Enabled": False, - "Overlay.XPos": 0, - "Overlay.YPos": 0, - "Overlay.MaskWindows.Color": "black", - "RateControl.MaxBitrate": 0, - "RateControl.Mode": "vbr", - "RateControl.Priority": "framerate", - "RateControl.TargetBitrate": 0, - "SizeControl.MaxFrameSize": 0, - "Stream.Duration": 0, - "Stream.FPS": 0, - "Stream.NbrOfFrames": 0, - "Text.BGColor": "black", - "Text.ClockEnabled": False, - "Text.Color": "white", - "Text.DateEnabled": False, - "Text.Position": "top", - "Text.String": "", - "Text.TextEnabled": False, - "Text.TextSize": "medium", - }, - 1: { - "Enabled": False, - "Name": "View Area 2", - "Source": 0, - "Appearance.ColorEnabled": True, - "Appearance.Compression": 30, - "Appearance.MirrorEnabled": False, - "Appearance.Resolution": "1920x1080", - "Appearance.Rotation": 0, - "MPEG.Complexity": 50, - "MPEG.ConfigHeaderInterval": 1, - "MPEG.FrameSkipMode": "drop", - "MPEG.ICount": 1, - "MPEG.PCount": 31, - "MPEG.UserDataEnabled": False, - "MPEG.UserDataInterval": 1, - "MPEG.ZChromaQPMode": "off", - "MPEG.ZFpsMode": "fixed", - "MPEG.ZGopMode": "fixed", - "MPEG.ZMaxGopLength": 300, - "MPEG.ZMinFps": 0, - "MPEG.ZStrength": 10, - "MPEG.H264.Profile": "high", - "MPEG.H264.PSEnabled": False, - "Overlay.Enabled": False, - "Overlay.XPos": 0, - "Overlay.YPos": 0, - "RateControl.MaxBitrate": 0, - "RateControl.Mode": "vbr", - "RateControl.Priority": "framerate", - "RateControl.TargetBitrate": 0, - "SizeControl.MaxFrameSize": 0, - "Stream.Duration": 0, - "Stream.FPS": 0, - "Stream.NbrOfFrames": 0, - "Text.BGColor": "black", - "Text.ClockEnabled": False, - "Text.Color": "white", - "Text.DateEnabled": False, - "Text.Position": "top", - "Text.String": "", - "Text.TextEnabled": False, - "Text.TextSize": "medium", - }, - } + params.image_params.id == "image" + assert ( + params.image_params.data + == params.image_sources + == { + 0: { + "Enabled": True, + "Name": "View Area 1", + "Source": 0, + "Appearance.ColorEnabled": True, + "Appearance.Compression": 30, + "Appearance.MirrorEnabled": False, + "Appearance.Resolution": "1920x1080", + "Appearance.Rotation": 0, + "MPEG.Complexity": 50, + "MPEG.ConfigHeaderInterval": 1, + "MPEG.FrameSkipMode": "drop", + "MPEG.ICount": 1, + "MPEG.PCount": 31, + "MPEG.UserDataEnabled": False, + "MPEG.UserDataInterval": 1, + "MPEG.ZChromaQPMode": "off", + "MPEG.ZFpsMode": "fixed", + "MPEG.ZGopMode": "fixed", + "MPEG.ZMaxGopLength": 300, + "MPEG.ZMinFps": 0, + "MPEG.ZStrength": 10, + "MPEG.H264.Profile": "high", + "MPEG.H264.PSEnabled": False, + "Overlay.Enabled": False, + "Overlay.XPos": 0, + "Overlay.YPos": 0, + "Overlay.MaskWindows.Color": "black", + "RateControl.MaxBitrate": 0, + "RateControl.Mode": "vbr", + "RateControl.Priority": "framerate", + "RateControl.TargetBitrate": 0, + "SizeControl.MaxFrameSize": 0, + "Stream.Duration": 0, + "Stream.FPS": 0, + "Stream.NbrOfFrames": 0, + "Text.BGColor": "black", + "Text.ClockEnabled": False, + "Text.Color": "white", + "Text.DateEnabled": False, + "Text.Position": "top", + "Text.String": "", + "Text.TextEnabled": False, + "Text.TextSize": "medium", + }, + 1: { + "Enabled": False, + "Name": "View Area 2", + "Source": 0, + "Appearance.ColorEnabled": True, + "Appearance.Compression": 30, + "Appearance.MirrorEnabled": False, + "Appearance.Resolution": "1920x1080", + "Appearance.Rotation": 0, + "MPEG.Complexity": 50, + "MPEG.ConfigHeaderInterval": 1, + "MPEG.FrameSkipMode": "drop", + "MPEG.ICount": 1, + "MPEG.PCount": 31, + "MPEG.UserDataEnabled": False, + "MPEG.UserDataInterval": 1, + "MPEG.ZChromaQPMode": "off", + "MPEG.ZFpsMode": "fixed", + "MPEG.ZGopMode": "fixed", + "MPEG.ZMaxGopLength": 300, + "MPEG.ZMinFps": 0, + "MPEG.ZStrength": 10, + "MPEG.H264.Profile": "high", + "MPEG.H264.PSEnabled": False, + "Overlay.Enabled": False, + "Overlay.XPos": 0, + "Overlay.YPos": 0, + "RateControl.MaxBitrate": 0, + "RateControl.Mode": "vbr", + "RateControl.Priority": "framerate", + "RateControl.TargetBitrate": 0, + "SizeControl.MaxFrameSize": 0, + "Stream.Duration": 0, + "Stream.FPS": 0, + "Stream.NbrOfFrames": 0, + "Text.BGColor": "black", + "Text.ClockEnabled": False, + "Text.Color": "white", + "Text.DateEnabled": False, + "Text.Position": "top", + "Text.String": "", + "Text.TextEnabled": False, + "Text.TextSize": "medium", + }, + } + ) # Ports assert params.nbrofinput == 1 @@ -245,97 +250,101 @@ async def test_update_image(params): assert route.calls.last.request.url.path == "/axis-cgi/param.cgi" assert params.image_nbrofviews == 2 - assert params.image_sources == { - 0: { - "Enabled": True, - "Name": "View Area 1", - "Source": 0, - "Appearance.ColorEnabled": True, - "Appearance.Compression": 30, - "Appearance.MirrorEnabled": False, - "Appearance.Resolution": "1920x1080", - "Appearance.Rotation": 0, - "MPEG.Complexity": 50, - "MPEG.ConfigHeaderInterval": 1, - "MPEG.FrameSkipMode": "drop", - "MPEG.ICount": 1, - "MPEG.PCount": 31, - "MPEG.UserDataEnabled": False, - "MPEG.UserDataInterval": 1, - "MPEG.ZChromaQPMode": "off", - "MPEG.ZFpsMode": "fixed", - "MPEG.ZGopMode": "fixed", - "MPEG.ZMaxGopLength": 300, - "MPEG.ZMinFps": 0, - "MPEG.ZStrength": 10, - "MPEG.H264.Profile": "high", - "MPEG.H264.PSEnabled": False, - "Overlay.Enabled": False, - "Overlay.XPos": 0, - "Overlay.YPos": 0, - "Overlay.MaskWindows.Color": "black", - "RateControl.MaxBitrate": 0, - "RateControl.Mode": "vbr", - "RateControl.Priority": "framerate", - "RateControl.TargetBitrate": 0, - "SizeControl.MaxFrameSize": 0, - "Stream.Duration": 0, - "Stream.FPS": 0, - "Stream.NbrOfFrames": 0, - "Text.BGColor": "black", - "Text.ClockEnabled": False, - "Text.Color": "white", - "Text.DateEnabled": False, - "Text.Position": "top", - "Text.String": "", - "Text.TextEnabled": False, - "Text.TextSize": "medium", - }, - 1: { - "Enabled": False, - "Name": "View Area 2", - "Source": 0, - "Appearance.ColorEnabled": True, - "Appearance.Compression": 30, - "Appearance.MirrorEnabled": False, - "Appearance.Resolution": "1920x1080", - "Appearance.Rotation": 0, - "MPEG.Complexity": 50, - "MPEG.ConfigHeaderInterval": 1, - "MPEG.FrameSkipMode": "drop", - "MPEG.ICount": 1, - "MPEG.PCount": 31, - "MPEG.UserDataEnabled": False, - "MPEG.UserDataInterval": 1, - "MPEG.ZChromaQPMode": "off", - "MPEG.ZFpsMode": "fixed", - "MPEG.ZGopMode": "fixed", - "MPEG.ZMaxGopLength": 300, - "MPEG.ZMinFps": 0, - "MPEG.ZStrength": 10, - "MPEG.H264.Profile": "high", - "MPEG.H264.PSEnabled": False, - "Overlay.Enabled": False, - "Overlay.XPos": 0, - "Overlay.YPos": 0, - "RateControl.MaxBitrate": 0, - "RateControl.Mode": "vbr", - "RateControl.Priority": "framerate", - "RateControl.TargetBitrate": 0, - "SizeControl.MaxFrameSize": 0, - "Stream.Duration": 0, - "Stream.FPS": 0, - "Stream.NbrOfFrames": 0, - "Text.BGColor": "black", - "Text.ClockEnabled": False, - "Text.Color": "white", - "Text.DateEnabled": False, - "Text.Position": "top", - "Text.String": "", - "Text.TextEnabled": False, - "Text.TextSize": "medium", - }, - } + assert ( + params.image_params.data + == params.image_sources + == { + 0: { + "Enabled": True, + "Name": "View Area 1", + "Source": 0, + "Appearance.ColorEnabled": True, + "Appearance.Compression": 30, + "Appearance.MirrorEnabled": False, + "Appearance.Resolution": "1920x1080", + "Appearance.Rotation": 0, + "MPEG.Complexity": 50, + "MPEG.ConfigHeaderInterval": 1, + "MPEG.FrameSkipMode": "drop", + "MPEG.ICount": 1, + "MPEG.PCount": 31, + "MPEG.UserDataEnabled": False, + "MPEG.UserDataInterval": 1, + "MPEG.ZChromaQPMode": "off", + "MPEG.ZFpsMode": "fixed", + "MPEG.ZGopMode": "fixed", + "MPEG.ZMaxGopLength": 300, + "MPEG.ZMinFps": 0, + "MPEG.ZStrength": 10, + "MPEG.H264.Profile": "high", + "MPEG.H264.PSEnabled": False, + "Overlay.Enabled": False, + "Overlay.XPos": 0, + "Overlay.YPos": 0, + "Overlay.MaskWindows.Color": "black", + "RateControl.MaxBitrate": 0, + "RateControl.Mode": "vbr", + "RateControl.Priority": "framerate", + "RateControl.TargetBitrate": 0, + "SizeControl.MaxFrameSize": 0, + "Stream.Duration": 0, + "Stream.FPS": 0, + "Stream.NbrOfFrames": 0, + "Text.BGColor": "black", + "Text.ClockEnabled": False, + "Text.Color": "white", + "Text.DateEnabled": False, + "Text.Position": "top", + "Text.String": "", + "Text.TextEnabled": False, + "Text.TextSize": "medium", + }, + 1: { + "Enabled": False, + "Name": "View Area 2", + "Source": 0, + "Appearance.ColorEnabled": True, + "Appearance.Compression": 30, + "Appearance.MirrorEnabled": False, + "Appearance.Resolution": "1920x1080", + "Appearance.Rotation": 0, + "MPEG.Complexity": 50, + "MPEG.ConfigHeaderInterval": 1, + "MPEG.FrameSkipMode": "drop", + "MPEG.ICount": 1, + "MPEG.PCount": 31, + "MPEG.UserDataEnabled": False, + "MPEG.UserDataInterval": 1, + "MPEG.ZChromaQPMode": "off", + "MPEG.ZFpsMode": "fixed", + "MPEG.ZGopMode": "fixed", + "MPEG.ZMaxGopLength": 300, + "MPEG.ZMinFps": 0, + "MPEG.ZStrength": 10, + "MPEG.H264.Profile": "high", + "MPEG.H264.PSEnabled": False, + "Overlay.Enabled": False, + "Overlay.XPos": 0, + "Overlay.YPos": 0, + "RateControl.MaxBitrate": 0, + "RateControl.Mode": "vbr", + "RateControl.Priority": "framerate", + "RateControl.TargetBitrate": 0, + "SizeControl.MaxFrameSize": 0, + "Stream.Duration": 0, + "Stream.FPS": 0, + "Stream.NbrOfFrames": 0, + "Text.BGColor": "black", + "Text.ClockEnabled": False, + "Text.Color": "white", + "Text.DateEnabled": False, + "Text.Position": "top", + "Text.String": "", + "Text.TextEnabled": False, + "Text.TextSize": "medium", + }, + } + ) @respx.mock @@ -580,6 +589,7 @@ async def test_update_stream_profiles(params): assert profile_params.stream_profiles[1].name == "profile_2" assert profile_params.stream_profiles[1].description == "profile_2_description" assert profile_params.stream_profiles[1].parameters == "videocodec=h265" + assert params.stream_profiles_max_groups == 26 assert len(params.stream_profiles) == 2 assert params.stream_profiles[0].name == "profile_1"