onvif-python 0.2.4__tar.gz → 0.2.6__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 (133) hide show
  1. {onvif_python-0.2.4/onvif_python.egg-info → onvif_python-0.2.6}/PKG-INFO +41 -24
  2. {onvif_python-0.2.4 → onvif_python-0.2.6}/README.md +40 -23
  3. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/__init__.py +1 -2
  4. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/cli/interactive.py +2 -2
  5. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/cli/main.py +213 -5
  6. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/devicemgmt.py +3 -0
  7. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/media2.py +17 -0
  8. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +9 -9
  9. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +53 -0
  10. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/schema/metadatastream.xsd +2 -0
  11. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/schema/onvif.xsd +224 -0
  12. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/media/wsdl/media.wsdl +148 -0
  13. {onvif_python-0.2.4 → onvif_python-0.2.6/onvif_python.egg-info}/PKG-INFO +41 -24
  14. {onvif_python-0.2.4 → onvif_python-0.2.6}/pyproject.toml +1 -1
  15. {onvif_python-0.2.4 → onvif_python-0.2.6}/LICENSE.md +0 -0
  16. {onvif_python-0.2.4 → onvif_python-0.2.6}/MANIFEST.in +0 -0
  17. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/cli/__init__.py +0 -0
  18. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/cli/__main__.py +0 -0
  19. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/cli/utils.py +0 -0
  20. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/client.py +0 -0
  21. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/db/companies.db +0 -0
  22. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/db/products.db +0 -0
  23. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/operator.py +0 -0
  24. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/__init__.py +0 -0
  25. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/accesscontrol.py +0 -0
  26. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/accessrules.py +0 -0
  27. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/actionengine.py +0 -0
  28. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/analytics/analytics.py +0 -0
  29. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/analytics/ruleengine.py +0 -0
  30. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/analyticsdevice.py +0 -0
  31. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/appmgmt.py +0 -0
  32. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/authenticationbehavior.py +0 -0
  33. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/credential.py +0 -0
  34. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/deviceio.py +0 -0
  35. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/display.py +0 -0
  36. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/doorcontrol.py +0 -0
  37. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/events/events.py +0 -0
  38. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/events/notification.py +0 -0
  39. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/events/pausable_subscription.py +0 -0
  40. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/events/pullpoint.py +0 -0
  41. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/events/subscription.py +0 -0
  42. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/imaging.py +0 -0
  43. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/media.py +0 -0
  44. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/provisioning.py +0 -0
  45. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/ptz.py +0 -0
  46. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/receiver.py +0 -0
  47. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/recording.py +0 -0
  48. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/replay.py +0 -0
  49. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/schedule.py +0 -0
  50. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/search.py +0 -0
  51. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/security/advancedsecurity.py +0 -0
  52. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/security/authorizationserver.py +0 -0
  53. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/security/dot1x.py +0 -0
  54. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/security/jwt.py +0 -0
  55. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/security/keystore.py +0 -0
  56. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/security/mediasigning.py +0 -0
  57. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/security/tlsserver.py +0 -0
  58. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/thermal.py +0 -0
  59. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/services/uplink.py +0 -0
  60. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/__init__.py +0 -0
  61. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/discovery.py +0 -0
  62. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/error_handlers.py +0 -0
  63. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/exceptions.py +0 -0
  64. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/parser.py +0 -0
  65. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/service.py +0 -0
  66. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/wsdl.py +0 -0
  67. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/xml_capture.py +0 -0
  68. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/utils/zeep.py +0 -0
  69. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/LICENSE.md +0 -0
  70. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  71. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  72. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  73. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  74. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  75. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  76. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  77. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  78. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  79. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  80. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  81. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  82. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  83. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  84. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  85. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/display.wsdl +0 -0
  86. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  87. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  88. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  89. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  90. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  91. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  92. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  93. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  94. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  95. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  96. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  97. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  98. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/recording.wsdl +0 -0
  99. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/replay.wsdl +0 -0
  100. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  101. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  102. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  103. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/search.wsdl +0 -0
  104. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  105. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  106. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  107. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  108. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  109. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  110. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  111. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/analytics/index.html +0 -0
  112. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  113. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  114. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  115. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  116. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  117. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  118. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  119. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  120. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  121. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  122. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  123. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif_python.egg-info/SOURCES.txt +0 -0
  124. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif_python.egg-info/dependency_links.txt +0 -0
  125. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif_python.egg-info/entry_points.txt +0 -0
  126. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif_python.egg-info/requires.txt +0 -0
  127. {onvif_python-0.2.4 → onvif_python-0.2.6}/onvif_python.egg-info/top_level.txt +0 -0
  128. {onvif_python-0.2.4 → onvif_python-0.2.6}/setup.cfg +0 -0
  129. {onvif_python-0.2.4 → onvif_python-0.2.6}/setup.py +0 -0
  130. {onvif_python-0.2.4 → onvif_python-0.2.6}/tests/test_client.py +0 -0
  131. {onvif_python-0.2.4 → onvif_python-0.2.6}/tests/test_core.py +0 -0
  132. {onvif_python-0.2.4 → onvif_python-0.2.6}/tests/test_error_handlers.py +0 -0
  133. {onvif_python-0.2.4 → onvif_python-0.2.6}/tests/test_exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onvif-python
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: A modern Python library for ONVIF-compliant devices
5
5
  Author-email: Nirsimetri Technologies® <open@nirsimetri.com>
