onvif-python 0.1.6__tar.gz → 0.1.7__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.1.6/onvif_python.egg-info → onvif_python-0.1.7}/PKG-INFO +23 -6
- {onvif_python-0.1.6 → onvif_python-0.1.7}/README.md +21 -5
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/interactive.py +2 -2
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/main.py +34 -20
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/utils.py +34 -11
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/accesscontrol.py +11 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/tlsserver.py +1 -1
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/discovery.py +71 -16
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/xml_capture.py +18 -16
- {onvif_python-0.1.6 → onvif_python-0.1.7/onvif_python.egg-info}/PKG-INFO +23 -6
- {onvif_python-0.1.6 → onvif_python-0.1.7}/pyproject.toml +2 -1
- {onvif_python-0.1.6 → onvif_python-0.1.7}/LICENSE.md +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/MANIFEST.in +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/__init__.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/__init__.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/__main__.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/client.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/operator.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/__init__.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/accessrules.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/actionengine.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/analytics/analytics.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/analytics/ruleengine.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/analyticsdevice.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/appmgmt.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/authenticationbehavior.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/credential.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/deviceio.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/devicemgmt.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/display.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/doorcontrol.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/events.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/notification.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/pullpoint.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/subscription.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/imaging.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/media.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/media2.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/provisioning.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/ptz.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/receiver.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/recording.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/replay.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/schedule.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/search.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/advancedsecurity.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/authorizationserver.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/dot1x.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/jwt.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/keystore.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/mediasigning.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/thermal.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/uplink.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/__init__.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/error_handlers.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/exceptions.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/wsdl.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/zeep.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/display/display.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/display.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/receiver.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/recording.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/replay.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schema/common.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/search.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/SOURCES.txt +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/dependency_links.txt +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/entry_points.txt +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/requires.txt +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/top_level.txt +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/setup.cfg +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/setup.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_client.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_core.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_error_handlers.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_exceptions.py +0 -0
- {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_wsdl_map.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onvif-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
8
9
|
Project-URL: Documentation, https://deepwiki.com/nirsimetri/onvif-python
|
|
9
10
|
Project-URL: Changelog, https://github.com/nirsimetri/onvif-python/releases
|
|
10
11
|
Keywords: onvif,Camera,onvif-python,ip-camera,cctv,soap,surveillance,cctv cameras,soap-client,onvif-client,onvif-library,onvif-specifications
|
|
@@ -40,7 +41,7 @@ Dynamic: license-file
|
|
|
40
41
|
|
|
41
42
|
[](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
42
43
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
43
|
-
[](https://pypi.org/project/onvif-python/)
|
|
44
45
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
45
46
|
<br>
|
|
46
47
|
[](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
|
|
@@ -107,6 +108,10 @@ discovery = ONVIFDiscovery(timeout=5)
|
|
|
107
108
|
# Discover devices
|
|
108
109
|
devices = discovery.discover()
|
|
109
110
|
|
|
111
|
+
# Or with
|
|
112
|
+
# Discover with search filter by types or scopes (case-insensitive substring match)
|
|
113
|
+
devices = discovery.discover(search="Profile/Streaming")
|
|
114
|
+
|
|
110
115
|
# Display discovered devices
|
|
111
116
|
for device in devices:
|
|
112
117
|
print(f"Found device at {device['host']}:{device['port']}")
|
|
@@ -232,11 +237,12 @@ This library includes a powerful command-line interface (CLI) for interacting wi
|
|
|
232
237
|
<summary><b>1. Direct CLI</b></summary>
|
|
233
238
|
|
|
234
239
|
```bash
|
|
235
|
-
usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--
|
|
236
|
-
[--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}]
|
|
240
|
+
usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--search SEARCH] [--timeout TIMEOUT] [--https]
|
|
241
|
+
[--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}]
|
|
242
|
+
[--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
|
|
237
243
|
[service] [method] [params ...]
|
|
238
244
|
|
|
239
|
-
ONVIF Terminal Client — v0.1.
|
|
245
|
+
ONVIF Terminal Client — v0.1.7
|
|
240
246
|
https://github.com/nirsimetri/onvif-python
|
|
241
247
|
|
|
242
248
|
positional arguments:
|
|
@@ -253,6 +259,8 @@ options:
|
|
|
253
259
|
--password PASSWORD, -p PASSWORD
|
|
254
260
|
Password for authentication
|
|
255
261
|
--discover, -d Discover ONVIF devices on the network using WS-Discovery
|
|
262
|
+
--search SEARCH, -s SEARCH
|
|
263
|
+
Filter discovered devices by types or scopes (case-insensitive substring match)
|
|
256
264
|
--timeout TIMEOUT Connection timeout in seconds (default: 10)
|
|
257
265
|
--https Use HTTPS instead of HTTP
|
|
258
266
|
--no-verify Disable SSL certificate verification
|
|
@@ -272,6 +280,11 @@ Examples:
|
|
|
272
280
|
onvif media GetProfiles --discover --username admin
|
|
273
281
|
onvif -d -i
|
|
274
282
|
|
|
283
|
+
# Discover with filtering
|
|
284
|
+
onvif --discover --search ptz --interactive
|
|
285
|
+
onvif -d -s "C210" -i
|
|
286
|
+
onvif -d -s "audio_encoder" -u admin -p admin123 -i
|
|
287
|
+
|
|
275
288
|
# Direct command execution
|
|
276
289
|
onvif devicemgmt GetCapabilities Category=All --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
277
290
|
onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity={"PanTilt": {"x": -0.1, "y": 0}} --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
@@ -293,7 +306,7 @@ Examples:
|
|
|
293
306
|
<summary><b>2. Interactive Shell</b></summary>
|
|
294
307
|
|
|
295
308
|
```bash
|
|
296
|
-
ONVIF Interactive Shell
|
|
309
|
+
ONVIF Interactive Shell — v0.1.7
|
|
297
310
|
https://github.com/nirsimetri/onvif-python
|
|
298
311
|
|
|
299
312
|
Basic Commands:
|
|
@@ -426,6 +439,10 @@ onvif --discover --username admin --password admin123 --interactive
|
|
|
426
439
|
# Short form
|
|
427
440
|
onvif -d -u admin -p admin123 -i
|
|
428
441
|
|
|
442
|
+
# Discover with search filter
|
|
443
|
+
onvif --discover --search "C210" --interactive
|
|
444
|
+
onvif -d -s ptz -u admin -p admin123 -i
|
|
445
|
+
|
|
429
446
|
# Discover and interactive (will prompt for credentials)
|
|
430
447
|
onvif -d -i
|
|
431
448
|
```
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
4
4
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
5
|
-
[](https://pypi.org/project/onvif-python/)
|
|
6
6
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
7
7
|
<br>
|
|
8
8
|
[](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
|
|
@@ -69,6 +69,10 @@ discovery = ONVIFDiscovery(timeout=5)
|
|
|
69
69
|
# Discover devices
|
|
70
70
|
devices = discovery.discover()
|
|
71
71
|
|
|
72
|
+
# Or with
|
|
73
|
+
# Discover with search filter by types or scopes (case-insensitive substring match)
|
|
74
|
+
devices = discovery.discover(search="Profile/Streaming")
|
|
75
|
+
|
|
72
76
|
# Display discovered devices
|
|
73
77
|
for device in devices:
|
|
74
78
|
print(f"Found device at {device['host']}:{device['port']}")
|
|
@@ -194,11 +198,12 @@ This library includes a powerful command-line interface (CLI) for interacting wi
|
|
|
194
198
|
<summary><b>1. Direct CLI</b></summary>
|
|
195
199
|
|
|
196
200
|
```bash
|
|
197
|
-
usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--
|
|
198
|
-
[--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}]
|
|
201
|
+
usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--search SEARCH] [--timeout TIMEOUT] [--https]
|
|
202
|
+
[--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}]
|
|
203
|
+
[--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
|
|
199
204
|
[service] [method] [params ...]
|
|
200
205
|
|
|
201
|
-
ONVIF Terminal Client — v0.1.
|
|
206
|
+
ONVIF Terminal Client — v0.1.7
|
|
202
207
|
https://github.com/nirsimetri/onvif-python
|
|
203
208
|
|
|
204
209
|
positional arguments:
|
|
@@ -215,6 +220,8 @@ options:
|
|
|
215
220
|
--password PASSWORD, -p PASSWORD
|
|
216
221
|
Password for authentication
|
|
217
222
|
--discover, -d Discover ONVIF devices on the network using WS-Discovery
|
|
223
|
+
--search SEARCH, -s SEARCH
|
|
224
|
+
Filter discovered devices by types or scopes (case-insensitive substring match)
|
|
218
225
|
--timeout TIMEOUT Connection timeout in seconds (default: 10)
|
|
219
226
|
--https Use HTTPS instead of HTTP
|
|
220
227
|
--no-verify Disable SSL certificate verification
|
|
@@ -234,6 +241,11 @@ Examples:
|
|
|
234
241
|
onvif media GetProfiles --discover --username admin
|
|
235
242
|
onvif -d -i
|
|
236
243
|
|
|
244
|
+
# Discover with filtering
|
|
245
|
+
onvif --discover --search ptz --interactive
|
|
246
|
+
onvif -d -s "C210" -i
|
|
247
|
+
onvif -d -s "audio_encoder" -u admin -p admin123 -i
|
|
248
|
+
|
|
237
249
|
# Direct command execution
|
|
238
250
|
onvif devicemgmt GetCapabilities Category=All --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
239
251
|
onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity={"PanTilt": {"x": -0.1, "y": 0}} --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
@@ -255,7 +267,7 @@ Examples:
|
|
|
255
267
|
<summary><b>2. Interactive Shell</b></summary>
|
|
256
268
|
|
|
257
269
|
```bash
|
|
258
|
-
ONVIF Interactive Shell
|
|
270
|
+
ONVIF Interactive Shell — v0.1.7
|
|
259
271
|
https://github.com/nirsimetri/onvif-python
|
|
260
272
|
|
|
261
273
|
Basic Commands:
|
|
@@ -388,6 +400,10 @@ onvif --discover --username admin --password admin123 --interactive
|
|
|
388
400
|
# Short form
|
|
389
401
|
onvif -d -u admin -p admin123 -i
|
|
390
402
|
|
|
403
|
+
# Discover with search filter
|
|
404
|
+
onvif --discover --search "C210" --interactive
|
|
405
|
+
onvif -d -s ptz -u admin -p admin123 -i
|
|
406
|
+
|
|
391
407
|
# Discover and interactive (will prompt for credentials)
|
|
392
408
|
onvif -d -i
|
|
393
409
|
```
|
|
@@ -191,7 +191,7 @@ class InteractiveShell(cmd.Cmd):
|
|
|
191
191
|
" / __ \\/ | / / | / / _/ ____/",
|
|
192
192
|
" / / / / |/ /| | / // // /_ ",
|
|
193
193
|
"/ /_/ / /| / | |/ // // __/ ",
|
|
194
|
-
"\\____/_/ |_/ |___/___/_/ v0.1.
|
|
194
|
+
"\\____/_/ |_/ |___/___/_/ v0.1.7",
|
|
195
195
|
" ",
|
|
196
196
|
]
|
|
197
197
|
|
|
@@ -1385,7 +1385,7 @@ class InteractiveShell(cmd.Cmd):
|
|
|
1385
1385
|
super().do_help(line)
|
|
1386
1386
|
else:
|
|
1387
1387
|
help_text = f"""
|
|
1388
|
-
{colorize('ONVIF Interactive Shell
|
|
1388
|
+
{colorize('ONVIF Interactive Shell — v0.1.7', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
|
|
1389
1389
|
|
|
1390
1390
|
{colorize('Basic Commands:', 'yellow')}
|
|
1391
1391
|
capabilities, caps - Show device capabilities
|
|
@@ -17,7 +17,7 @@ def create_parser():
|
|
|
17
17
|
"""Create argument parser for ONVIF CLI"""
|
|
18
18
|
parser = argparse.ArgumentParser(
|
|
19
19
|
prog="onvif",
|
|
20
|
-
description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.1.
|
|
20
|
+
description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.1.7\nhttps://github.com/nirsimetri/onvif-python",
|
|
21
21
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
22
22
|
epilog=f"""
|
|
23
23
|
Examples:
|
|
@@ -26,6 +26,11 @@ Examples:
|
|
|
26
26
|
{colorize('onvif', 'yellow')} media GetProfiles --discover --username admin
|
|
27
27
|
{colorize('onvif', 'yellow')} -d -i
|
|
28
28
|
|
|
29
|
+
# Discover with filtering
|
|
30
|
+
{colorize('onvif', 'yellow')} --discover --search ptz --interactive
|
|
31
|
+
{colorize('onvif', 'yellow')} -d -s "C210" -i
|
|
32
|
+
{colorize('onvif', 'yellow')} -d -s "audio_encoder" -u admin -p admin123 -i
|
|
33
|
+
|
|
29
34
|
# Direct command execution
|
|
30
35
|
{colorize('onvif', 'yellow')} devicemgmt GetCapabilities Category=All --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
31
36
|
{colorize('onvif', 'yellow')} ptz ContinuousMove ProfileToken=Profile_1 Velocity={{"PanTilt": {{"x": -0.1, "y": 0}}}} --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
@@ -61,6 +66,11 @@ Examples:
|
|
|
61
66
|
action="store_true",
|
|
62
67
|
help="Discover ONVIF devices on the network using WS-Discovery",
|
|
63
68
|
)
|
|
69
|
+
parser.add_argument(
|
|
70
|
+
"--search",
|
|
71
|
+
"-s",
|
|
72
|
+
help="Filter discovered devices by types or scopes (case-insensitive substring match)",
|
|
73
|
+
)
|
|
64
74
|
|
|
65
75
|
# Connection options
|
|
66
76
|
parser.add_argument(
|
|
@@ -139,7 +149,7 @@ def main():
|
|
|
139
149
|
|
|
140
150
|
# Show ONVIF CLI version
|
|
141
151
|
if args.version:
|
|
142
|
-
print(colorize("0.1.
|
|
152
|
+
print(colorize("0.1.7", "yellow"))
|
|
143
153
|
sys.exit(0)
|
|
144
154
|
|
|
145
155
|
# Validate arguments early (before discovery)
|
|
@@ -155,11 +165,18 @@ def main():
|
|
|
155
165
|
f"{colorize('--discover', 'white')} cannot be used with {colorize('--host', 'white')}"
|
|
156
166
|
)
|
|
157
167
|
|
|
158
|
-
# Discover devices (pass --https flag to prioritize HTTPS XAddrs)
|
|
159
|
-
devices = discover_devices(
|
|
168
|
+
# Discover devices (pass --https flag to prioritize HTTPS XAddrs and search term)
|
|
169
|
+
devices = discover_devices(
|
|
170
|
+
timeout=4, prefer_https=args.https, search=args.search
|
|
171
|
+
)
|
|
160
172
|
|
|
161
173
|
if not devices:
|
|
162
|
-
|
|
174
|
+
if args.search:
|
|
175
|
+
print(
|
|
176
|
+
f"{colorize('No devices found matching search:', 'red')} {colorize(args.search, 'white')}"
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
print(colorize("No ONVIF devices discovered. Exiting.", "red"))
|
|
163
180
|
sys.exit(1)
|
|
164
181
|
|
|
165
182
|
# Let user select a device
|
|
@@ -278,34 +295,31 @@ def execute_command(
|
|
|
278
295
|
return method(**params)
|
|
279
296
|
|
|
280
297
|
|
|
281
|
-
def discover_devices(
|
|
298
|
+
def discover_devices(
|
|
299
|
+
timeout: int = 4, prefer_https: bool = False, search: Optional[str] = None
|
|
300
|
+
) -> list:
|
|
282
301
|
"""Discover ONVIF devices on the network using WS-Discovery.
|
|
283
302
|
|
|
284
303
|
Args:
|
|
285
304
|
timeout: Discovery timeout in seconds
|
|
286
305
|
prefer_https: If True, prioritize HTTPS XAddrs when available
|
|
306
|
+
search: Optional search term to filter devices by types or scopes
|
|
287
307
|
|
|
288
308
|
Returns:
|
|
289
309
|
List of discovered devices with connection info
|
|
290
310
|
"""
|
|
291
|
-
# Get local network interface for display
|
|
292
|
-
try:
|
|
293
|
-
import socket
|
|
294
311
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
local_ip = s.getsockname()[0]
|
|
298
|
-
s.close()
|
|
299
|
-
except Exception:
|
|
300
|
-
local_ip = "0.0.0.0"
|
|
312
|
+
# Use ONVIFDiscovery class
|
|
313
|
+
discovery = ONVIFDiscovery(timeout=timeout)
|
|
301
314
|
|
|
302
315
|
print(f"\n{colorize('Discovering ONVIF devices on network...', 'yellow')}")
|
|
303
|
-
print(f"Network interface: {colorize(
|
|
304
|
-
print(f"Timeout: {timeout}s
|
|
316
|
+
print(f"Network interface: {colorize(discovery._get_local_ip(), 'white')}")
|
|
317
|
+
print(f"Timeout: {timeout}s")
|
|
318
|
+
if search:
|
|
319
|
+
print(f"Filter: {colorize(search, 'yellow')}")
|
|
320
|
+
print()
|
|
305
321
|
|
|
306
|
-
|
|
307
|
-
discovery = ONVIFDiscovery(timeout=timeout)
|
|
308
|
-
devices = discovery.discover(prefer_https=prefer_https)
|
|
322
|
+
devices = discovery.discover(prefer_https=prefer_https, search=search)
|
|
309
323
|
|
|
310
324
|
return devices
|
|
311
325
|
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import os
|
|
5
5
|
import inspect
|
|
6
6
|
from typing import Any, Dict, Optional
|
|
7
|
-
import
|
|
7
|
+
from lxml import etree
|
|
8
8
|
import re
|
|
9
9
|
|
|
10
10
|
|
|
@@ -231,10 +231,17 @@ def get_method_documentation(service_obj, method_name: str) -> Optional[Dict[str
|
|
|
231
231
|
try:
|
|
232
232
|
# 1. Get documentation from WSDL (existing logic)
|
|
233
233
|
wsdl_path = service_obj.operator.wsdl_path
|
|
234
|
-
|
|
234
|
+
|
|
235
|
+
# Use secure lxml parser
|
|
236
|
+
parser = etree.XMLParser(
|
|
237
|
+
resolve_entities=False,
|
|
238
|
+
no_network=True,
|
|
239
|
+
remove_blank_text=True,
|
|
240
|
+
)
|
|
241
|
+
tree = etree.parse(wsdl_path, parser)
|
|
235
242
|
root = tree.getroot()
|
|
236
243
|
namespaces = {
|
|
237
|
-
node[0]: node[1] for node in
|
|
244
|
+
node[0]: node[1] for node in etree.iterparse(wsdl_path, events=["start-ns"])
|
|
238
245
|
}
|
|
239
246
|
namespaces["wsdl"] = "http://schemas.xmlsoap.org/wsdl/"
|
|
240
247
|
namespaces["xs"] = "http://www.w3.org/2001/XMLSchema"
|
|
@@ -321,7 +328,7 @@ def get_method_documentation(service_obj, method_name: str) -> Optional[Dict[str
|
|
|
321
328
|
|
|
322
329
|
return {"doc": doc_text, "required": required_args, "optional": optional_args}
|
|
323
330
|
|
|
324
|
-
except (
|
|
331
|
+
except (etree.ParseError, FileNotFoundError, AttributeError, ValueError):
|
|
325
332
|
# Fallback in case of any error, still try to get params
|
|
326
333
|
try:
|
|
327
334
|
method = getattr(service_obj, method_name)
|
|
@@ -709,14 +716,19 @@ def get_operation_type_info(
|
|
|
709
716
|
Returns a dictionary with 'input' and 'output' keys containing message details.
|
|
710
717
|
"""
|
|
711
718
|
try:
|
|
712
|
-
import os
|
|
713
|
-
|
|
714
719
|
wsdl_path = service_obj.operator.wsdl_path
|
|
715
|
-
|
|
720
|
+
|
|
721
|
+
# Use secure lxml parser
|
|
722
|
+
parser = etree.XMLParser(
|
|
723
|
+
resolve_entities=False,
|
|
724
|
+
no_network=True,
|
|
725
|
+
remove_blank_text=True,
|
|
726
|
+
)
|
|
727
|
+
tree = etree.parse(wsdl_path, parser)
|
|
716
728
|
root = tree.getroot()
|
|
717
729
|
|
|
718
730
|
namespaces = {
|
|
719
|
-
node[0]: node[1] for node in
|
|
731
|
+
node[0]: node[1] for node in etree.iterparse(wsdl_path, events=["start-ns"])
|
|
720
732
|
}
|
|
721
733
|
namespaces["wsdl"] = "http://schemas.xmlsoap.org/wsdl/"
|
|
722
734
|
namespaces["xs"] = "http://www.w3.org/2001/XMLSchema"
|
|
@@ -773,7 +785,6 @@ def _load_imported_schemas(root, schema_context: dict, namespaces: dict):
|
|
|
773
785
|
"""
|
|
774
786
|
Recursively load all imported and included schemas into the schema context.
|
|
775
787
|
"""
|
|
776
|
-
import os
|
|
777
788
|
|
|
778
789
|
# Find all xs:import and xs:include in xs:schema elements
|
|
779
790
|
for schema in root.findall(".//xs:schema", namespaces):
|
|
@@ -787,7 +798,13 @@ def _load_imported_schemas(root, schema_context: dict, namespaces: dict):
|
|
|
787
798
|
|
|
788
799
|
if os.path.exists(schema_path):
|
|
789
800
|
try:
|
|
790
|
-
|
|
801
|
+
# Use secure parser for imported schemas
|
|
802
|
+
parser = etree.XMLParser(
|
|
803
|
+
resolve_entities=False,
|
|
804
|
+
no_network=True,
|
|
805
|
+
remove_blank_text=True,
|
|
806
|
+
)
|
|
807
|
+
imported_tree = etree.parse(schema_path, parser)
|
|
791
808
|
imported_root = imported_tree.getroot()
|
|
792
809
|
|
|
793
810
|
# Add to context if not already present
|
|
@@ -817,7 +834,13 @@ def _load_imported_schemas(root, schema_context: dict, namespaces: dict):
|
|
|
817
834
|
|
|
818
835
|
if os.path.exists(schema_path):
|
|
819
836
|
try:
|
|
820
|
-
|
|
837
|
+
# Use secure parser for included schemas
|
|
838
|
+
parser = etree.XMLParser(
|
|
839
|
+
resolve_entities=False,
|
|
840
|
+
no_network=True,
|
|
841
|
+
remove_blank_text=True,
|
|
842
|
+
)
|
|
843
|
+
included_tree = etree.parse(schema_path, parser)
|
|
821
844
|
included_root = included_tree.getroot()
|
|
822
845
|
|
|
823
846
|
# Add to context if not already present
|
|
@@ -98,6 +98,17 @@ class AccessControl:
|
|
|
98
98
|
def DisableAccessPoint(self, Token):
|
|
99
99
|
return self.operator.call("DisableAccessPoint", Token=Token)
|
|
100
100
|
|
|
101
|
+
def Feedback(
|
|
102
|
+
self, AccessPointToken, FeedbackType, RecognitionType=None, TextMessage=None
|
|
103
|
+
):
|
|
104
|
+
return self.operator.call(
|
|
105
|
+
"Feedback",
|
|
106
|
+
AccessPointToken=AccessPointToken,
|
|
107
|
+
FeedbackType=FeedbackType,
|
|
108
|
+
RecognitionType=RecognitionType,
|
|
109
|
+
TextMessage=TextMessage,
|
|
110
|
+
)
|
|
111
|
+
|
|
101
112
|
def ExternalAuthorization(
|
|
102
113
|
self, AccessPointToken, Decision, CredentialToken=None, Reason=None
|
|
103
114
|
):
|
|
@@ -57,7 +57,7 @@ class TLSServer:
|
|
|
57
57
|
return self.operator.call("GetClientAuthenticationRequired")
|
|
58
58
|
|
|
59
59
|
def SetCnMapsToUser(self, cnMapsToUser):
|
|
60
|
-
return self.operator.call("
|
|
60
|
+
return self.operator.call("SetCnMapsToUser", cnMapsToUser=cnMapsToUser)
|
|
61
61
|
|
|
62
62
|
def GetCnMapsToUser(self):
|
|
63
63
|
return self.operator.call("GetCnMapsToUser")
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import socket
|
|
4
4
|
import uuid
|
|
5
|
-
import xml.etree.ElementTree as ET
|
|
6
5
|
import struct
|
|
6
|
+
from lxml import etree
|
|
7
7
|
from typing import List, Dict, Any, Optional
|
|
8
8
|
|
|
9
9
|
|
|
@@ -92,11 +92,14 @@ class ONVIFDiscovery:
|
|
|
92
92
|
|
|
93
93
|
return self._local_ip
|
|
94
94
|
|
|
95
|
-
def discover(
|
|
95
|
+
def discover(
|
|
96
|
+
self, prefer_https: bool = False, search: Optional[str] = None
|
|
97
|
+
) -> List[Dict[str, Any]]:
|
|
96
98
|
"""Discover ONVIF devices on the network.
|
|
97
99
|
|
|
98
100
|
Args:
|
|
99
101
|
prefer_https: If True, prioritize HTTPS XAddrs when available
|
|
102
|
+
search: Optional search term to filter devices by types or scopes (case-insensitive)
|
|
100
103
|
|
|
101
104
|
Returns:
|
|
102
105
|
List of discovered devices with connection information.
|
|
@@ -114,6 +117,10 @@ class ONVIFDiscovery:
|
|
|
114
117
|
>>> devices = discovery.discover()
|
|
115
118
|
>>> for device in devices:
|
|
116
119
|
... print(f"{device['host']}:{device['port']}")
|
|
120
|
+
|
|
121
|
+
>>> # Filter devices by search term
|
|
122
|
+
>>> devices = discovery.discover(search="ptz")
|
|
123
|
+
>>> devices = discovery.discover(search="Hong Kong")
|
|
117
124
|
"""
|
|
118
125
|
local_ip = self._get_local_ip()
|
|
119
126
|
|
|
@@ -158,7 +165,13 @@ class ONVIFDiscovery:
|
|
|
158
165
|
return []
|
|
159
166
|
|
|
160
167
|
# Parse responses
|
|
161
|
-
|
|
168
|
+
devices = self._parse_responses(responses, prefer_https)
|
|
169
|
+
|
|
170
|
+
# Apply search filter if provided
|
|
171
|
+
if search:
|
|
172
|
+
devices = self._filter_devices(devices, search)
|
|
173
|
+
|
|
174
|
+
return devices
|
|
162
175
|
|
|
163
176
|
def _parse_responses(
|
|
164
177
|
self, responses: List[Dict[str, str]], prefer_https: bool = False
|
|
@@ -185,6 +198,41 @@ class ONVIFDiscovery:
|
|
|
185
198
|
|
|
186
199
|
return devices
|
|
187
200
|
|
|
201
|
+
def _filter_devices(
|
|
202
|
+
self, devices: List[Dict[str, Any]], search_term: str
|
|
203
|
+
) -> List[Dict[str, Any]]:
|
|
204
|
+
"""Filter devices based on search term in types or scopes.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
devices: List of discovered devices
|
|
208
|
+
search_term: Search string to match against types/scopes (case-insensitive)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Filtered list of devices matching the search term
|
|
212
|
+
"""
|
|
213
|
+
if not search_term:
|
|
214
|
+
return devices
|
|
215
|
+
|
|
216
|
+
search_lower = search_term.lower()
|
|
217
|
+
filtered = []
|
|
218
|
+
|
|
219
|
+
for device in devices:
|
|
220
|
+
# Check types
|
|
221
|
+
types_match = any(
|
|
222
|
+
search_lower in t.lower() for t in device.get("types", [])
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Check scopes
|
|
226
|
+
scopes_match = any(
|
|
227
|
+
search_lower in s.lower() for s in device.get("scopes", [])
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Include device if match found in types or scopes
|
|
231
|
+
if types_match or scopes_match:
|
|
232
|
+
filtered.append(device)
|
|
233
|
+
|
|
234
|
+
return filtered
|
|
235
|
+
|
|
188
236
|
def _parse_single_response(
|
|
189
237
|
self, xml_data: str, prefer_https: bool = False
|
|
190
238
|
) -> Optional[Dict[str, Any]]:
|
|
@@ -198,10 +246,17 @@ class ONVIFDiscovery:
|
|
|
198
246
|
Device information dictionary or None if parsing fails
|
|
199
247
|
"""
|
|
200
248
|
try:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
249
|
+
# Use lxml's secure parser that prevents XXE attacks
|
|
250
|
+
parser = etree.XMLParser(
|
|
251
|
+
resolve_entities=False, # Disable entity resolution
|
|
252
|
+
no_network=True, # Disable network access
|
|
253
|
+
remove_blank_text=True,
|
|
204
254
|
)
|
|
255
|
+
root = etree.fromstring(xml_data.encode("utf-8"), parser)
|
|
256
|
+
|
|
257
|
+
probe_match = probe_match = root.find(".//d:ProbeMatch", self.NAMESPACES)
|
|
258
|
+
if probe_match is None:
|
|
259
|
+
probe_match = root.find(".//wsd:ProbeMatch", self.NAMESPACES)
|
|
205
260
|
|
|
206
261
|
if probe_match is None:
|
|
207
262
|
return None
|
|
@@ -220,27 +275,27 @@ class ONVIFDiscovery:
|
|
|
220
275
|
epr = probe_match.find(
|
|
221
276
|
".//wsa:EndpointReference/wsa:Address", self.NAMESPACES
|
|
222
277
|
)
|
|
223
|
-
if epr is not None:
|
|
278
|
+
if epr is not None and epr.text:
|
|
224
279
|
device_info["epr"] = epr.text
|
|
225
280
|
|
|
226
281
|
# Extract Types
|
|
227
|
-
types_elem = probe_match.find(
|
|
228
|
-
|
|
229
|
-
|
|
282
|
+
types_elem = probe_match.find(".//d:Types", self.NAMESPACES)
|
|
283
|
+
if types_elem is None:
|
|
284
|
+
types_elem = probe_match.find(".//wsd:Types", self.NAMESPACES)
|
|
230
285
|
if types_elem is not None and types_elem.text:
|
|
231
286
|
device_info["types"] = types_elem.text.split()
|
|
232
287
|
|
|
233
288
|
# Extract Scopes
|
|
234
|
-
scopes_elem = probe_match.find(
|
|
235
|
-
|
|
236
|
-
|
|
289
|
+
scopes_elem = probe_match.find(".//d:Scopes", self.NAMESPACES)
|
|
290
|
+
if scopes_elem is None:
|
|
291
|
+
scopes_elem = probe_match.find(".//wsd:Scopes", self.NAMESPACES)
|
|
237
292
|
if scopes_elem is not None and scopes_elem.text:
|
|
238
293
|
device_info["scopes"] = scopes_elem.text.split()
|
|
239
294
|
|
|
240
295
|
# Extract XAddrs
|
|
241
|
-
xaddrs_elem = probe_match.find(
|
|
242
|
-
|
|
243
|
-
|
|
296
|
+
xaddrs_elem = probe_match.find(".//d:XAddrs", self.NAMESPACES)
|
|
297
|
+
if xaddrs_elem is None:
|
|
298
|
+
xaddrs_elem = probe_match.find(".//wsd:XAddrs", self.NAMESPACES)
|
|
244
299
|
if xaddrs_elem is not None and xaddrs_elem.text:
|
|
245
300
|
device_info["xaddrs"] = xaddrs_elem.text.split()
|
|
246
301
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from zeep import Plugin
|
|
4
4
|
from lxml import etree
|
|
5
|
-
from xml.dom import minidom
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class XMLCapturePlugin(Plugin):
|
|
@@ -38,7 +37,7 @@ class XMLCapturePlugin(Plugin):
|
|
|
38
37
|
|
|
39
38
|
def _format_xml(self, element):
|
|
40
39
|
"""
|
|
41
|
-
Format XML element with proper indentation using
|
|
40
|
+
Format XML element with proper indentation using lxml.
|
|
42
41
|
|
|
43
42
|
Args:
|
|
44
43
|
element: lxml Element to format
|
|
@@ -47,21 +46,24 @@ class XMLCapturePlugin(Plugin):
|
|
|
47
46
|
str: Pretty-printed XML string
|
|
48
47
|
"""
|
|
49
48
|
try:
|
|
50
|
-
# Convert
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
# Convert element to string first
|
|
50
|
+
xml_bytes = etree.tostring(element, encoding="utf-8")
|
|
51
|
+
|
|
52
|
+
# Re-parse with parser that removes blank text
|
|
53
|
+
# This is safe as we control the input (it's from zeep)
|
|
54
|
+
parser = etree.XMLParser(
|
|
55
|
+
remove_blank_text=True, resolve_entities=False, no_network=True
|
|
56
|
+
)
|
|
57
|
+
reparsed = etree.fromstring(xml_bytes, parser)
|
|
58
|
+
|
|
59
|
+
# Now pretty print the cleaned tree
|
|
60
|
+
xml_string = etree.tostring(
|
|
61
|
+
reparsed, pretty_print=True, encoding="unicode", xml_declaration=False
|
|
62
|
+
)
|
|
63
|
+
return xml_string.strip()
|
|
62
64
|
except Exception:
|
|
63
|
-
# Fallback to
|
|
64
|
-
return etree.tostring(element, pretty_print=
|
|
65
|
+
# Fallback to non-pretty printed version
|
|
66
|
+
return etree.tostring(element, pretty_print=False, encoding="unicode")
|
|
65
67
|
|
|
66
68
|
def egress(self, envelope, http_headers, operation, binding_options):
|
|
67
69
|
"""Called before sending the SOAP request"""
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onvif-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
8
9
|
Project-URL: Documentation, https://deepwiki.com/nirsimetri/onvif-python
|
|
9
10
|
Project-URL: Changelog, https://github.com/nirsimetri/onvif-python/releases
|
|
10
11
|
Keywords: onvif,Camera,onvif-python,ip-camera,cctv,soap,surveillance,cctv cameras,soap-client,onvif-client,onvif-library,onvif-specifications
|
|
@@ -40,7 +41,7 @@ Dynamic: license-file
|
|
|
40
41
|
|
|
41
42
|
[](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
42
43
|
[](https://deepwiki.com/nirsimetri/onvif-python)
|
|
43
|
-
[](https://pypi.org/project/onvif-python/)
|
|
44
45
|
[](https://clickpy.clickhouse.com/dashboard/onvif-python)
|
|
45
46
|
<br>
|
|
46
47
|
[](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml)
|
|
@@ -107,6 +108,10 @@ discovery = ONVIFDiscovery(timeout=5)
|
|
|
107
108
|
# Discover devices
|
|
108
109
|
devices = discovery.discover()
|
|
109
110
|
|
|
111
|
+
# Or with
|
|
112
|
+
# Discover with search filter by types or scopes (case-insensitive substring match)
|
|
113
|
+
devices = discovery.discover(search="Profile/Streaming")
|
|
114
|
+
|
|
110
115
|
# Display discovered devices
|
|
111
116
|
for device in devices:
|
|
112
117
|
print(f"Found device at {device['host']}:{device['port']}")
|
|
@@ -232,11 +237,12 @@ This library includes a powerful command-line interface (CLI) for interacting wi
|
|
|
232
237
|
<summary><b>1. Direct CLI</b></summary>
|
|
233
238
|
|
|
234
239
|
```bash
|
|
235
|
-
usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--
|
|
236
|
-
[--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}]
|
|
240
|
+
usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--search SEARCH] [--timeout TIMEOUT] [--https]
|
|
241
|
+
[--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}]
|
|
242
|
+
[--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
|
|
237
243
|
[service] [method] [params ...]
|
|
238
244
|
|
|
239
|
-
ONVIF Terminal Client — v0.1.
|
|
245
|
+
ONVIF Terminal Client — v0.1.7
|
|
240
246
|
https://github.com/nirsimetri/onvif-python
|
|
241
247
|
|
|
242
248
|
positional arguments:
|
|
@@ -253,6 +259,8 @@ options:
|
|
|
253
259
|
--password PASSWORD, -p PASSWORD
|
|
254
260
|
Password for authentication
|
|
255
261
|
--discover, -d Discover ONVIF devices on the network using WS-Discovery
|
|
262
|
+
--search SEARCH, -s SEARCH
|
|
263
|
+
Filter discovered devices by types or scopes (case-insensitive substring match)
|
|
256
264
|
--timeout TIMEOUT Connection timeout in seconds (default: 10)
|
|
257
265
|
--https Use HTTPS instead of HTTP
|
|
258
266
|
--no-verify Disable SSL certificate verification
|
|
@@ -272,6 +280,11 @@ Examples:
|
|
|
272
280
|
onvif media GetProfiles --discover --username admin
|
|
273
281
|
onvif -d -i
|
|
274
282
|
|
|
283
|
+
# Discover with filtering
|
|
284
|
+
onvif --discover --search ptz --interactive
|
|
285
|
+
onvif -d -s "C210" -i
|
|
286
|
+
onvif -d -s "audio_encoder" -u admin -p admin123 -i
|
|
287
|
+
|
|
275
288
|
# Direct command execution
|
|
276
289
|
onvif devicemgmt GetCapabilities Category=All --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
277
290
|
onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity={"PanTilt": {"x": -0.1, "y": 0}} --host 192.168.1.17 --port 8000 --username admin --password admin123
|
|
@@ -293,7 +306,7 @@ Examples:
|
|
|
293
306
|
<summary><b>2. Interactive Shell</b></summary>
|
|
294
307
|
|
|
295
308
|
```bash
|
|
296
|
-
ONVIF Interactive Shell
|
|
309
|
+
ONVIF Interactive Shell — v0.1.7
|
|
297
310
|
https://github.com/nirsimetri/onvif-python
|
|
298
311
|
|
|
299
312
|
Basic Commands:
|
|
@@ -426,6 +439,10 @@ onvif --discover --username admin --password admin123 --interactive
|
|
|
426
439
|
# Short form
|
|
427
440
|
onvif -d -u admin -p admin123 -i
|
|
428
441
|
|
|
442
|
+
# Discover with search filter
|
|
443
|
+
onvif --discover --search "C210" --interactive
|
|
444
|
+
onvif -d -s ptz -u admin -p admin123 -i
|
|
445
|
+
|
|
429
446
|
# Discover and interactive (will prompt for credentials)
|
|
430
447
|
onvif -d -i
|
|
431
448
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "onvif-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.7"
|
|
8
8
|
description = "A comprehensive and developer-friendly Python library for working with ONVIF-compliant devices"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -69,5 +69,6 @@ extend-exclude = '''
|
|
|
69
69
|
'''
|
|
70
70
|
|
|
71
71
|
[project.urls]
|
|
72
|
+
Homepage = "https://github.com/nirsimetri/onvif-python"
|
|
72
73
|
Documentation = "https://deepwiki.com/nirsimetri/onvif-python"
|
|
73
74
|
Changelog = "https://github.com/nirsimetri/onvif-python/releases"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/federated_search_documentation.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|