libonvif 4.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
libonvif/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,302 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Optional
5
+ from lxml import etree
6
+ from libonvif.utils.xml import text, int_text, bool_text, NS
7
+ from .ptz import PTZPreset, PresetTour, PresetTourOptions
8
+ from .event import EventServiceCapabilities, EventProperties
9
+ from .device_io import DeviceIOServiceCapabilities, RelayOutput
10
+
11
+ @dataclass
12
+ class OnvifVersion:
13
+ major: int
14
+ minor: int
15
+
16
+ def __str__(self) -> str:
17
+ return f"{self.major}.{self.minor}"
18
+
19
+ @dataclass
20
+ class AnalyticsCapabilities:
21
+ xaddr: Optional[str] = None
22
+ rule_support: Optional[bool] = None
23
+ analytics_module_support: Optional[bool] = None
24
+
25
+ @dataclass
26
+ class NetworkCapabilities:
27
+ ip_filter: Optional[bool] = None
28
+ zero_configuration: Optional[bool] = None
29
+ ip_version6: Optional[bool] = None
30
+ dyn_dns: Optional[bool] = None
31
+ dot11_configuration: Optional[bool] = None
32
+
33
+ @dataclass
34
+ class SystemCapabilities:
35
+ discovery_resolve: Optional[bool] = None
36
+ discovery_bye: Optional[bool] = None
37
+ remote_discovery: Optional[bool] = None
38
+ system_backup: Optional[bool] = None
39
+ system_logging: Optional[bool] = None
40
+ firmware_upgrade: Optional[bool] = None
41
+ supported_versions: list[OnvifVersion] = field(default_factory=list)
42
+ http_firmware_upgrade: Optional[bool] = None
43
+ http_system_backup: Optional[bool] = None
44
+ http_system_logging: Optional[bool] = None
45
+ http_support_information: Optional[bool] = None
46
+
47
+ @dataclass
48
+ class IOCapabilities:
49
+ input_connectors: Optional[int] = None
50
+ relay_outputs: Optional[int] = None
51
+ auxiliary: Optional[bool] = None
52
+
53
+ @dataclass
54
+ class SecurityCapabilities:
55
+ tls_1_0: Optional[bool] = None
56
+ tls_1_1: Optional[bool] = None
57
+ tls_1_2: Optional[bool] = None
58
+ onboard_key_generation: Optional[bool] = None
59
+ access_policy_config: Optional[bool] = None
60
+ x509_token: Optional[bool] = None
61
+ saml_token: Optional[bool] = None
62
+ kerberos_token: Optional[bool] = None
63
+ rel_token: Optional[bool] = None
64
+ dot1x: Optional[bool] = None
65
+ supported_eap_method: Optional[int] = None
66
+ remote_user_handling: Optional[bool] = None
67
+
68
+ @dataclass
69
+ class DeviceCapabilities:
70
+ xaddr: Optional[str] = None
71
+ network: NetworkCapabilities = field(default_factory=NetworkCapabilities)
72
+ system: SystemCapabilities = field(default_factory=SystemCapabilities)
73
+ io: IOCapabilities = field(default_factory=IOCapabilities)
74
+ security: SecurityCapabilities = field(default_factory=SecurityCapabilities)
75
+
76
+ @dataclass
77
+ class EventsCapabilities:
78
+ xaddr: Optional[str] = None
79
+ ws_subscription_policy_support: Optional[bool] = None
80
+ ws_pull_point_support: Optional[bool] = None
81
+ ws_pausable_subscription_manager_interface_support: Optional[bool] = None
82
+ service_capabilities: EventServiceCapabilities = field(default_factory=EventServiceCapabilities)
83
+
84
+ @dataclass
85
+ class ImagingCapabilities:
86
+ xaddr: Optional[str] = None
87
+
88
+ @dataclass
89
+ class StreamingCapabilities:
90
+ rtp_multicast: Optional[bool] = None
91
+ rtp_tcp: Optional[bool] = None
92
+ rtp_rtsp_tcp: Optional[bool] = None
93
+
94
+ @dataclass
95
+ class MediaCapabilities:
96
+ xaddr: Optional[str] = None
97
+ streaming: StreamingCapabilities = field(default_factory=StreamingCapabilities)
98
+ maximum_number_of_profiles: Optional[int] = None
99
+
100
+ @dataclass
101
+ class PTZCapabilities:
102
+ xaddr: Optional[str] = None
103
+
104
+ @dataclass
105
+ class DeviceIOCapabilities:
106
+ xaddr: Optional[str] = None
107
+ video_sources: Optional[int] = None
108
+ video_outputs: Optional[int] = None
109
+ audio_sources: Optional[int] = None
110
+ audio_outputs: Optional[int] = None
111
+ relay_outputs: Optional[int] = None
112
+ service_capabilities: DeviceIOServiceCapabilities = field(default_factory=DeviceIOServiceCapabilities)
113
+
114
+ @dataclass
115
+ class TelexCapabilities:
116
+ xaddr: Optional[str] = None
117
+ time_osd_support: Optional[bool] = None
118
+ title_osd_support: Optional[bool] = None
119
+ ptz_3d_zoom_support: Optional[bool] = None
120
+ ptz_aux_switch_support: Optional[bool] = None
121
+ motion_detector_support: Optional[bool] = None
122
+ tamper_detector_support: Optional[bool] = None
123
+
124
+ @dataclass
125
+ class Capabilities:
126
+ analytics: Optional[AnalyticsCapabilities] = None
127
+ device: Optional[DeviceCapabilities] = None
128
+ events: Optional[EventsCapabilities] = None
129
+ imaging: Optional[ImagingCapabilities] = None
130
+ media: Optional[MediaCapabilities] = None
131
+ ptz: Optional[PTZCapabilities] = None
132
+ device_io: Optional[DeviceIOCapabilities] = None
133
+ telex: Optional[TelexCapabilities] = None
134
+
135
+ def parse_capabilities_response(xml: str) -> Capabilities:
136
+ if not xml: return
137
+ root = etree.fromstring(xml.encode('utf-8'))
138
+
139
+ caps = root.find(".//tds:GetCapabilitiesResponse/tds:Capabilities", NS)
140
+ if caps is None:
141
+ raise ValueError("Could not find tds:GetCapabilitiesResponse/tds:Capabilities")
142
+
143
+ result = Capabilities()
144
+
145
+ analytics = caps.find("tt:Analytics", NS)
146
+ if analytics is not None:
147
+ result.analytics = AnalyticsCapabilities(
148
+ xaddr=text(analytics, "tt:XAddr"),
149
+ rule_support=bool_text(analytics, "tt:RuleSupport"),
150
+ analytics_module_support=bool_text(analytics, "tt:AnalyticsModuleSupport"),
151
+ )
152
+
153
+ device = caps.find("tt:Device", NS)
154
+ if device is not None:
155
+ d = DeviceCapabilities(
156
+ xaddr=text(device, "tt:XAddr"),
157
+ )
158
+
159
+ net = device.find("tt:Network", NS)
160
+ if net is not None:
161
+ d.network = NetworkCapabilities(
162
+ ip_filter=bool_text(net, "tt:IPFilter"),
163
+ zero_configuration=bool_text(net, "tt:ZeroConfiguration"),
164
+ ip_version6=bool_text(net, "tt:IPVersion6"),
165
+ dyn_dns=bool_text(net, "tt:DynDNS"),
166
+ dot11_configuration=bool_text(
167
+ net, "tt:Extension/tt:Dot11Configuration"
168
+ ),
169
+ )
170
+
171
+ system = device.find("tt:System", NS)
172
+ if system is not None:
173
+ versions: list[OnvifVersion] = []
174
+ for ver in system.findall("tt:SupportedVersions", NS):
175
+ major = int_text(ver, "tt:Major")
176
+ minor = int_text(ver, "tt:Minor")
177
+ if major is not None and minor is not None:
178
+ versions.append(OnvifVersion(major, minor))
179
+
180
+ d.system = SystemCapabilities(
181
+ discovery_resolve=bool_text(system, "tt:DiscoveryResolve"),
182
+ discovery_bye=bool_text(system, "tt:DiscoveryBye"),
183
+ remote_discovery=bool_text(system, "tt:RemoteDiscovery"),
184
+ system_backup=bool_text(system, "tt:SystemBackup"),
185
+ system_logging=bool_text(system, "tt:SystemLogging"),
186
+ firmware_upgrade=bool_text(system, "tt:FirmwareUpgrade"),
187
+ supported_versions=versions,
188
+ http_firmware_upgrade=bool_text(
189
+ system, "tt:Extension/tt:HttpFirmwareUpgrade"
190
+ ),
191
+ http_system_backup=bool_text(
192
+ system, "tt:Extension/tt:HttpSystemBackup"
193
+ ),
194
+ http_system_logging=bool_text(
195
+ system, "tt:Extension/tt:HttpSystemLogging"
196
+ ),
197
+ http_support_information=bool_text(
198
+ system, "tt:Extension/tt:HttpSupportInformation"
199
+ ),
200
+ )
201
+
202
+ io = device.find("tt:IO", NS)
203
+ if io is not None:
204
+ d.io = IOCapabilities(
205
+ input_connectors=int_text(io, "tt:InputConnectors"),
206
+ relay_outputs=int_text(io, "tt:RelayOutputs"),
207
+ auxiliary=bool_text(io, "tt:Extension/tt:Auxiliary"),
208
+ )
209
+
210
+ sec = device.find("tt:Security", NS)
211
+ if sec is not None:
212
+ d.security = SecurityCapabilities(
213
+ tls_1_1=bool_text(sec, "tt:TLS1.1"),
214
+ tls_1_2=bool_text(sec, "tt:TLS1.2"),
215
+ onboard_key_generation=bool_text(sec, "tt:OnboardKeyGeneration"),
216
+ access_policy_config=bool_text(sec, "tt:AccessPolicyConfig"),
217
+ x509_token=bool_text(sec, "tt:X.509Token"),
218
+ saml_token=bool_text(sec, "tt:SAMLToken"),
219
+ kerberos_token=bool_text(sec, "tt:KerberosToken"),
220
+ rel_token=bool_text(sec, "tt:RELToken"),
221
+ tls_1_0=bool_text(sec, "tt:Extension/tt:TLS1.0"),
222
+ dot1x=bool_text(sec, "tt:Extension/tt:Extension/tt:Dot1X"),
223
+ supported_eap_method=int_text(
224
+ sec, "tt:Extension/tt:Extension/tt:SupportedEAPMethod"
225
+ ),
226
+ remote_user_handling=bool_text(
227
+ sec, "tt:Extension/tt:Extension/tt:RemoteUserHandling"
228
+ ),
229
+ )
230
+
231
+ result.device = d
232
+
233
+ events = caps.find("tt:Events", NS)
234
+ if events is not None:
235
+ result.events = EventsCapabilities(
236
+ xaddr=text(events, "tt:XAddr"),
237
+ ws_subscription_policy_support=bool_text(
238
+ events, "tt:WSSubscriptionPolicySupport"
239
+ ),
240
+ ws_pull_point_support=bool_text(events, "tt:WSPullPointSupport"),
241
+ ws_pausable_subscription_manager_interface_support=bool_text(
242
+ events, "tt:WSPausableSubscriptionManagerInterfaceSupport"
243
+ ),
244
+ )
245
+
246
+ imaging = caps.find("tt:Imaging", NS)
247
+ if imaging is not None:
248
+ result.imaging = ImagingCapabilities(
249
+ xaddr=text(imaging, "tt:XAddr"),
250
+ )
251
+
252
+ media = caps.find("tt:Media", NS)
253
+ if media is not None:
254
+ result.media = MediaCapabilities(
255
+ xaddr=text(media, "tt:XAddr"),
256
+ streaming=StreamingCapabilities(
257
+ rtp_multicast=bool_text(
258
+ media, "tt:StreamingCapabilities/tt:RTPMulticast"
259
+ ),
260
+ rtp_tcp=bool_text(
261
+ media, "tt:StreamingCapabilities/tt:RTP_TCP"
262
+ ),
263
+ rtp_rtsp_tcp=bool_text(
264
+ media, "tt:StreamingCapabilities/tt:RTP_RTSP_TCP"
265
+ ),
266
+ ),
267
+ maximum_number_of_profiles=int_text(
268
+ media,
269
+ "tt:Extension/tt:ProfileCapabilities/tt:MaximumNumberOfProfiles",
270
+ ),
271
+ )
272
+
273
+ ptz = caps.find("tt:PTZ", NS)
274
+ if ptz is not None:
275
+ result.ptz = PTZCapabilities(
276
+ xaddr=text(ptz, "tt:XAddr"),
277
+ )
278
+
279
+ device_io = caps.find("tt:Extension/tt:DeviceIO", NS)
280
+ if device_io is not None:
281
+ result.device_io = DeviceIOCapabilities(
282
+ xaddr=text(device_io, "tt:XAddr"),
283
+ video_sources=int_text(device_io, "tt:VideoSources"),
284
+ video_outputs=int_text(device_io, "tt:VideoOutputs"),
285
+ audio_sources=int_text(device_io, "tt:AudioSources"),
286
+ audio_outputs=int_text(device_io, "tt:AudioOutputs"),
287
+ relay_outputs=int_text(device_io, "tt:RelayOutputs"),
288
+ )
289
+
290
+ telex = caps.find("tt:Extension/tt:Extensions/tt:TelexCapabilities", NS)
291
+ if telex is not None:
292
+ result.telex = TelexCapabilities(
293
+ xaddr=text(telex, "tt:XAddr"),
294
+ time_osd_support=bool_text(telex, "tt:TimeOSDSupport"),
295
+ title_osd_support=bool_text(telex, "tt:TitleOSDSupport"),
296
+ ptz_3d_zoom_support=bool_text(telex, "tt:PTZ3DZoomSupport"),
297
+ ptz_aux_switch_support=bool_text(telex, "tt:PTZAuxSwitchSupport"),
298
+ motion_detector_support=bool_text(telex, "tt:MotionDetectorSupport"),
299
+ tamper_detector_support=bool_text(telex, "tt:TamperDetectorSupport"),
300
+ )
301
+
302
+ return result
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Optional
5
+ from lxml import etree
6
+ from libonvif.utils.xml import text, int_text, bool_text, NS
7
+
8
+ @dataclass
9
+ class NTPInformation:
10
+ from_dhcp: Optional[bool] = None
11
+ ntp_from_dhcp: list[str] = field(default_factory=list)
12
+ ntp_manual: list[str] = field(default_factory=list)
13
+
14
+ @dataclass
15
+ class Time:
16
+ hour: Optional[int] = None
17
+ minute: Optional[int] = None
18
+ second: Optional[int] = None
19
+
20
+ @dataclass
21
+ class Date:
22
+ year: Optional[int] = None
23
+ month: Optional[int] = None
24
+ day: Optional[int] = None
25
+
26
+ @dataclass
27
+ class DateTime:
28
+ time: Time = field(default_factory=Time)
29
+ date: Date = field(default_factory=Date)
30
+
31
+ @dataclass
32
+ class TimeZone:
33
+ tz: Optional[str] = None
34
+
35
+ @dataclass
36
+ class SystemDateAndTime:
37
+ date_time_type: Optional[str] = None
38
+ daylight_savings: Optional[bool] = None
39
+ time_zone: Optional[TimeZone] = None
40
+ utc_date_time: Optional[DateTime] = None
41
+ local_date_time: Optional[DateTime] = None
42
+
43
+ def parse_time(elem: Optional[etree._Element]) -> Time:
44
+ if elem is None:
45
+ return Time()
46
+
47
+ return Time(
48
+ hour=int_text(elem, "tt:Hour"),
49
+ minute=int_text(elem, "tt:Minute"),
50
+ second=int_text(elem, "tt:Second"),
51
+ )
52
+
53
+ def parse_date(elem: Optional[etree._Element]) -> Date:
54
+ if elem is None:
55
+ return Date()
56
+
57
+ return Date(
58
+ year=int_text(elem, "tt:Year"),
59
+ month=int_text(elem, "tt:Month"),
60
+ day=int_text(elem, "tt:Day"),
61
+ )
62
+
63
+ def parse_datetime(elem: Optional[etree._Element]) -> Optional[DateTime]:
64
+ if elem is None:
65
+ return None
66
+
67
+ return DateTime(
68
+ time=parse_time(elem.find("tt:Time", NS)),
69
+ date=parse_date(elem.find("tt:Date", NS)),
70
+ )
71
+
72
+ def parse_timezone(elem: Optional[etree._Element]) -> Optional[TimeZone]:
73
+ if elem is None:
74
+ return None
75
+
76
+ return TimeZone(
77
+ tz=text(elem, "tt:TZ"),
78
+ )
79
+
80
+ def parse_system_date_and_time_response(xml: str) -> SystemDateAndTime:
81
+ if not xml: return
82
+ root = etree.fromstring(xml.encode('utf-8'))
83
+
84
+ elem = root.find(
85
+ ".//tds:GetSystemDateAndTimeResponse/tds:SystemDateAndTime",
86
+ NS,
87
+ )
88
+ if elem is None:
89
+ raise ValueError(
90
+ "Could not find tds:GetSystemDateAndTimeResponse/tds:SystemDateAndTime"
91
+ )
92
+
93
+ return SystemDateAndTime(
94
+ date_time_type=text(elem, "tt:DateTimeType"),
95
+ daylight_savings=bool_text(elem, "tt:DaylightSavings"),
96
+ time_zone=parse_timezone(elem.find("tt:TimeZone", NS)),
97
+ utc_date_time=parse_datetime(elem.find("tt:UTCDateTime", NS)),
98
+ local_date_time=parse_datetime(elem.find("tt:LocalDateTime", NS)),
99
+ )
100
+
101
+ def parse_ip_address(elem):
102
+ return (
103
+ text(elem, "tt:IPv4Address")
104
+ or text(elem, "tt:IPv6Address")
105
+ or text(elem, "tt:DNSname")
106
+ )
107
+
108
+ def parse_ntp_response(xml: str) -> NTPInformation:
109
+ if not xml: return
110
+ root = etree.fromstring(xml.encode('utf-8'))
111
+
112
+ ntp_elem = root.find(".//tds:NTPInformation", NS)
113
+ if ntp_elem is None:
114
+ raise ValueError("Missing NTPInformation")
115
+
116
+ return NTPInformation(
117
+ from_dhcp=bool_text(ntp_elem, "tt:FromDHCP"),
118
+ ntp_from_dhcp=[
119
+ parse_ip_address(e)
120
+ for e in ntp_elem.findall("tt:NTPFromDHCP", NS)
121
+ ],
122
+ ntp_manual=[
123
+ parse_ip_address(e)
124
+ for e in ntp_elem.findall("tt:NTPManual", NS)
125
+ ],
126
+ )
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Optional
5
+ from lxml import etree
6
+ from libonvif.utils.xml import int_attr, text, text_list, bool_text, NS
7
+
8
+ @dataclass
9
+ class RelayOutputOptions:
10
+ token: Optional[str] = None
11
+ modes: list[str] = field(default_factory=list)
12
+ delay_times: list[str] = field(default_factory=list)
13
+ discrete: Optional[bool] = None
14
+
15
+ @dataclass
16
+ class RelayOutputProperties:
17
+ mode: Optional[str] = None
18
+ delay_time: Optional[str] = None
19
+ idle_state: Optional[str] = None
20
+
21
+ @dataclass
22
+ class RelayOutput:
23
+ token: Optional[str] = None
24
+ properties: RelayOutputProperties = field(default_factory=RelayOutputProperties)
25
+ options: RelayOutputOptions = field(default_factory=RelayOutputOptions)
26
+
27
+ @dataclass
28
+ class DeviceIOServiceCapabilities:
29
+ video_sources: Optional[int] = None
30
+ video_outputs: Optional[int] = None
31
+ audio_sources: Optional[int] = None
32
+ audio_outputs: Optional[int] = None
33
+ relay_outputs: Optional[int] = None
34
+ serial_ports: Optional[int] = None
35
+ digital_inputs: Optional[int] = None
36
+
37
+
38
+ def parse_deviceio_service_capabilities_response(xml: str) -> Optional[DeviceIOServiceCapabilities]:
39
+ if not xml: return
40
+ root = etree.fromstring(xml.encode("utf-8"))
41
+
42
+ result = root.xpath(
43
+ ".//tmd:GetServiceCapabilitiesResponse/tmd:Capabilities",
44
+ namespaces=NS,
45
+ )
46
+
47
+ if not result: return
48
+
49
+ cap = result[0]
50
+
51
+ return DeviceIOServiceCapabilities(
52
+ video_sources=int_attr(cap, "VideoSources"),
53
+ video_outputs=int_attr(cap, "VideoOutputs"),
54
+ audio_sources=int_attr(cap, "AudioSources"),
55
+ audio_outputs=int_attr(cap, "AudioOutputs"),
56
+ relay_outputs=int_attr(cap, "RelayOutputs"),
57
+ serial_ports=int_attr(cap, "SerialPorts"),
58
+ digital_inputs=int_attr(cap, "DigitalInputs"),
59
+ )
60
+
61
+ def parse_get_relay_outputs_response(xml: str) -> list[RelayOutput]:
62
+ if not xml:
63
+ return []
64
+
65
+ root = etree.fromstring(xml.encode("utf-8"))
66
+
67
+ outputs: list[RelayOutput] = []
68
+
69
+ for output_el in root.xpath(
70
+ ".//*[local-name()='GetRelayOutputsResponse']/*[local-name()='RelayOutputs']"
71
+ ):
72
+ outputs.append(
73
+ RelayOutput(
74
+ token=output_el.get("token"),
75
+ properties=RelayOutputProperties(
76
+ mode=text(output_el, "./tt:Properties/tt:Mode"),
77
+ delay_time=text(output_el, "./tt:Properties/tt:DelayTime"),
78
+ idle_state=text(output_el, "./tt:Properties/tt:IdleState"),
79
+ ),
80
+ )
81
+ )
82
+
83
+ return outputs
84
+
85
+ def parse_get_relay_output_options_response(xml: str) -> RelayOutputOptions:
86
+ if not xml:
87
+ return RelayOutputOptions()
88
+
89
+ root = etree.fromstring(xml.encode("utf-8"))
90
+
91
+ result = root.xpath(
92
+ ".//tmd:GetRelayOutputOptionsResponse/tmd:RelayOutputOptions",
93
+ namespaces=NS,
94
+ )
95
+
96
+ if not result:
97
+ return RelayOutputOptions()
98
+
99
+ options_el = result[0]
100
+
101
+ delay_time_text = text(options_el, "./tmd:DelayTimes")
102
+
103
+ return RelayOutputOptions(
104
+ token=options_el.get("token"),
105
+ modes=text_list(options_el, "./tmd:Mode"),
106
+ delay_times=delay_time_text.split() if delay_time_text else [],
107
+ discrete=bool_text(options_el, "./tmd:Discrete"),
108
+ )
109
+