6
6
  Maintainer: Kaburagi
@@ -44,13 +44,13 @@ Dynamic: license-file
44
44
  <h1 align="center">ONVIF Python</h1>
45
45
 
46
46
  <div align="center">
47
- <img alt="Codacy grade" src="https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy" href="https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
48
- <img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" href="https://deepwiki.com/nirsimetri/onvif-python">
49
- <img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.4-orange?logo=archive&color=yellow" href="https://pypi.org/project/onvif-python/">
50
- <img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red" href="https://pepy.tech/projects/onvif-python">
47
+ <a href="https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img src="https://app.codacy.com/project/badge/Grade/bff08a94e4d447b690cea49c6594826d"/></a>
48
+ <a href="https://deepwiki.com/nirsimetri/onvif-python"><img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg"></a>
49
+ <a href="https://pypi.org/project/onvif-python/"><img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.6-orange?logo=archive&color=yellow"></a>
50
+ <a href="https://pepy.tech/projects/onvif-python"><img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red"></a>
51
51
  <br>
52
- <img alt="Build" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml">
53
- <img alt="Upload Python Package" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml">
52
+ <a href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml"><img alt="Build" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main"></a>
53
+ <a href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml"><img alt="Upload Python Package" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg"></a>
54
54
  </div>
55
55
 
56
56
  <h1 align="center">
@@ -64,15 +64,21 @@ Dynamic: license-file
64
64
 
