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.
Files changed (128) hide show
  1. {onvif_python-0.1.6/onvif_python.egg-info → onvif_python-0.1.7}/PKG-INFO +23 -6
  2. {onvif_python-0.1.6 → onvif_python-0.1.7}/README.md +21 -5
  3. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/interactive.py +2 -2
  4. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/main.py +34 -20
  5. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/utils.py +34 -11
  6. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/accesscontrol.py +11 -0
  7. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/tlsserver.py +1 -1
  8. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/discovery.py +71 -16
  9. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/xml_capture.py +18 -16
  10. {onvif_python-0.1.6 → onvif_python-0.1.7/onvif_python.egg-info}/PKG-INFO +23 -6
  11. {onvif_python-0.1.6 → onvif_python-0.1.7}/pyproject.toml +2 -1
  12. {onvif_python-0.1.6 → onvif_python-0.1.7}/LICENSE.md +0 -0
  13. {onvif_python-0.1.6 → onvif_python-0.1.7}/MANIFEST.in +0 -0
  14. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/__init__.py +0 -0
  15. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/__init__.py +0 -0
  16. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/cli/__main__.py +0 -0
  17. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/client.py +0 -0
  18. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/operator.py +0 -0
  19. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/__init__.py +0 -0
  20. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/accessrules.py +0 -0
  21. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/actionengine.py +0 -0
  22. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/analytics/analytics.py +0 -0
  23. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/analytics/ruleengine.py +0 -0
  24. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/analyticsdevice.py +0 -0
  25. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/appmgmt.py +0 -0
  26. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/authenticationbehavior.py +0 -0
  27. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/credential.py +0 -0
  28. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/deviceio.py +0 -0
  29. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/devicemgmt.py +0 -0
  30. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/display.py +0 -0
  31. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/doorcontrol.py +0 -0
  32. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/events.py +0 -0
  33. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/notification.py +0 -0
  34. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/pullpoint.py +0 -0
  35. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/events/subscription.py +0 -0
  36. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/imaging.py +0 -0
  37. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/media.py +0 -0
  38. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/media2.py +0 -0
  39. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/provisioning.py +0 -0
  40. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/ptz.py +0 -0
  41. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/receiver.py +0 -0
  42. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/recording.py +0 -0
  43. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/replay.py +0 -0
  44. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/schedule.py +0 -0
  45. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/search.py +0 -0
  46. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/advancedsecurity.py +0 -0
  47. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/authorizationserver.py +0 -0
  48. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/dot1x.py +0 -0
  49. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/jwt.py +0 -0
  50. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/keystore.py +0 -0
  51. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/security/mediasigning.py +0 -0
  52. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/thermal.py +0 -0
  53. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/services/uplink.py +0 -0
  54. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/__init__.py +0 -0
  55. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/error_handlers.py +0 -0
  56. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/exceptions.py +0 -0
  57. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/wsdl.py +0 -0
  58. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/utils/zeep.py +0 -0
  59. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  60. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  61. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  62. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  63. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
  64. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  65. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  66. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  67. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  68. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  69. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  70. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  71. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  72. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
  73. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  74. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  75. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  76. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/display.wsdl +0 -0
  77. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  78. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  79. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  80. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  81. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  82. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  83. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  84. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  85. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  86. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  87. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  88. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  89. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/recording.wsdl +0 -0
  90. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/replay.wsdl +0 -0
  91. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  92. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  93. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  94. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
  95. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
  96. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/search.wsdl +0 -0
  97. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  98. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  99. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  100. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  101. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  102. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  103. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  104. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/index.html +0 -0
  105. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  106. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  107. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  108. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  109. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  110. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  111. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
  112. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  113. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  114. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  115. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  116. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  117. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/SOURCES.txt +0 -0
  118. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/dependency_links.txt +0 -0
  119. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/entry_points.txt +0 -0
  120. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/requires.txt +0 -0
  121. {onvif_python-0.1.6 → onvif_python-0.1.7}/onvif_python.egg-info/top_level.txt +0 -0
  122. {onvif_python-0.1.6 → onvif_python-0.1.7}/setup.cfg +0 -0
  123. {onvif_python-0.1.6 → onvif_python-0.1.7}/setup.py +0 -0
  124. {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_client.py +0 -0
  125. {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_core.py +0 -0
  126. {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_error_handlers.py +0 -0
  127. {onvif_python-0.1.6 → onvif_python-0.1.7}/tests/test_exceptions.py +0 -0
  128. {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.6
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
  [![Codacy grade](https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Project%20Quality&logo=codacy)](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
42
43
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
43
- [![PyPI](https://img.shields.io/badge/PyPI-0.1.6-orange?logo=archive)](https://pypi.org/project/onvif-python/)
44
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.7-orange?logo=archive)](https://pypi.org/project/onvif-python/)
44
45
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
45
46
  <br>
46
47
  [![Build](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main)](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] [--timeout TIMEOUT] [--https] [--no-verify]
236
- [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
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.6
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 Commands - v0.1.6
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
  [![Codacy grade](https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Project%20Quality&logo=codacy)](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
4
4
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
5
- [![PyPI](https://img.shields.io/badge/PyPI-0.1.6-orange?logo=archive)](https://pypi.org/project/onvif-python/)
5
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.7-orange?logo=archive)](https://pypi.org/project/onvif-python/)
6
6
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
7
7
  <br>
8
8
  [![Build](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main)](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] [--timeout TIMEOUT] [--https] [--no-verify]
198
- [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
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.6
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 Commands - v0.1.6
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.6",
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 Commands - v0.1.6', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
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.6\nhttps://github.com/nirsimetri/onvif-python",
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.6", "yellow"))
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(timeout=4, prefer_https=args.https)
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
- print(colorize("No ONVIF devices discovered. Exiting.", "red"))
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(timeout: int = 4, prefer_https: bool = False) -> list:
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
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
296
- s.connect(("8.8.8.8", 80))
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(local_ip, 'white')}")
304
- print(f"Timeout: {timeout}s\n")
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
- # Use ONVIFDiscovery class
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 xml.etree.ElementTree as ET
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
- tree = ET.parse(wsdl_path)
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 ET.iterparse(wsdl_path, events=["start-ns"])
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 (ET.ParseError, FileNotFoundError, AttributeError, ValueError):
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
- tree = ET.parse(wsdl_path)
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 ET.iterparse(wsdl_path, events=["start-ns"])
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
- imported_tree = ET.parse(schema_path)
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
- included_tree = ET.parse(schema_path)
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("SetCnMapsToUsername", cnMapsToUser=cnMapsToUser)
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(self, prefer_https: bool = False) -> List[Dict[str, Any]]:
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
- return self._parse_responses(responses, prefer_https)
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
- root = ET.fromstring(xml_data)
202
- probe_match = root.find(".//d:ProbeMatch", self.NAMESPACES) or root.find(
203
- ".//wsd:ProbeMatch", self.NAMESPACES
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
- ".//d:Types", self.NAMESPACES
229
- ) or probe_match.find(".//wsd:Types", self.NAMESPACES)
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
- ".//d:Scopes", self.NAMESPACES
236
- ) or probe_match.find(".//wsd:Scopes", self.NAMESPACES)
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
- ".//d:XAddrs", self.NAMESPACES
243
- ) or probe_match.find(".//wsd:XAddrs", self.NAMESPACES)
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 minidom.
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 lxml element to string
51
- xml_string = etree.tostring(element, encoding="unicode")
52
- # Parse with minidom and pretty print
53
- dom = minidom.parseString(xml_string)
54
- # Use toprettyxml with proper indentation
55
- pretty_xml = dom.toprettyxml(indent=" ", encoding=None)
56
- # Remove extra blank lines and XML declaration
57
- lines = [line for line in pretty_xml.split("\n") if line.strip()]
58
- # Remove XML declaration line if present
59
- if lines and lines[0].startswith("<?xml"):
60
- lines = lines[1:]
61
- return "\n".join(lines)
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 lxml if minidom fails
64
- return etree.tostring(element, pretty_print=True, encoding="unicode")
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.6
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
  [![Codacy grade](https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Project%20Quality&logo=codacy)](https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
42
43
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/nirsimetri/onvif-python)
43
- [![PyPI](https://img.shields.io/badge/PyPI-0.1.6-orange?logo=archive)](https://pypi.org/project/onvif-python/)
44
+ [![PyPI](https://img.shields.io/badge/PyPI-0.1.7-orange?logo=archive)](https://pypi.org/project/onvif-python/)
44
45
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=Downloads&color=red)](https://clickpy.clickhouse.com/dashboard/onvif-python)
45
46
  <br>
46
47
  [![Build](https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main)](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] [--timeout TIMEOUT] [--https] [--no-verify]
236
- [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
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.6
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 Commands - v0.1.6
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.6"
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