onvif-python 0.0.7__tar.gz → 0.0.9__tar.gz
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.
- {onvif_python-0.0.7/onvif_python.egg-info → onvif_python-0.0.9}/PKG-INFO +100 -55
- {onvif_python-0.0.7 → onvif_python-0.0.9}/README.md +97 -49
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/client.py +113 -43
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/operator.py +3 -3
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/__init__.py +2 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/uplink.py +1 -1
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/utils/__init__.py +2 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/utils/xml_capture.py +43 -8
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/utils/zeep.py +175 -20
- {onvif_python-0.0.7 → onvif_python-0.0.9/onvif_python.egg-info}/PKG-INFO +100 -55
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif_python.egg-info/requires.txt +2 -2
- {onvif_python-0.0.7 → onvif_python-0.0.9}/pyproject.toml +13 -7
- {onvif_python-0.0.7 → onvif_python-0.0.9}/LICENSE.md +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/MANIFEST.in +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/__init__.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/accesscontrol.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/accessrules.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/actionengine.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/analytics/analytics.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/analytics/ruleengine.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/analyticsdevice.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/appmgmt.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/authenticationbehavior.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/credential.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/deviceio.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/devicemgmt.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/display.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/doorcontrol.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/events/events.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/events/notification.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/events/pullpoint.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/events/subscription.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/imaging.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/media.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/media2.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/provisioning.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/ptz.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/receiver.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/recording.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/replay.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/schedule.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/search.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/security/advancedsecurity.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/security/authorizationserver.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/security/dot1x.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/security/jwt.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/security/keystore.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/security/mediasigning.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/security/tlsserver.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/services/thermal.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/utils/error_handlers.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/utils/exceptions.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/utils/wsdl.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/display/display.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/display.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/receiver.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/recording.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/replay.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/schema/common.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/search.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif_python.egg-info/SOURCES.txt +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif_python.egg-info/dependency_links.txt +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/onvif_python.egg-info/top_level.txt +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/setup.cfg +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/setup.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/tests/test_client.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/tests/test_core.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/tests/test_exceptions.py +0 -0
- {onvif_python-0.0.7 → onvif_python-0.0.9}/tests/test_wsdl_map.py +0 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onvif-python
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: A comprehensive and developer-friendly Python library for working with ONVIF-compliant devices
|
|
5
5
|
Author-email: Nirsimetri Technologies® <open@nirsimetri.com>
|
|
6
6
|
Maintainer: Kaburagi
|
|
7
7
|
License-Expression: MIT
|
|
8
|
-
Project-URL: Homepage, https://github.com/nirsimetri/onvif-python
|
|
9
|
-
Project-URL: Issues, https://github.com/nirsimetri/onvif-python/issues
|
|
10
8
|
Project-URL: Documentation, https://deepwiki.com/nirsimetri/onvif-python
|
|
11
|
-
Project-URL: Repository, https://github.com/nirsimetri/onvif-python.git
|
|
12
9
|
Project-URL: Changelog, https://github.com/nirsimetri/onvif-python/releases
|
|
13
10
|
Keywords: onvif,Camera,onvif-python,ip-camera,cctv,soap,surveillance,cctv cameras,soap-client,onvif-client,onvif-library,onvif-specifications
|
|
14
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
@@ -31,8 +28,8 @@ Classifier: Natural Language :: Indonesian
|
|
|
31
28
|
Requires-Python: >=3.9
|
|
32
29
|
Description-Content-Type: text/markdown
|
|
33
30
|
License-File: LICENSE.md
|
|
34
|
-
Requires-Dist: zeep>=4.
|
|
35
|
-
Requires-Dist: requests>=2.
|
|
31
|
+
Requires-Dist: zeep>=4.3.0
|
|
32
|
+
Requires-Dist: requests>=2.32.0
|
|
36
33
|
Provides-Extra: dev
|
|
37
34
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
35
|
Requires-Dist: black; extra == "dev"
|
|
@@ -43,14 +40,11 @@ Dynamic: license-file
|
|
|
43
40
|
|
|
44
41
|
[](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
|
|
45
42
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
46
|
-
[](https://github.com/nirsimetri/onvif-python/releases)
|
|
47
44
|
<br>
|
|
48
|
-
[](https://pypi.org/project/onvif-python/)
|
|
49
46
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
50
47
|
|
|
51
|
-
Are you having trouble finding a Python ONVIF library that supports your device?
|
|
52
|
-
Are you confused about the compatibility of the various ONVIF versions, which are updated every six months?
|
|
53
|
-
|
|
54
48
|
**This project provides a comprehensive and developer-friendly Python library for working with ONVIF-compliant devices.** It is designed to be reliable, easy to integrate, and flexible enough to support a wide range of ONVIF profiles and services.
|
|
55
49
|
|
|
56
50
|
**[ONVIF](https://www.onvif.org) (Open Network Video Interface Forum)** is a global standard for the interface of IP-based physical security products, including network cameras, video recorders, and related systems.
|
|
@@ -154,7 +148,9 @@ Explore more advanced usage and service-specific operations in the [`examples/`]
|
|
|
154
148
|
|
|
155
149
|
The `ONVIFClient` class provides various configuration options to customize the connection behavior, caching strategy, security settings, and debugging capabilities. Below is a detailed description of all available parameters:
|
|
156
150
|
|
|
157
|
-
|
|
151
|
+
|
|
152
|
+
<details>
|
|
153
|
+
<summary><b>Basic Parameters</b></summary>
|
|
158
154
|
|
|
159
155
|
| Parameter | Type | Required | Default | Description |
|
|
160
156
|
|-----------|------|----------|---------|-------------|
|
|
@@ -163,7 +159,10 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
163
159
|
| `username` | `str` | ✅ Yes | - | Username for device authentication (use digest authentication) |
|
|
164
160
|
| `password` | `str` | ✅ Yes | - | Password for device authentication |
|
|
165
161
|
|
|
166
|
-
|
|
162
|
+
</details>
|
|
163
|
+
|
|
164
|
+
<details>
|
|
165
|
+
<summary><b>Connection Parameters</b></summary>
|
|
167
166
|
|
|
168
167
|
| Parameter | Type | Required | Default | Description |
|
|
169
168
|
|-----------|------|----------|---------|-------------|
|
|
@@ -171,33 +170,45 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
171
170
|
| `use_https` | `bool` | ❌ No | `False` | Use HTTPS instead of HTTP for secure communication |
|
|
172
171
|
| `verify_ssl` | `bool` | ❌ No | `True` | Verify SSL certificates when using HTTPS (set to `False` for self-signed certificates) |
|
|
173
172
|
|
|
174
|
-
|
|
173
|
+
</details>
|
|
174
|
+
|
|
175
|
+
<details>
|
|
176
|
+
<summary><b>Caching Parameters</b></summary>
|
|
175
177
|
|
|
176
178
|
| Parameter | Type | Required | Default | Description |
|
|
177
179
|
|-----------|------|----------|---------|-------------|
|
|
178
180
|
| `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see [Cache Modes](#cache-modes) below) |
|
|
179
181
|
|
|
180
|
-
|
|
182
|
+
</details>
|
|
183
|
+
|
|
184
|
+
<details>
|
|
185
|
+
<summary><b>Feature Parameters</b></summary>
|
|
181
186
|
|
|
182
187
|
| Parameter | Type | Required | Default | Description |
|
|
183
188
|
|-----------|------|----------|---------|-------------|
|
|
184
|
-
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better xsd:any field parsing and automatic flattening |
|
|
189
|
+
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better `xsd:any` field parsing and automatic flattening |
|
|
185
190
|
| `capture_xml` | `bool` | ❌ No | `False` | Enable XML capture plugin for debugging SOAP requests/responses |
|
|
186
191
|
|
|
187
|
-
|
|
192
|
+
</details>
|
|
193
|
+
|
|
194
|
+
<details>
|
|
195
|
+
<summary><b>Cache Modes</b></summary>
|
|
188
196
|
|
|
189
197
|
The library provides four caching strategies via the `CacheMode` enum:
|
|
190
198
|
|
|
191
199
|
| Mode | Description | Best For | Startup Speed | Disk Usage | Memory Usage |
|
|
192
200
|
|------|-------------|----------|---------------|------------|--------------|
|
|
193
|
-
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps |
|
|
194
|
-
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools |
|
|
195
|
-
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos |
|
|
196
|
-
| `CacheMode.NONE` | No caching | Testing, debugging |
|
|
201
|
+
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps | Fast | High | High |
|
|
202
|
+
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools | Medium | Medium | Low |
|
|
203
|
+
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos | Medium | None | Medium |
|
|
204
|
+
| `CacheMode.NONE` | No caching | Testing, debugging | Slow | None | Low |
|
|
197
205
|
|
|
198
206
|
**Recommendation:** Use `CacheMode.ALL` (default) for production applications to maximize performance.
|
|
199
207
|
|
|
200
|
-
|
|
208
|
+
</details>
|
|
209
|
+
|
|
210
|
+
<details>
|
|
211
|
+
<summary><b>Usage Examples</b></summary>
|
|
201
212
|
|
|
202
213
|
**Basic Connection:**
|
|
203
214
|
```python
|
|
@@ -218,8 +229,7 @@ client = ONVIFClient(
|
|
|
218
229
|
"admin",
|
|
219
230
|
"password",
|
|
220
231
|
timeout=30,
|
|
221
|
-
use_https=True
|
|
222
|
-
verify_ssl=False # For self-signed certificates
|
|
232
|
+
use_https=True
|
|
223
233
|
)
|
|
224
234
|
```
|
|
225
235
|
|
|
@@ -237,7 +247,7 @@ client = ONVIFClient(
|
|
|
237
247
|
)
|
|
238
248
|
```
|
|
239
249
|
|
|
240
|
-
**No Caching (Testing):**
|
|
250
|
+
**No Caching and No Zeep Patching (Testing):**
|
|
241
251
|
```python
|
|
242
252
|
from onvif import ONVIFClient, CacheMode
|
|
243
253
|
|
|
@@ -296,7 +306,6 @@ if client.xml_plugin:
|
|
|
296
306
|
client.xml_plugin.clear_history()
|
|
297
307
|
```
|
|
298
308
|
|
|
299
|
-
> [!NOTE]
|
|
300
309
|
> **XML Capture Plugin Methods:**
|
|
301
310
|
> - `last_sent_xml` - Get the last SOAP request XML
|
|
302
311
|
> - `last_received_xml` - Get the last SOAP response XML
|
|
@@ -308,8 +317,11 @@ if client.xml_plugin:
|
|
|
308
317
|
> - `save_to_file(request_file, response_file)` - Save XML to files
|
|
309
318
|
> - `clear_history()` - Clear captured history
|
|
310
319
|
|
|
320
|
+
</details>
|
|
321
|
+
|
|
322
|
+
<details>
|
|
323
|
+
<summary><b>Production Configuration</b></summary>
|
|
311
324
|
|
|
312
|
-
**Production Configuration:**
|
|
313
325
|
```python
|
|
314
326
|
from onvif import ONVIFClient, CacheMode
|
|
315
327
|
|
|
@@ -320,13 +332,14 @@ client = ONVIFClient(
|
|
|
320
332
|
username="admin",
|
|
321
333
|
password="secure_password",
|
|
322
334
|
timeout=15,
|
|
323
|
-
cache=CacheMode.ALL, # Maximum performance
|
|
335
|
+
cache=CacheMode.ALL, # Maximum performance (default)
|
|
324
336
|
use_https=True, # Secure communication
|
|
325
337
|
verify_ssl=True, # Verify certificates (default)
|
|
326
338
|
apply_patch=True, # Enhanced parsing (default)
|
|
327
339
|
capture_xml=False # Disable debug mode (default)
|
|
328
340
|
)
|
|
329
341
|
```
|
|
342
|
+
</details>
|
|
330
343
|
|
|
331
344
|
### Notes
|
|
332
345
|
|
|
@@ -338,7 +351,7 @@ client = ONVIFClient(
|
|
|
338
351
|
## Service Discovery: Understanding Device Capabilities
|
|
339
352
|
|
|
340
353
|
> [!WARNING]
|
|
341
|
-
> Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically
|
|
354
|
+
> Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically performs comprehensive service discovery during initialization using a robust fallback mechanism.
|
|
342
355
|
|
|
343
356
|
**Why discover device services?**
|
|
344
357
|
|
|
@@ -349,50 +362,84 @@ client = ONVIFClient(
|
|
|
349
362
|
|
|
350
363
|
**How service discovery works in this library:**
|
|
351
364
|
|
|
352
|
-
The `ONVIFClient`
|
|
365
|
+
The `ONVIFClient` uses a **3-tier discovery approach** to maximize device compatibility:
|
|
366
|
+
|
|
367
|
+
1. **GetServices (Preferred)** - Tries `GetServices` first for detailed service information
|
|
368
|
+
2. **GetCapabilities (Fallback)** - Falls back to `GetCapabilities` if `GetServices` is not supported
|
|
369
|
+
3. **Default URLs (Final Fallback)** - Uses standard ONVIF URLs as last resort
|
|
353
370
|
|
|
354
371
|
```python
|
|
355
372
|
from onvif import ONVIFClient
|
|
356
373
|
|
|
357
374
|
client = ONVIFClient("192.168.1.17", 8000, "admin", "admin123")
|
|
358
375
|
|
|
359
|
-
#
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
376
|
+
# Check what discovery method was used
|
|
377
|
+
if client.services:
|
|
378
|
+
print("Service discovery: GetServices (preferred)")
|
|
379
|
+
print("Discovered services:", len(client.services))
|
|
380
|
+
print("Service map:", client._service_map)
|
|
381
|
+
elif client.capabilities:
|
|
382
|
+
print("Service discovery: GetCapabilities (fallback)")
|
|
383
|
+
print("Available capabilities:", client.capabilities)
|
|
384
|
+
else:
|
|
385
|
+
print("Service discovery: Using default URLs")
|
|
366
386
|
```
|
|
367
387
|
|
|
368
|
-
**
|
|
388
|
+
**Why this approach?**
|
|
369
389
|
|
|
370
|
-
|
|
390
|
+
- **GetServices** provides the most accurate and detailed service information, but it's **optional** in the ONVIF specification
|
|
391
|
+
- **GetCapabilities** is **mandatory** for all ONVIF-compliant devices, ensuring broader compatibility
|
|
392
|
+
- **Default URLs** guarantee basic connectivity even with non-compliant devices
|
|
393
|
+
|
|
394
|
+
**Get detailed service information:**
|
|
395
|
+
|
|
396
|
+
If you need comprehensive service details, you can manually call `GetServices` with capabilities:
|
|
371
397
|
|
|
372
398
|
```python
|
|
373
399
|
device = client.devicemgmt()
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
# Try to get detailed service information
|
|
403
|
+
services = device.GetServices(IncludeCapability=True)
|
|
404
|
+
for service in services:
|
|
405
|
+
print(f"Service: {service.Namespace}")
|
|
406
|
+
print(f"XAddr: {service.XAddr}")
|
|
407
|
+
if hasattr(service, 'Capabilities'):
|
|
408
|
+
print(f"Capabilities: {service.Capabilities}")
|
|
409
|
+
except Exception:
|
|
410
|
+
# Fallback to GetCapabilities for legacy devices
|
|
411
|
+
capabilities = device.GetCapabilities()
|
|
412
|
+
print("Capabilities:", capabilities)
|
|
382
413
|
```
|
|
383
414
|
|
|
384
|
-
**
|
|
415
|
+
**Access capabilities information:**
|
|
385
416
|
|
|
386
|
-
|
|
417
|
+
When using `GetCapabilities` fallback, you can access capability information:
|
|
387
418
|
|
|
388
419
|
```python
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
420
|
+
device = client.devicemgmt()
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
# Get capabilities information from device
|
|
424
|
+
capabilities = device.GetCapabilities()
|
|
425
|
+
|
|
426
|
+
# Main services (always available)
|
|
427
|
+
print("Media XAddr:", getattr(capabilities.Media, 'XAddr', 'Not available'))
|
|
428
|
+
print("PTZ XAddr:", getattr(capabilities.PTZ, 'XAddr', 'Not available'))
|
|
429
|
+
|
|
430
|
+
# Extension services (device-dependent)
|
|
431
|
+
ext = getattr(capabilities, 'Extension', None)
|
|
432
|
+
if ext:
|
|
433
|
+
print("DeviceIO XAddr:", getattr(ext.DeviceIO, 'XAddr', 'Not available'))
|
|
434
|
+
print("Recording XAddr:", getattr(ext.Recording, 'XAddr', 'Not available'))
|
|
435
|
+
print("Search XAddr:", getattr(ext.Search, 'XAddr', 'Not available'))
|
|
436
|
+
print("Replay XAddr:", getattr(ext.Replay, 'XAddr', 'Not available'))
|
|
437
|
+
except Exception as e:
|
|
438
|
+
print(f"Error getting capabilities: {e}")
|
|
392
439
|
```
|
|
393
440
|
|
|
394
441
|
> [!TIP]
|
|
395
|
-
> The library handles service discovery automatically
|
|
442
|
+
> The library handles service discovery automatically with intelligent fallback. You typically don't need to call discovery methods manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
|
|
396
443
|
|
|
397
444
|
## Tested Devices
|
|
398
445
|
|
|
@@ -598,8 +645,6 @@ This project is an **independent open-source implementation** of the [ONVIF](htt
|
|
|
598
645
|
|
|
599
646
|
If you require certified ONVIF-compliant devices or clients, please refer to the official [ONVIF conformant product list](https://www.onvif.org/conformant-products/). For authoritative reference and the latest official ONVIF specifications, please consult the [ONVIF Official Specifications](https://www.onvif.org/profiles/specifications/specification-history/).
|
|
600
647
|
|
|
601
|
-
Use of this library is at your own risk. The authors and contributors assume no liability for any damages, direct or indirect, arising from its use.
|
|
602
|
-
|
|
603
648
|
## License
|
|
604
649
|
|
|
605
650
|
This project is licensed under the MIT License. See [LICENSE](./LICENSE.md) for details.
|
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
|
|
4
4
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
5
|
-
[](https://github.com/nirsimetri/onvif-python/releases)
|
|
6
6
|
<br>
|
|
7
|
-
[](https://pypi.org/project/onvif-python/)
|
|
8
8
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
9
9
|
|
|
10
|
-
Are you having trouble finding a Python ONVIF library that supports your device?
|
|
11
|
-
Are you confused about the compatibility of the various ONVIF versions, which are updated every six months?
|
|
12
|
-
|
|
13
10
|
**This project provides a comprehensive and developer-friendly Python library for working with ONVIF-compliant devices.** It is designed to be reliable, easy to integrate, and flexible enough to support a wide range of ONVIF profiles and services.
|
|
14
11
|
|
|
15
12
|
**[ONVIF](https://www.onvif.org) (Open Network Video Interface Forum)** is a global standard for the interface of IP-based physical security products, including network cameras, video recorders, and related systems.
|
|
@@ -113,7 +110,9 @@ Explore more advanced usage and service-specific operations in the [`examples/`]
|
|
|
113
110
|
|
|
114
111
|
The `ONVIFClient` class provides various configuration options to customize the connection behavior, caching strategy, security settings, and debugging capabilities. Below is a detailed description of all available parameters:
|
|
115
112
|
|
|
116
|
-
|
|
113
|
+
|
|
114
|
+
<details>
|
|
115
|
+
<summary><b>Basic Parameters</b></summary>
|
|
117
116
|
|
|
118
117
|
| Parameter | Type | Required | Default | Description |
|
|
119
118
|
|-----------|------|----------|---------|-------------|
|
|
@@ -122,7 +121,10 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
122
121
|
| `username` | `str` | ✅ Yes | - | Username for device authentication (use digest authentication) |
|
|
123
122
|
| `password` | `str` | ✅ Yes | - | Password for device authentication |
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
</details>
|
|
125
|
+
|
|
126
|
+
<details>
|
|
127
|
+
<summary><b>Connection Parameters</b></summary>
|
|
126
128
|
|
|
127
129
|
| Parameter | Type | Required | Default | Description |
|
|
128
130
|
|-----------|------|----------|---------|-------------|
|
|
@@ -130,33 +132,45 @@ The `ONVIFClient` class provides various configuration options to customize the
|
|
|
130
132
|
| `use_https` | `bool` | ❌ No | `False` | Use HTTPS instead of HTTP for secure communication |
|
|
131
133
|
| `verify_ssl` | `bool` | ❌ No | `True` | Verify SSL certificates when using HTTPS (set to `False` for self-signed certificates) |
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
<details>
|
|
138
|
+
<summary><b>Caching Parameters</b></summary>
|
|
134
139
|
|
|
135
140
|
| Parameter | Type | Required | Default | Description |
|
|
136
141
|
|-----------|------|----------|---------|-------------|
|
|
137
142
|
| `cache` | `CacheMode` | ❌ No | `CacheMode.ALL` | WSDL caching strategy (see [Cache Modes](#cache-modes) below) |
|
|
138
143
|
|
|
139
|
-
|
|
144
|
+
</details>
|
|
145
|
+
|
|
146
|
+
<details>
|
|
147
|
+
<summary><b>Feature Parameters</b></summary>
|
|
140
148
|
|
|
141
149
|
| Parameter | Type | Required | Default | Description |
|
|
142
150
|
|-----------|------|----------|---------|-------------|
|
|
143
|
-
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better xsd:any field parsing and automatic flattening |
|
|
151
|
+
| `apply_patch` | `bool` | ❌ No | `True` | Enable zeep patching for better `xsd:any` field parsing and automatic flattening |
|
|
144
152
|
| `capture_xml` | `bool` | ❌ No | `False` | Enable XML capture plugin for debugging SOAP requests/responses |
|
|
145
153
|
|
|
146
|
-
|
|
154
|
+
</details>
|
|
155
|
+
|
|
156
|
+
<details>
|
|
157
|
+
<summary><b>Cache Modes</b></summary>
|
|
147
158
|
|
|
148
159
|
The library provides four caching strategies via the `CacheMode` enum:
|
|
149
160
|
|
|
150
161
|
| Mode | Description | Best For | Startup Speed | Disk Usage | Memory Usage |
|
|
151
162
|
|------|-------------|----------|---------------|------------|--------------|
|
|
152
|
-
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps |
|
|
153
|
-
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools |
|
|
154
|
-
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos |
|
|
155
|
-
| `CacheMode.NONE` | No caching | Testing, debugging |
|
|
163
|
+
| `CacheMode.ALL` | In-memory + disk cache (SQLite) | Production servers, multi-device apps | Fast | High | High |
|
|
164
|
+
| `CacheMode.DB` | Disk cache only (SQLite) | Batch jobs, CLI tools | Medium | Medium | Low |
|
|
165
|
+
| `CacheMode.MEM` | In-memory cache only | Short-lived scripts, demos | Medium | None | Medium |
|
|
166
|
+
| `CacheMode.NONE` | No caching | Testing, debugging | Slow | None | Low |
|
|
156
167
|
|
|
157
168
|
**Recommendation:** Use `CacheMode.ALL` (default) for production applications to maximize performance.
|
|
158
169
|
|
|
159
|
-
|
|
170
|
+
</details>
|
|
171
|
+
|
|
172
|
+
<details>
|
|
173
|
+
<summary><b>Usage Examples</b></summary>
|
|
160
174
|
|
|
161
175
|
**Basic Connection:**
|
|
162
176
|
```python
|
|
@@ -177,8 +191,7 @@ client = ONVIFClient(
|
|
|
177
191
|
"admin",
|
|
178
192
|
"password",
|
|
179
193
|
timeout=30,
|
|
180
|
-
use_https=True
|
|
181
|
-
verify_ssl=False # For self-signed certificates
|
|
194
|
+
use_https=True
|
|
182
195
|
)
|
|
183
196
|
```
|
|
184
197
|
|
|
@@ -196,7 +209,7 @@ client = ONVIFClient(
|
|
|
196
209
|
)
|
|
197
210
|
```
|
|
198
211
|
|
|
199
|
-
**No Caching (Testing):**
|
|
212
|
+
**No Caching and No Zeep Patching (Testing):**
|
|
200
213
|
```python
|
|
201
214
|
from onvif import ONVIFClient, CacheMode
|
|
202
215
|
|
|
@@ -255,7 +268,6 @@ if client.xml_plugin:
|
|
|
255
268
|
client.xml_plugin.clear_history()
|
|
256
269
|
```
|
|
257
270
|
|
|
258
|
-
> [!NOTE]
|
|
259
271
|
> **XML Capture Plugin Methods:**
|
|
260
272
|
> - `last_sent_xml` - Get the last SOAP request XML
|
|
261
273
|
> - `last_received_xml` - Get the last SOAP response XML
|
|
@@ -267,8 +279,11 @@ if client.xml_plugin:
|
|
|
267
279
|
> - `save_to_file(request_file, response_file)` - Save XML to files
|
|
268
280
|
> - `clear_history()` - Clear captured history
|
|
269
281
|
|
|
282
|
+
</details>
|
|
283
|
+
|
|
284
|
+
<details>
|
|
285
|
+
<summary><b>Production Configuration</b></summary>
|
|
270
286
|
|
|
271
|
-
**Production Configuration:**
|
|
272
287
|
```python
|
|
273
288
|
from onvif import ONVIFClient, CacheMode
|
|
274
289
|
|
|
@@ -279,13 +294,14 @@ client = ONVIFClient(
|
|
|
279
294
|
username="admin",
|
|
280
295
|
password="secure_password",
|
|
281
296
|
timeout=15,
|
|
282
|
-
cache=CacheMode.ALL, # Maximum performance
|
|
297
|
+
cache=CacheMode.ALL, # Maximum performance (default)
|
|
283
298
|
use_https=True, # Secure communication
|
|
284
299
|
verify_ssl=True, # Verify certificates (default)
|
|
285
300
|
apply_patch=True, # Enhanced parsing (default)
|
|
286
301
|
capture_xml=False # Disable debug mode (default)
|
|
287
302
|
)
|
|
288
303
|
```
|
|
304
|
+
</details>
|
|
289
305
|
|
|
290
306
|
### Notes
|
|
291
307
|
|
|
@@ -297,7 +313,7 @@ client = ONVIFClient(
|
|
|
297
313
|
## Service Discovery: Understanding Device Capabilities
|
|
298
314
|
|
|
299
315
|
> [!WARNING]
|
|
300
|
-
> Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically
|
|
316
|
+
> Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically performs comprehensive service discovery during initialization using a robust fallback mechanism.
|
|
301
317
|
|
|
302
318
|
**Why discover device services?**
|
|
303
319
|
|
|
@@ -308,50 +324,84 @@ client = ONVIFClient(
|
|
|
308
324
|
|
|
309
325
|
**How service discovery works in this library:**
|
|
310
326
|
|
|
311
|
-
The `ONVIFClient`
|
|
327
|
+
The `ONVIFClient` uses a **3-tier discovery approach** to maximize device compatibility:
|
|
328
|
+
|
|
329
|
+
1. **GetServices (Preferred)** - Tries `GetServices` first for detailed service information
|
|
330
|
+
2. **GetCapabilities (Fallback)** - Falls back to `GetCapabilities` if `GetServices` is not supported
|
|
331
|
+
3. **Default URLs (Final Fallback)** - Uses standard ONVIF URLs as last resort
|
|
312
332
|
|
|
313
333
|
```python
|
|
314
334
|
from onvif import ONVIFClient
|
|
315
335
|
|
|
316
336
|
client = ONVIFClient("192.168.1.17", 8000, "admin", "admin123")
|
|
317
337
|
|
|
318
|
-
#
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
338
|
+
# Check what discovery method was used
|
|
339
|
+
if client.services:
|
|
340
|
+
print("Service discovery: GetServices (preferred)")
|
|
341
|
+
print("Discovered services:", len(client.services))
|
|
342
|
+
print("Service map:", client._service_map)
|
|
343
|
+
elif client.capabilities:
|
|
344
|
+
print("Service discovery: GetCapabilities (fallback)")
|
|
345
|
+
print("Available capabilities:", client.capabilities)
|
|
346
|
+
else:
|
|
347
|
+
print("Service discovery: Using default URLs")
|
|
325
348
|
```
|
|
326
349
|
|
|
327
|
-
**
|
|
350
|
+
**Why this approach?**
|
|
328
351
|
|
|
329
|
-
|
|
352
|
+
- **GetServices** provides the most accurate and detailed service information, but it's **optional** in the ONVIF specification
|
|
353
|
+
- **GetCapabilities** is **mandatory** for all ONVIF-compliant devices, ensuring broader compatibility
|
|
354
|
+
- **Default URLs** guarantee basic connectivity even with non-compliant devices
|
|
355
|
+
|
|
356
|
+
**Get detailed service information:**
|
|
357
|
+
|
|
358
|
+
If you need comprehensive service details, you can manually call `GetServices` with capabilities:
|
|
330
359
|
|
|
331
360
|
```python
|
|
332
361
|
device = client.devicemgmt()
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
# Try to get detailed service information
|
|
365
|
+
services = device.GetServices(IncludeCapability=True)
|
|
366
|
+
for service in services:
|
|
367
|
+
print(f"Service: {service.Namespace}")
|
|
368
|
+
print(f"XAddr: {service.XAddr}")
|
|
369
|
+
if hasattr(service, 'Capabilities'):
|
|
370
|
+
print(f"Capabilities: {service.Capabilities}")
|
|
371
|
+
except Exception:
|
|
372
|
+
# Fallback to GetCapabilities for legacy devices
|
|
373
|
+
capabilities = device.GetCapabilities()
|
|
374
|
+
print("Capabilities:", capabilities)
|
|
341
375
|
```
|
|
342
376
|
|
|
343
|
-
**
|
|
377
|
+
**Access capabilities information:**
|
|
344
378
|
|
|
345
|
-
|
|
379
|
+
When using `GetCapabilities` fallback, you can access capability information:
|
|
346
380
|
|
|
347
381
|
```python
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
382
|
+
device = client.devicemgmt()
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
# Get capabilities information from device
|
|
386
|
+
capabilities = device.GetCapabilities()
|
|
387
|
+
|
|
388
|
+
# Main services (always available)
|
|
389
|
+
print("Media XAddr:", getattr(capabilities.Media, 'XAddr', 'Not available'))
|
|
390
|
+
print("PTZ XAddr:", getattr(capabilities.PTZ, 'XAddr', 'Not available'))
|
|
391
|
+
|
|
392
|
+
# Extension services (device-dependent)
|
|
393
|
+
ext = getattr(capabilities, 'Extension', None)
|
|
394
|
+
if ext:
|
|
395
|
+
print("DeviceIO XAddr:", getattr(ext.DeviceIO, 'XAddr', 'Not available'))
|
|
396
|
+
print("Recording XAddr:", getattr(ext.Recording, 'XAddr', 'Not available'))
|
|
397
|
+
print("Search XAddr:", getattr(ext.Search, 'XAddr', 'Not available'))
|
|
398
|
+
print("Replay XAddr:", getattr(ext.Replay, 'XAddr', 'Not available'))
|
|
399
|
+
except Exception as e:
|
|
400
|
+
print(f"Error getting capabilities: {e}")
|
|
351
401
|
```
|
|
352
402
|
|
|
353
403
|
> [!TIP]
|
|
354
|
-
> The library handles service discovery automatically
|
|
404
|
+
> The library handles service discovery automatically with intelligent fallback. You typically don't need to call discovery methods manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
|
|
355
405
|
|
|
356
406
|
## Tested Devices
|
|
357
407
|
|
|
@@ -557,8 +607,6 @@ This project is an **independent open-source implementation** of the [ONVIF](htt
|
|
|
557
607
|
|
|
558
608
|
If you require certified ONVIF-compliant devices or clients, please refer to the official [ONVIF conformant product list](https://www.onvif.org/conformant-products/). For authoritative reference and the latest official ONVIF specifications, please consult the [ONVIF Official Specifications](https://www.onvif.org/profiles/specifications/specification-history/).
|
|
559
609
|
|
|
560
|
-
Use of this library is at your own risk. The authors and contributors assume no liability for any damages, direct or indirect, arising from its use.
|
|
561
|
-
|
|
562
610
|
## License
|
|
563
611
|
|
|
564
612
|
This project is licensed under the MIT License. See [LICENSE](./LICENSE.md) for details.
|