65
65
  Behind the scenes, ONVIF communication relies on **[SOAP](https://en.wikipedia.org/wiki/SOAP) (Simple Object Access Protocol)** — an [XML](https://en.wikipedia.org/wiki/XML)-based messaging protocol with strict schema definitions ([WSDL](https://en.wikipedia.org/wiki/Web_Services_Description_Language)/[XSD](https://en.wikipedia.org/wiki/XML_Schema_(W3C))). SOAP ensures interoperability, but when used directly it can be verbose, complex, and error-prone.
66
66
 
67
- This library simplifies that process by wrapping SOAP communication into a clean, Pythonic API. You no longer need to handle low-level XML parsing, namespaces, or security tokens manually — the library takes care of it, letting you focus on building functionality.
67
+ This library simplifies that process by wrapping SOAP communication into a clean, Pythonic API. You no longer need to handle low-level XML parsing, namespaces, or security tokens manually — the library takes care of it, letting you focus on building functionality.
68
68
 
69
- ## Key Features
70
- - Full implementation of ONVIF core services and profiles
71
- - Support for device discovery, media streaming, PTZ control, event management, and more
72
- - Pythonic abstraction over SOAP requests and responses (no need to handcraft XML)
73
- - Extensible architecture for custom ONVIF extensions
74
- - Compatible with multiple ONVIF specification versions
75
- - Example scripts and tests included
69
+ ## Library Philosophy
70
+ > [!NOTE]
71
+ > This library will be continuously updated as ONVIF versions are updated. It uses a built-in WSDL that will always follow changes to the [ONVIF WSDL Specifications](https://github.com/onvif/specs). You can also use your own ONVIF WSDL file by adding the `wsdl_dir` argument; see [ONVIFClient Parameters](#onvifclient-parameters).
72
+
73
+ - **WYSIWYG (What You See is What You Get)**: Every ONVIF operation in the library mirrors the official ONVIF specification exactly. Method names, parameter structures, and response formats follow ONVIF standards without abstraction layers or renamed interfaces. What you see in the ONVIF documentation is exactly what you get in Python.
74
+
75
+ - **Device Variety Interoperability**: Built to handle the real-world diversity of ONVIF implementations across manufacturers. The library gracefully handles missing features, optional operations, and vendor-specific behaviors through comprehensive error handling and fallback mechanisms. Whether you're working with high-end enterprise cameras or budget IP cameras, the library adapts.
76
+
77
+ - **Official Specifications Accuracy**: All service implementations are generated and validated against official `ONVIF WSDL Specifications`. The library includes comprehensive test suites that verify compliance with ONVIF standards, ensuring that method signatures, parameter types, and behavior match the official specifications precisely.
78
+
79
+ - **Modern Python Approach**: Designed for excellent IDE support with full type hints, auto-completion, and immediate error detection. You'll get `TypeError` exceptions upfront when accessing ONVIF operations with wrong arguments, instead of cryptic `SOAP faults` later. Clean, Pythonic API that feels natural to Python developers while maintaining ONVIF compatibility.
80
+
81
+ - **Minimal Dependencies**: Only depends on essential, well-maintained libraries (`zeep` for SOAP, `requests` for HTTP). No bloated framework dependencies or custom XML parsers. The library stays lightweight while providing full ONVIF functionality, making it easy to integrate into any project without dependency conflicts.
76
82
 
77
83
  ## Who Is It For?
78
84
  - **Individual developers** exploring ONVIF or building hobby projects
@@ -351,11 +357,12 @@ This library includes a powerful command-line interface (CLI) for interacting wi
351
357
  <summary><b>1. Direct CLI</b></summary>
352
358
 
353
359
  ```bash
354
- usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--filter FILTER] [--search SEARCH] [--page PAGE] [--per-page PER_PAGE] [--timeout TIMEOUT] [--https]
355
- [--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
360
+ usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--filter FILTER] [--search SEARCH] [--page PAGE]
361
+ [--per-page PER_PAGE] [--timeout TIMEOUT] [--https] [--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL]
362
+ [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--output OUTPUT] [--version]
356
363
  [service] [method] [params ...]
357
364
 
358
- ONVIF Terminal Client — v0.2.4
365
+ ONVIF Terminal Client — v0.2.6
359
366
  https://github.com/nirsimetri/onvif-python
360
367
 
361
368
  positional arguments:
@@ -389,6 +396,9 @@ options:
389
396
  Caching mode for ONVIFClient (default: all). 'all': memory+disk, 'db': disk-only, 'mem': memory-only, 'none': disabled.
390
397
  --health-check-interval HEALTH_CHECK_INTERVAL, -hci HEALTH_CHECK_INTERVAL
391
398
  Health check interval in seconds for interactive mode (default: 10)
399
+ --output OUTPUT, -o OUTPUT
400
+ Save command output to file. Supports .json, .xml extensions for format detection, or plain text. XML format automatically enables
401
+ debug mode for SOAP capture.
392
402
  --version, -v Show ONVIF CLI version and exit
393
403
 
394
404
  Examples:
@@ -409,7 +419,12 @@ Examples:
409
419
 
410
420
  # Direct command execution
411
421
  onvif devicemgmt GetCapabilities Category=All --host 192.168.1.17 --port 8000 --username admin --password admin123
412
- onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity={"PanTilt": {"x": -0.1, "y": 0}} --host 192.168.1.17 --port 8000 --username admin --password admin123
422
+ onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity={'PanTilt': {'x': -0.1, 'y': 0}} -H 192.168.1.17 -P 8000 -u admin -p admin123
423
+
424
+ # Save output to file
425
+ onvif devicemgmt GetDeviceInformation --host 192.168.1.17 --port 8000 --username admin --password admin123 --output device_info.json
426
+ onvif media GetProfiles --host 192.168.1.17 --port 8000 --username admin --password admin123 --output profiles.xml
427
+ onvif ptz GetConfigurations --host 192.168.1.17 --port 8000 --username admin --password admin123 --output ptz_config.txt --debug
413
428
 
414
429
  # Interactive mode
415
430
  onvif --host 192.168.1.17 --port 8000 --username admin --password admin123 --interactive
@@ -428,7 +443,7 @@ Examples:
428
443
  <summary><b>2. Interactive Shell</b></summary>
429
444
 
430
445
  ```bash
431
- ONVIF Interactive Shell — v0.2.4
446
+ ONVIF Interactive Shell — v0.2.6
432
447
  https://github.com/nirsimetri/onvif-python
433
448
 
434
449
  Basic Commands:
@@ -636,7 +651,11 @@ onvif <service> <method> [parameters...] -H <host> -P <port> -u <user> -p <pass>
636
651
  onvif devicemgmt GetCapabilities Category=All -H 192.168.1.17 -P 8000 -u admin -p admin123
637
652
 
638
653
  # Move a PTZ camera
639
- onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity='{"PanTilt": {"x": 0.1}}' -H 192.168.1.17 -P 8000 -u admin -p admin123
654
+ onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity='{"PanTilt": {"x": 0.1, "y": 0}}' -H 192.168.1.17 -P 8000 -u admin -p admin123
655
+
656
+ # Save output to file
657
+ onvif devicemgmt GetDeviceInformation --host 192.168.1.17 --port 8000 --username admin --password admin123 --output device_info.json
658
+ onvif media GetProfiles -H 192.168.1.17 -P 8000 -u admin -p admin123 -o profiles.xml
640
659
  ```
641
660
 
642
661
  **4. ONVIF Product Search**
@@ -1152,13 +1171,11 @@ Some ONVIF services have multiple bindings in the same WSDL. These typically inc
1152
1171
  - [ ] Add more usage examples for advanced features.
1153
1172
  - [ ] Add benchmarking and performance metrics.
1154
1173
  - [ ] Add community-contributed device configuration templates.
1155
- - [ ] Implement missing or partial ONVIF services.
1156
- - [ ] Add function to expose ONVIF devices (for debugging purposes by the community).
1157
1174
 
1158
1175
  ## Related Projects
1159
1176
 
1160
1177
  - [onvif-products-directory](https://github.com/nirsimetri/onvif-products-directory):
1161
- This project is a comprehensive ONVIF data aggregation and management suite, designed to help developers explore, analyze, and process ONVIF-compliant product information from hundreds of manufacturers worldwide. It provides a unified structure for device, client, and company data, making it easier to perform research, build integrations, and generate statistics for ONVIF ecosystem analysis.
1178
+ This project is a comprehensive ONVIF data aggregation and management suite, designed to help developers explore, analyze, and process ONVIF-compliant product information from hundreds of manufacturers worldwide.
1162
1179
 
1163
1180
  - (soon) [onvif-rest-server](https://github.com/nirsimetri/onvif-rest-server):
1164
1181
  A RESTful API server for ONVIF devices, enabling easy integration of ONVIF device management, media streaming, and other capabilities into web applications and services.
@@ -1,13 +1,13 @@
1
1
  <h1 align="center">ONVIF Python</h1>
2
2
 
3
3
  <div align="center">
4
- <img alt="Codacy grade" src="https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy" href="https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade">
5
- <img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg" href="https://deepwiki.com/nirsimetri/onvif-python">
6
- <img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.4-orange?logo=archive&color=yellow" href="https://pypi.org/project/onvif-python/">
7
- <img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red" href="https://pepy.tech/projects/onvif-python">
4
+ <a href="https://app.codacy.com/gh/nirsimetri/onvif-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade"><img src="https://app.codacy.com/project/badge/Grade/bff08a94e4d447b690cea49c6594826d"/></a>
5
+ <a href="https://deepwiki.com/nirsimetri/onvif-python"><img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg"></a>
6
+ <a href="https://pypi.org/project/onvif-python/"><img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.6-orange?logo=archive&color=yellow"></a>
7
+ <a href="https://pepy.tech/projects/onvif-python"><img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red"></a>
8
8
  <br>
9
- <img alt="Build" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml">
10
- <img alt="Upload Python Package" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg" href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml">
9
+ <a href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml"><img alt="Build" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-app.yml/badge.svg?branch=main"></a>
10
+ <a href="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml"><img alt="Upload Python Package" src="https://github.com/nirsimetri/onvif-python/actions/workflows/python-publish.yml/badge.svg"></a>
11
11
  </div>
12
12
 
13
13
  <h1 align="center">
@@ -21,15 +21,21 @@
21
21
 
22
22
  Behind the scenes, ONVIF communication relies on **[SOAP](https://en.wikipedia.org/wiki/SOAP) (Simple Object Access Protocol)** — an [XML](https://en.wikipedia.org/wiki/XML)-based messaging protocol with strict schema definitions ([WSDL](https://en.wikipedia.org/wiki/Web_Services_Description_Language)/[XSD](https://en.wikipedia.org/wiki/XML_Schema_(W3C))). SOAP ensures interoperability, but when used directly it can be verbose, complex, and error-prone.
23
23
 
24
- This library simplifies that process by wrapping SOAP communication into a clean, Pythonic API. You no longer need to handle low-level XML parsing, namespaces, or security tokens manually — the library takes care of it, letting you focus on building functionality.
24
+ This library simplifies that process by wrapping SOAP communication into a clean, Pythonic API. You no longer need to handle low-level XML parsing, namespaces, or security tokens manually — the library takes care of it, letting you focus on building functionality.
25
25
 
26
- ## Key Features
27
- - Full implementation of ONVIF core services and profiles
28
- - Support for device discovery, media streaming, PTZ control, event management, and more
29
- - Pythonic abstraction over SOAP requests and responses (no need to handcraft XML)
30
- - Extensible architecture for custom ONVIF extensions
31
- - Compatible with multiple ONVIF specification versions
32
- - Example scripts and tests included
26
+ ## Library Philosophy
27
+ > [!NOTE]
28
+ > This library will be continuously updated as ONVIF versions are updated. It uses a built-in WSDL that will always follow changes to the [ONVIF WSDL Specifications](https://github.com/onvif/specs). You can also use your own ONVIF WSDL file by adding the `wsdl_dir` argument; see [ONVIFClient Parameters](#onvifclient-parameters).
29
+
30
+ - **WYSIWYG (What You See is What You Get)**: Every ONVIF operation in the library mirrors the official ONVIF specification exactly. Method names, parameter structures, and response formats follow ONVIF standards without abstraction layers or renamed interfaces. What you see in the ONVIF documentation is exactly what you get in Python.
31
+
32
+ - **Device Variety Interoperability**: Built to handle the real-world diversity of ONVIF implementations across manufacturers. The library gracefully handles missing features, optional operations, and vendor-specific behaviors through comprehensive error handling and fallback mechanisms. Whether you're working with high-end enterprise cameras or budget IP cameras, the library adapts.
33
+
34
+ - **Official Specifications Accuracy**: All service implementations are generated and validated against official `ONVIF WSDL Specifications`. The library includes comprehensive test suites that verify compliance with ONVIF standards, ensuring that method signatures, parameter types, and behavior match the official specifications precisely.
35
+
36
+ - **Modern Python Approach**: Designed for excellent IDE support with full type hints, auto-completion, and immediate error detection. You'll get `TypeError` exceptions upfront when accessing ONVIF operations with wrong arguments, instead of cryptic `SOAP faults` later. Clean, Pythonic API that feels natural to Python developers while maintaining ONVIF compatibility.
37
+
38
+ - **Minimal Dependencies**: Only depends on essential, well-maintained libraries (`zeep` for SOAP, `requests` for HTTP). No bloated framework dependencies or custom XML parsers. The library stays lightweight while providing full ONVIF functionality, making it easy to integrate into any project without dependency conflicts.
33
39
 
34
40
  ## Who Is It For?
35
41
  - **Individual developers** exploring ONVIF or building hobby projects
@@ -308,11 +314,12 @@ This library includes a powerful command-line interface (CLI) for interacting wi
308
314
  <summary><b>1. Direct CLI</b></summary>
309
315
 
310
316
  ```bash
311
- usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--filter FILTER] [--search SEARCH] [--page PAGE] [--per-page PER_PAGE] [--timeout TIMEOUT] [--https]
312
- [--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL] [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--version]
317
+ usage: onvif [-h] [--host HOST] [--port PORT] [--username USERNAME] [--password PASSWORD] [--discover] [--filter FILTER] [--search SEARCH] [--page PAGE]
318
+ [--per-page PER_PAGE] [--timeout TIMEOUT] [--https] [--no-verify] [--no-patch] [--interactive] [--debug] [--wsdl WSDL]
319
+ [--cache {all,db,mem,none}] [--health-check-interval HEALTH_CHECK_INTERVAL] [--output OUTPUT] [--version]
313
320
  [service] [method] [params ...]
314
321
 
315
- ONVIF Terminal Client — v0.2.4
322
+ ONVIF Terminal Client — v0.2.6
316
323
  https://github.com/nirsimetri/onvif-python
317
324
 
318
325
  positional arguments:
@@ -346,6 +353,9 @@ options:
346
353
  Caching mode for ONVIFClient (default: all). 'all': memory+disk, 'db': disk-only, 'mem': memory-only, 'none': disabled.
347
354
  --health-check-interval HEALTH_CHECK_INTERVAL, -hci HEALTH_CHECK_INTERVAL
348
355
  Health check interval in seconds for interactive mode (default: 10)
356
+ --output OUTPUT, -o OUTPUT
357
+ Save command output to file. Supports .json, .xml extensions for format detection, or plain text. XML format automatically enables
358
+ debug mode for SOAP capture.
349
359
  --version, -v Show ONVIF CLI version and exit
350
360
 
351
361
  Examples:
@@ -366,7 +376,12 @@ Examples:
366
376
 
367
377
  # Direct command execution
368
378
  onvif devicemgmt GetCapabilities Category=All --host 192.168.1.17 --port 8000 --username admin --password admin123
369
- onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity={"PanTilt": {"x": -0.1, "y": 0}} --host 192.168.1.17 --port 8000 --username admin --password admin123
379
+ onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity={'PanTilt': {'x': -0.1, 'y': 0}} -H 192.168.1.17 -P 8000 -u admin -p admin123
380
+
381
+ # Save output to file
382
+ onvif devicemgmt GetDeviceInformation --host 192.168.1.17 --port 8000 --username admin --password admin123 --output device_info.json
383
+ onvif media GetProfiles --host 192.168.1.17 --port 8000 --username admin --password admin123 --output profiles.xml
384
+ onvif ptz GetConfigurations --host 192.168.1.17 --port 8000 --username admin --password admin123 --output ptz_config.txt --debug
370
385
 
371
386
  # Interactive mode
372
387
  onvif --host 192.168.1.17 --port 8000 --username admin --password admin123 --interactive
@@ -385,7 +400,7 @@ Examples:
385
400
  <summary><b>2. Interactive Shell</b></summary>
386
401
 
387
402
  ```bash
388
- ONVIF Interactive Shell — v0.2.4
403
+ ONVIF Interactive Shell — v0.2.6
389
404
  https://github.com/nirsimetri/onvif-python
390
405
 
391
406
  Basic Commands:
@@ -593,7 +608,11 @@ onvif <service> <method> [parameters...] -H <host> -P <port> -u <user> -p <pass>
593
608
  onvif devicemgmt GetCapabilities Category=All -H 192.168.1.17 -P 8000 -u admin -p admin123
594
609
 
595
610
  # Move a PTZ camera
596
- onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity='{"PanTilt": {"x": 0.1}}' -H 192.168.1.17 -P 8000 -u admin -p admin123
611
+ onvif ptz ContinuousMove ProfileToken=Profile_1 Velocity='{"PanTilt": {"x": 0.1, "y": 0}}' -H 192.168.1.17 -P 8000 -u admin -p admin123
612
+
613
+ # Save output to file
614
+ onvif devicemgmt GetDeviceInformation --host 192.168.1.17 --port 8000 --username admin --password admin123 --output device_info.json
615
+ onvif media GetProfiles -H 192.168.1.17 -P 8000 -u admin -p admin123 -o profiles.xml
597
616
  ```
598
617
 
599
618
  **4. ONVIF Product Search**
@@ -1109,13 +1128,11 @@ Some ONVIF services have multiple bindings in the same WSDL. These typically inc
1109
1128
  - [ ] Add more usage examples for advanced features.
1110
1129
  - [ ] Add benchmarking and performance metrics.
1111
1130
  - [ ] Add community-contributed device configuration templates.
1112
- - [ ] Implement missing or partial ONVIF services.
1113
- - [ ] Add function to expose ONVIF devices (for debugging purposes by the community).
1114
1131
 
1115
1132
  ## Related Projects
1116
1133
 
1117
1134
  - [onvif-products-directory](https://github.com/nirsimetri/onvif-products-directory):
1118
- This project is a comprehensive ONVIF data aggregation and management suite, designed to help developers explore, analyze, and process ONVIF-compliant product information from hundreds of manufacturers worldwide. It provides a unified structure for device, client, and company data, making it easier to perform research, build integrations, and generate statistics for ONVIF ecosystem analysis.
1135
+ This project is a comprehensive ONVIF data aggregation and management suite, designed to help developers explore, analyze, and process ONVIF-compliant product information from hundreds of manufacturers worldwide.
1119
1136
 
1120
1137
  - (soon) [onvif-rest-server](https://github.com/nirsimetri/onvif-rest-server):
1121
1138
  A RESTful API server for ONVIF devices, enabling easy integration of ONVIF device management, media streaming, and other capabilities into web applications and services.
@@ -1,7 +1,7 @@
1
1
  # onvif/__init__.py
2
2
 
3
3
  from .client import ONVIFClient
4
- from .operator import ONVIFOperator, CacheMode
4
+ from .operator import CacheMode
5
5
  from .utils import (
6
6
  ONVIFWSDL,
7
7
  ONVIFOperationException,
@@ -14,7 +14,6 @@ from .cli import main as ONVIFCLI
14
14
 
15
15
  __all__ = [
16
16
  "ONVIFClient",
17
- "ONVIFOperator",
18
17
  "CacheMode",
19
18
  "ONVIFWSDL",
20
19
  "ONVIFOperationException",
@@ -194,7 +194,7 @@ class InteractiveShell(cmd.Cmd):
194
194
  " / __ \\/ | / / | / / _/ ____/",
195
195
  " / / / / |/ /| | / // // /_ ",
196
196
  "/ /_/ / /| / | |/ // // __/ ",
197
- "\\____/_/ |_/ |___/___/_/ v0.2.4",
197
+ "\\____/_/ |_/ |___/___/_/ v0.2.6",
198
198
  " ",
199
199
  ]
200
200
 
@@ -1400,7 +1400,7 @@ class InteractiveShell(cmd.Cmd):
1400
1400
  super().do_help(line)
1401
1401
  else:
1402
1402
  help_text = f"""
1403
- {colorize('ONVIF Interactive Shell — v0.2.4', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1403
+ {colorize('ONVIF Interactive Shell — v0.2.6', 'cyan')}\n{colorize('https://github.com/nirsimetri/onvif-python', 'white')}
1404
1404
 
1405
1405
  {colorize('Basic Commands:', 'yellow')}
1406
1406
  capabilities, caps - Show device capabilities
@@ -7,6 +7,7 @@ import getpass
7
7
  import sqlite3
8
8
  import os
9
9
  import shutil
10
+ import json
10
11
  from datetime import datetime
11
12
  from typing import Any, Optional, Tuple
12
13
 
@@ -21,7 +22,7 @@ def create_parser():
21
22
  """Create argument parser for ONVIF CLI"""
22
23
  parser = argparse.ArgumentParser(
23
24
  prog="onvif",
24
- description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.2.4\nhttps://github.com/nirsimetri/onvif-python",
25
+ description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.2.6\nhttps://github.com/nirsimetri/onvif-python",
25
26
  formatter_class=argparse.RawDescriptionHelpFormatter,
26
27
  epilog=f"""
27
28
  Examples:
@@ -42,7 +43,12 @@ Examples:
42
43
 
43
44
  # Direct command execution
44
45
  {colorize('onvif', 'yellow')} devicemgmt GetCapabilities Category=All --host 192.168.1.17 --port 8000 --username admin --password admin123
45
- {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
46
+ {colorize('onvif', 'yellow')} ptz ContinuousMove ProfileToken=Profile_1 Velocity={{'PanTilt': {{'x': -0.1, 'y': 0}}}} -H 192.168.1.17 -P 8000 -u admin -p admin123
47
+
48
+ # Save output to file
49
+ {colorize('onvif', 'yellow')} devicemgmt GetDeviceInformation --host 192.168.1.17 --port 8000 --username admin --password admin123 --output device_info.json
50
+ {colorize('onvif', 'yellow')} media GetProfiles --host 192.168.1.17 --port 8000 --username admin --password admin123 --output profiles.xml
51
+ {colorize('onvif', 'yellow')} ptz GetConfigurations --host 192.168.1.17 --port 8000 --username admin --password admin123 --output ptz_config.txt --debug
46
52
 
47
53
  # Interactive mode
48
54
  {colorize('onvif', 'yellow')} --host 192.168.1.17 --port 8000 --username admin --password admin123 --interactive
@@ -139,6 +145,11 @@ Examples:
139
145
  default=10,
140
146
  help="Health check interval in seconds for interactive mode (default: 10)",
141
147
  )
148
+ parser.add_argument(
149
+ "--output",
150
+ "-o",
151
+ help="Save command output to file. Supports .json, .xml extensions for format detection, or plain text. XML format automatically enables debug mode for SOAP capture.",
152
+ )
142
153
 
143
154
  # Service and method (for direct command execution)
144
155
  parser.add_argument(
@@ -177,7 +188,7 @@ def main():
177
188
 
178
189
  # Show ONVIF CLI version
179
190
  if args.version:
180
- print(colorize("0.2.4", "yellow"))
191
+ print(colorize("0.2.6", "yellow"))
181
192
  sys.exit(0)
182
193
 
183
194
  # Handle product search
@@ -196,6 +207,12 @@ def main():
196
207
  f"Either {colorize('--interactive', 'white')}/{colorize('-i', 'white')} mode or {colorize('service/method', 'white')} must be specified"
197
208
  )
198
209
 
210
+ # Validate output argument
211
+ if args.output and args.interactive:
212
+ parser.error(
213
+ f"{colorize('--output', 'white')} cannot be used with {colorize('--interactive', 'white')} mode"
214
+ )
215
+
199
216
  # Handle discovery mode
200
217
  if args.discover:
201
218
  if args.host:
@@ -260,6 +277,11 @@ def main():
260
277
 
261
278
  try:
262
279
  # Create ONVIF client
280
+ # Auto-enable debug mode if output format is XML
281
+ auto_debug = args.debug or (
282
+ args.output and args.output.lower().endswith(".xml")
283
+ )
284
+
263
285
  client = ONVIFClient(
264
286
  host=args.host,
265
287
  port=args.port,
@@ -270,7 +292,7 @@ def main():
270
292
  use_https=args.https,
271
293
  verify_ssl=not args.no_verify,
272
294
  apply_patch=not args.no_patch,
273
- capture_xml=args.debug,
295
+ capture_xml=auto_debug,
274
296
  wsdl_dir=args.wsdl,
275
297
  )
276
298
 
@@ -298,7 +320,17 @@ def main():
298
320
  # Execute direct command
299
321
  params_str = " ".join(args.params) if args.params else None
300
322
  result = execute_command(client, args.service, args.method, params_str)
301
- print(str(result))
323
+
324
+ # Save output to file if specified
325
+ if args.output:
326
+ # Auto-enable debug mode for XML output
327
+ effective_debug = args.debug or args.output.lower().endswith(".xml")
328
+ save_output_to_file(result, args.output, effective_debug, client)
329
+ print(
330
+ f"{colorize('Output saved to:', 'green')} {colorize(args.output, 'white')}"
331
+ )
332
+ else:
333
+ print(str(result))
302
334
 
303
335
  except KeyboardInterrupt:
304
336
  print("\nOperation cancelled by user")
@@ -337,6 +369,182 @@ def execute_command(
337
369
  return method(**params)
338
370
 
339
371
 
372
+ def save_output_to_file(
373
+ result: Any, output_path: str, debug_mode: bool, client: ONVIFClient
374
+ ) -> None:
375
+ """Save command output to file in appropriate format based on file extension.
376
+
377
+ Args:
378
+ result: The ONVIF command result
379
+ output_path: Path to output file
380
+ debug_mode: Whether debug mode is enabled (for XML capture)
381
+ client: ONVIFClient instance (for accessing XML plugin)
382
+ """
383
+ try:
384
+ # Determine output format based on file extension
385
+ _, ext = os.path.splitext(output_path.lower())
386
+
387
+ if ext == ".json":
388
+ # Prepare output data
389
+ output_data = {}
390
+
391
+ # JSON format
392
+ output_data["result"] = _serialize_for_json(result)
393
+ output_data["timestamp"] = datetime.now().isoformat()
394
+ output_data["raw_result"] = str(result) # Add raw string as fallback
395
+
396
+ # Add XML data if debug mode is enabled and XML plugin is available
397
+ if debug_mode and client.xml_plugin:
398
+ output_data["debug"] = {
399
+ "last_request_xml": client.xml_plugin.last_sent_xml,
400
+ "last_response_xml": client.xml_plugin.last_received_xml,
401
+ "last_operation": client.xml_plugin.last_operation,
402
+ }
403
+
404
+ with open(output_path, "w", encoding="utf-8") as f:
405
+ json.dump(output_data, f, indent=2, ensure_ascii=False)
406
+
407
+ elif ext == ".xml":
408
+ # XML format - prioritize raw SOAP XML over parsed result
409
+ if client.xml_plugin and client.xml_plugin.last_received_xml:
410
+ # Save the raw SOAP response XML with minimal wrapper
411
+ content = f"""<?xml version="1.0" encoding="UTF-8"?>
412
+ <!-- ONVIF SOAP Response -->
413
+ <!-- Timestamp: {datetime.now().isoformat()} -->
414
+ <!-- Operation: {client.xml_plugin.last_operation or 'Unknown'} -->
415
+
416
+ {client.xml_plugin.last_received_xml}
417
+ """
418
+ else:
419
+ # Fallback: Simple XML wrapper for the parsed result
420
+ content = f"""<?xml version="1.0" encoding="UTF-8"?>
421
+ <!-- ONVIF Command Output (Parsed Result) -->
422
+ <!-- Timestamp: {datetime.now().isoformat()} -->
423
+ <!-- Note: Raw SOAP XML not available. Enable --debug for full SOAP capture. -->
424
+ <onvif_result>
425
+ <![CDATA[
426
+ {str(result)}
427
+ ]]>
428
+ </onvif_result>
429
+ """
430
+
431
+ with open(output_path, "w", encoding="utf-8") as f:
432
+ f.write(content)
433
+
434
+ else:
435
+ # Plain text format (default)
436
+ content = "ONVIF Command Output\n"
437
+ content += f"Timestamp: {datetime.now().isoformat()}\n"
438
+ content += f"{'='*50}\n\n"
439
+ content += str(result)
440
+
441
+ # Add debug information if available
442
+ if debug_mode and client.xml_plugin:
443
+ content += f"\n\n{'='*50}\n"
444
+ content += "DEBUG INFORMATION\n"
445
+ content += f"{'='*50}\n"
446
+ if client.xml_plugin.last_operation:
447
+ content += f"Operation: {client.xml_plugin.last_operation}\n\n"
448
+ if client.xml_plugin.last_sent_xml:
449
+ content += "SOAP Request:\n"
450
+ content += client.xml_plugin.last_sent_xml + "\n\n"
451
+ if client.xml_plugin.last_received_xml:
452
+ content += "SOAP Response:\n"
453
+ content += client.xml_plugin.last_received_xml + "\n"
454
+
455
+ with open(output_path, "w", encoding="utf-8") as f:
456
+ f.write(content)
457
+
458
+ except Exception as e:
459
+ print(f"{colorize('Error saving output:', 'red')} {e}", file=sys.stderr)
460
+ # Still print the result to console if file save fails
461
+ print(str(result))
462
+
463
+
464
+ def _serialize_for_json(obj: Any) -> Any:
465
+ """Recursively serialize ONVIF objects for JSON output.
466
+
467
+ Args:
468
+ obj: Object to serialize
469
+
470
+ Returns:
471
+ JSON-serializable representation of the object
472
+ """
473
+ if obj is None:
474
+ return None
475
+ elif isinstance(obj, (str, int, float, bool)):
476
+ return obj
477
+ elif isinstance(obj, datetime):
478
+ return obj.isoformat()
479
+ elif isinstance(obj, (list, tuple)):
480
+ return [_serialize_for_json(item) for item in obj]
481
+ elif isinstance(obj, dict):
482
+ return {key: _serialize_for_json(value) for key, value in obj.items()}
483
+
484
+ # Check if this is a Zeep object (has _xsd_type attribute)
485
+ elif hasattr(obj, "_xsd_type"):
486
+ result = {}
487
+ # Try to get all elements from XSD type
488
+ if hasattr(obj._xsd_type, "elements"):
489
+ for elem_name, elem_obj in obj._xsd_type.elements:
490
+ try:
491
+ value = getattr(obj, elem_name, None)
492
+ if value is not None:
493
+ result[elem_name] = _serialize_for_json(value)
494
+ except (AttributeError, TypeError):
495
+ # Skip elements that can't be accessed or have type issues
496
+ pass
497
+
498
+ # Also try regular attributes
499
+ for attr_name in dir(obj):
500
+ if not attr_name.startswith("_") and not callable(
501
+ getattr(obj, attr_name, None)
502
+ ):
503
+ try:
504
+ attr_value = getattr(obj, attr_name)
505
+ if attr_value is not None and attr_name not in result:
506
+ result[attr_name] = _serialize_for_json(attr_value)
507
+ except (AttributeError, TypeError):
508
+ # Skip attributes that can't be accessed or have type issues
509
+ pass
510
+
511
+ return result
512
+
513
+ elif hasattr(obj, "__dict__"):
514
+ # Handle regular objects with attributes
515
+ result = {}
516
+ for key, value in obj.__dict__.items():
517
+ if not key.startswith("_"): # Skip private attributes
518
+ result[key] = _serialize_for_json(value)
519
+
520
+ # If result is empty, try to get attributes using dir()
521
+ if not result:
522
+ for attr_name in dir(obj):
523
+ if not attr_name.startswith("_") and not callable(
524
+ getattr(obj, attr_name, None)
525
+ ):
526
+ try:
527
+ attr_value = getattr(obj, attr_name)
528
+ if attr_value is not None:
529
+ result[attr_name] = _serialize_for_json(attr_value)
530
+ except (AttributeError, TypeError):
531
+ # Skip attributes that can't be accessed or have type issues
532
+ pass
533
+
534
+ return result
535
+ elif hasattr(obj, "_value_1"):
536
+ # Handle zeep objects with special structure
537
+ return _serialize_for_json(obj._value_1)
538
+ else:
539
+ # Try to convert to dict using vars() if available
540
+ try:
541
+ obj_dict = vars(obj)
542
+ return _serialize_for_json(obj_dict)
543
+ except TypeError:
544
+ # Fallback to string representation
545
+ return str(obj)
546
+
547
+
340
548
  def discover_devices(
341
549
  timeout: int = 4, prefer_https: bool = False, filter_term: Optional[str] = None
342
550
  ) -> list:
@@ -411,3 +411,6 @@ class Device(ONVIFService):
411
411
 
412
412
  def SetHashingAlgorithm(self, Algorithm):
413
413
  return self.operator.call("SetHashingAlgorithm", Algorithm=Algorithm)
414
+
415
+ def UpgradeFirmware(self, Version):
416
+ return self.operator.call("UpgradeFirmware", Version=Version)
@@ -313,3 +313,20 @@ class Media2(ONVIFService):
313
313
 
314
314
  def GetPlayingAudioClips(self):
315
315
  return self.operator.call("GetPlayingAudioClips")
316
+
317
+ def GetMulticastAudioDecoderConfigurationOptions(self, ConfigurationToken=None):
318
+ return self.operator.call(
319
+ "GetMulticastAudioDecoderConfigurationOptions",
320
+ ConfigurationToken=ConfigurationToken,
321
+ )
322
+
323
+ def GetMulticastAudioDecoderConfigurations(self, ConfigurationToken=None):
324
+ return self.operator.call(
325
+ "GetMulticastAudioDecoderConfigurations",
326
+ ConfigurationToken=ConfigurationToken,
327
+ )
328
+
329
+ def SetMulticastAudioDecoderConfiguration(self, Configuration):
330
+ return self.operator.call(
331
+ "SetMulticastAudioDecoderConfiguration", Configuration=Configuration
332
+ )