onvif-python 0.0.8__tar.gz → 0.0.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. {onvif_python-0.0.8/onvif_python.egg-info → onvif_python-0.0.9}/PKG-INFO +62 -31
  2. {onvif_python-0.0.8 → onvif_python-0.0.9}/README.md +61 -27
  3. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/client.py +113 -43
  4. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/operator.py +3 -3
  5. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/uplink.py +1 -1
  6. {onvif_python-0.0.8 → onvif_python-0.0.9/onvif_python.egg-info}/PKG-INFO +62 -31
  7. {onvif_python-0.0.8 → onvif_python-0.0.9}/pyproject.toml +1 -4
  8. {onvif_python-0.0.8 → onvif_python-0.0.9}/LICENSE.md +0 -0
  9. {onvif_python-0.0.8 → onvif_python-0.0.9}/MANIFEST.in +0 -0
  10. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/__init__.py +0 -0
  11. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/__init__.py +0 -0
  12. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/accesscontrol.py +0 -0
  13. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/accessrules.py +0 -0
  14. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/actionengine.py +0 -0
  15. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/analytics/analytics.py +0 -0
  16. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/analytics/ruleengine.py +0 -0
  17. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/analyticsdevice.py +0 -0
  18. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/appmgmt.py +0 -0
  19. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/authenticationbehavior.py +0 -0
  20. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/credential.py +0 -0
  21. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/deviceio.py +0 -0
  22. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/devicemgmt.py +0 -0
  23. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/display.py +0 -0
  24. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/doorcontrol.py +0 -0
  25. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/events/events.py +0 -0
  26. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/events/notification.py +0 -0
  27. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/events/pullpoint.py +0 -0
  28. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/events/subscription.py +0 -0
  29. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/imaging.py +0 -0
  30. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/media.py +0 -0
  31. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/media2.py +0 -0
  32. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/provisioning.py +0 -0
  33. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/ptz.py +0 -0
  34. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/receiver.py +0 -0
  35. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/recording.py +0 -0
  36. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/replay.py +0 -0
  37. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/schedule.py +0 -0
  38. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/search.py +0 -0
  39. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/security/advancedsecurity.py +0 -0
  40. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/security/authorizationserver.py +0 -0
  41. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/security/dot1x.py +0 -0
  42. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/security/jwt.py +0 -0
  43. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/security/keystore.py +0 -0
  44. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/security/mediasigning.py +0 -0
  45. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/security/tlsserver.py +0 -0
  46. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/services/thermal.py +0 -0
  47. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/utils/__init__.py +0 -0
  48. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/utils/error_handlers.py +0 -0
  49. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/utils/exceptions.py +0 -0
  50. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/utils/wsdl.py +0 -0
  51. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/utils/xml_capture.py +0 -0
  52. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/utils/zeep.py +0 -0
  53. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/accesscontrol/wsdl/index.html +0 -0
  54. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +0 -0
  55. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/accessrules/wsdl/index.html +0 -0
  56. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/actionengine.wsdl +0 -0
  57. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +0 -0
  58. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +0 -0
  59. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/analyticsdevice.wsdl +0 -0
  60. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +0 -0
  61. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/appmgmt/wsdl/index.html +0 -0
  62. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +0 -0
  63. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +0 -0
  64. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/credential/wsdl/credential.wsdl +0 -0
  65. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/credential/wsdl/index.html +0 -0
  66. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +0 -0
  67. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/deviceio.wsdl +0 -0
  68. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/display/display.wsdl +0 -0
  69. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/display/wsdl/index.htm +0 -0
  70. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/display.wsdl +0 -0
  71. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/doorcontrol/wsdl/index.html +0 -0
  72. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +0 -0
  73. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +0 -0
  74. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/event.wsdl +0 -0
  75. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/events/wsdl/index.html +0 -0
  76. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/federatedsearch.wsdl +0 -0
  77. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/media/wsdl/media.wsdl +0 -0
  78. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/pacs/accesscontrol.wsdl +0 -0
  79. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/pacs/doorcontrol.wsdl +0 -0
  80. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/pacs/types.xsd +0 -0
  81. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +0 -0
  82. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/receiver.wsdl +0 -0
  83. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/recording.wsdl +0 -0
  84. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/replay.wsdl +0 -0
  85. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/schedule/wsdl/index.html +0 -0
  86. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +0 -0
  87. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/schema/common.xsd +0 -0
  88. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/schema/metadatastream.xsd +0 -0
  89. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/schema/onvif.xsd +0 -0
  90. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/search.wsdl +0 -0
  91. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/thermal/wsdl/index.html +0 -0
  92. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +0 -0
  93. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/topics/topicns.xml +0 -0
  94. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/uplink/wsdl/index.html +0 -0
  95. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +0 -0
  96. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/humanbody.xsd +0 -0
  97. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/humanface.xsd +0 -0
  98. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/index.html +0 -0
  99. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/radiometry.xsd +0 -0
  100. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/rules.xsd +0 -0
  101. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +0 -0
  102. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/analytics/wsdl/index.html +0 -0
  103. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +0 -0
  104. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/media/wsdl/index.html +0 -0
  105. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/media/wsdl/media.wsdl +0 -0
  106. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +0 -0
  107. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/federated_search_documentation.html +0 -0
  108. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +0 -0
  109. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +0 -0
  110. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif/wsdl/ver20/util/operationIndex.html +0 -0
  111. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif_python.egg-info/SOURCES.txt +0 -0
  112. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif_python.egg-info/dependency_links.txt +0 -0
  113. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif_python.egg-info/requires.txt +0 -0
  114. {onvif_python-0.0.8 → onvif_python-0.0.9}/onvif_python.egg-info/top_level.txt +0 -0
  115. {onvif_python-0.0.8 → onvif_python-0.0.9}/setup.cfg +0 -0
  116. {onvif_python-0.0.8 → onvif_python-0.0.9}/setup.py +0 -0
  117. {onvif_python-0.0.8 → onvif_python-0.0.9}/tests/test_client.py +0 -0
  118. {onvif_python-0.0.8 → onvif_python-0.0.9}/tests/test_core.py +0 -0
  119. {onvif_python-0.0.8 → onvif_python-0.0.9}/tests/test_exceptions.py +0 -0
  120. {onvif_python-0.0.8 → onvif_python-0.0.9}/tests/test_wsdl_map.py +0 -0
@@ -1,14 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onvif-python
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: A comprehensive and developer-friendly Python library for working with ONVIF-compliant devices
5
5
  Author-email: Nirsimetri Technologies® <open@nirsimetri.com>
6
6
  Maintainer: Kaburagi
7
7
  License-Expression: MIT
8
- Project-URL: Homepage, https://github.com/nirsimetri/onvif-python
9
- Project-URL: Issues, https://github.com/nirsimetri/onvif-python/issues
10
8
  Project-URL: Documentation, https://deepwiki.com/nirsimetri/onvif-python
11
- Project-URL: Repository, https://github.com/nirsimetri/onvif-python.git
12
9
  Project-URL: Changelog, https://github.com/nirsimetri/onvif-python/releases
13
10
  Keywords: onvif,Camera,onvif-python,ip-camera,cctv,soap,surveillance,cctv cameras,soap-client,onvif-client,onvif-library,onvif-specifications
14
11
  Classifier: Development Status :: 5 - Production/Stable
@@ -43,9 +40,9 @@ Dynamic: license-file
43
40
 
44
41
  [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
45
42
  [![DeepWiki](https://img.shields.io/badge/DeepWiki-AI%20Wiki-orange)](https://deepwiki.com/nirsimetri/onvif-python)
46
- [![Release](https://img.shields.io/badge/Release-v0.0.8-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
43
+ [![Release](https://img.shields.io/badge/Release-v0.0.9-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
47
44
  <br>
48
- [![PyPI](https://img.shields.io/badge/PyPI-0.0.8-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
45
+ [![PyPI](https://img.shields.io/badge/PyPI-0.0.9-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
49
46
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=PyPI%20Downloads)](https://clickpy.clickhouse.com/dashboard/onvif-python)
50
47
 
51
48
  **This project provides a comprehensive and developer-friendly Python library for working with ONVIF-compliant devices.** It is designed to be reliable, easy to integrate, and flexible enough to support a wide range of ONVIF profiles and services.
@@ -354,7 +351,7 @@ client = ONVIFClient(
354
351
  ## Service Discovery: Understanding Device Capabilities
355
352
 
356
353
  > [!WARNING]
357
- > Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically uses `GetServices` during initialization to discover service endpoints, but you can also query services manually for detailed information including capabilities.
354
+ > Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically performs comprehensive service discovery during initialization using a robust fallback mechanism.
358
355
 
359
356
  **Why discover device services?**
360
357
 
@@ -365,50 +362,84 @@ client = ONVIFClient(
365
362
 
366
363
  **How service discovery works in this library:**
367
364
 
368
- The `ONVIFClient` automatically calls `GetServices` during initialization to build a service map. This map is used internally to resolve service endpoints:
365
+ The `ONVIFClient` uses a **3-tier discovery approach** to maximize device compatibility:
366
+
367
+ 1. **GetServices (Preferred)** - Tries `GetServices` first for detailed service information
368
+ 2. **GetCapabilities (Fallback)** - Falls back to `GetCapabilities` if `GetServices` is not supported
369
+ 3. **Default URLs (Final Fallback)** - Uses standard ONVIF URLs as last resort
369
370
 
370
371
  ```python
371
372
  from onvif import ONVIFClient
372
373
 
373
374
  client = ONVIFClient("192.168.1.17", 8000, "admin", "admin123")
374
375
 
375
- # Access the discovered services
376
- print(client.services)
377
- # Example output: [{'Namespace': 'http://www.onvif.org/ver10/device/wsdl', 'XAddr': '...', 'Version': {...}}, ...]
378
-
379
- # Check the service map (namespace -> XAddr mapping)
380
- print(client._service_map)
381
- # Example output: {'http://www.onvif.org/ver10/media/wsdl': 'http://192.168.1.17:8000/onvif/Media', ...}
376
+ # Check what discovery method was used
377
+ if client.services:
378
+ print("Service discovery: GetServices (preferred)")
379
+ print("Discovered services:", len(client.services))
380
+ print("Service map:", client._service_map)
381
+ elif client.capabilities:
382
+ print("Service discovery: GetCapabilities (fallback)")
383
+ print("Available capabilities:", client.capabilities)
384
+ else:
385
+ print("Service discovery: Using default URLs")
382
386
  ```
383
387
 
384
- **Get detailed service information with capabilities:**
388
+ **Why this approach?**
389
+
390
+ - **GetServices** provides the most accurate and detailed service information, but it's **optional** in the ONVIF specification
391
+ - **GetCapabilities** is **mandatory** for all ONVIF-compliant devices, ensuring broader compatibility
392
+ - **Default URLs** guarantee basic connectivity even with non-compliant devices
385
393
 
386
- If you need detailed capability information for each service, call `GetServices` with `IncludeCapability=True`:
394
+ **Get detailed service information:**
395
+
396
+ If you need comprehensive service details, you can manually call `GetServices` with capabilities:
387
397
 
388
398
  ```python
389
399
  device = client.devicemgmt()
390
- services = device.GetServices(IncludeCapability=True)
391
-
392
- for service in services:
393
- print(f"Service: {service.Namespace}")
394
- print(f"Endpoint: {service.XAddr}")
395
- print(f"Version: {service.Version.Major}.{service.Version.Minor}")
396
- if hasattr(service, 'Capabilities') and service.Capabilities:
397
- print(f"Capabilities: {service.Capabilities}")
400
+
401
+ try:
402
+ # Try to get detailed service information
403
+ services = device.GetServices(IncludeCapability=True)
404
+ for service in services:
405
+ print(f"Service: {service.Namespace}")
406
+ print(f"XAddr: {service.XAddr}")
407
+ if hasattr(service, 'Capabilities'):
408
+ print(f"Capabilities: {service.Capabilities}")
409
+ except Exception:
410
+ # Fallback to GetCapabilities for legacy devices
411
+ capabilities = device.GetCapabilities()
412
+ print("Capabilities:", capabilities)
398
413
  ```
399
414
 
400
- **Alternative: Use GetCapabilities for legacy compatibility:**
415
+ **Access capabilities information:**
401
416
 
402
- For backward compatibility or when you need a quick overview of major service categories, you can still use `GetCapabilities`:
417
+ When using `GetCapabilities` fallback, you can access capability information:
403
418
 
404
419
  ```python
405
- capabilities = client.devicemgmt().GetCapabilities()
406
- print(capabilities)
407
- # Example output: {'Media': {'XAddr': '...', ...}, 'PTZ': {...}, 'Events': {...}, ...}
420
+ device = client.devicemgmt()
421
+
422
+ try:
423
+ # Get capabilities information from device
424
+ capabilities = device.GetCapabilities()
425
+
426
+ # Main services (always available)
427
+ print("Media XAddr:", getattr(capabilities.Media, 'XAddr', 'Not available'))
428
+ print("PTZ XAddr:", getattr(capabilities.PTZ, 'XAddr', 'Not available'))
429
+
430
+ # Extension services (device-dependent)
431
+ ext = getattr(capabilities, 'Extension', None)
432
+ if ext:
433
+ print("DeviceIO XAddr:", getattr(ext.DeviceIO, 'XAddr', 'Not available'))
434
+ print("Recording XAddr:", getattr(ext.Recording, 'XAddr', 'Not available'))
435
+ print("Search XAddr:", getattr(ext.Search, 'XAddr', 'Not available'))
436
+ print("Replay XAddr:", getattr(ext.Replay, 'XAddr', 'Not available'))
437
+ except Exception as e:
438
+ print(f"Error getting capabilities: {e}")
408
439
  ```
409
440
 
410
441
  > [!TIP]
411
- > The library handles service discovery automatically, so you typically don't need to call `GetServices` manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
442
+ > The library handles service discovery automatically with intelligent fallback. You typically don't need to call discovery methods manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
412
443
 
413
444
  ## Tested Devices
414
445
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
4
4
  [![DeepWiki](https://img.shields.io/badge/DeepWiki-AI%20Wiki-orange)](https://deepwiki.com/nirsimetri/onvif-python)
5
- [![Release](https://img.shields.io/badge/Release-v0.0.8-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
5
+ [![Release](https://img.shields.io/badge/Release-v0.0.9-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
6
6
  <br>
7
- [![PyPI](https://img.shields.io/badge/PyPI-0.0.8-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
7
+ [![PyPI](https://img.shields.io/badge/PyPI-0.0.9-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
8
8
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=PyPI%20Downloads)](https://clickpy.clickhouse.com/dashboard/onvif-python)
9
9
 
10
10
  **This project provides a comprehensive and developer-friendly Python library for working with ONVIF-compliant devices.** It is designed to be reliable, easy to integrate, and flexible enough to support a wide range of ONVIF profiles and services.
@@ -313,7 +313,7 @@ client = ONVIFClient(
313
313
  ## Service Discovery: Understanding Device Capabilities
314
314
 
315
315
  > [!WARNING]
316
- > Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically uses `GetServices` during initialization to discover service endpoints, but you can also query services manually for detailed information including capabilities.
316
+ > Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically performs comprehensive service discovery during initialization using a robust fallback mechanism.
317
317
 
318
318
  **Why discover device services?**
319
319
 
@@ -324,50 +324,84 @@ client = ONVIFClient(
324
324
 
325
325
  **How service discovery works in this library:**
326
326
 
327
- The `ONVIFClient` automatically calls `GetServices` during initialization to build a service map. This map is used internally to resolve service endpoints:
327
+ The `ONVIFClient` uses a **3-tier discovery approach** to maximize device compatibility:
328
+
329
+ 1. **GetServices (Preferred)** - Tries `GetServices` first for detailed service information
330
+ 2. **GetCapabilities (Fallback)** - Falls back to `GetCapabilities` if `GetServices` is not supported
331
+ 3. **Default URLs (Final Fallback)** - Uses standard ONVIF URLs as last resort
328
332
 
329
333
  ```python
330
334
  from onvif import ONVIFClient
331
335
 
332
336
  client = ONVIFClient("192.168.1.17", 8000, "admin", "admin123")
333
337
 
334
- # Access the discovered services
335
- print(client.services)
336
- # Example output: [{'Namespace': 'http://www.onvif.org/ver10/device/wsdl', 'XAddr': '...', 'Version': {...}}, ...]
337
-
338
- # Check the service map (namespace -> XAddr mapping)
339
- print(client._service_map)
340
- # Example output: {'http://www.onvif.org/ver10/media/wsdl': 'http://192.168.1.17:8000/onvif/Media', ...}
338
+ # Check what discovery method was used
339
+ if client.services:
340
+ print("Service discovery: GetServices (preferred)")
341
+ print("Discovered services:", len(client.services))
342
+ print("Service map:", client._service_map)
343
+ elif client.capabilities:
344
+ print("Service discovery: GetCapabilities (fallback)")
345
+ print("Available capabilities:", client.capabilities)
346
+ else:
347
+ print("Service discovery: Using default URLs")
341
348
  ```
342
349
 
343
- **Get detailed service information with capabilities:**
350
+ **Why this approach?**
351
+
352
+ - **GetServices** provides the most accurate and detailed service information, but it's **optional** in the ONVIF specification
353
+ - **GetCapabilities** is **mandatory** for all ONVIF-compliant devices, ensuring broader compatibility
354
+ - **Default URLs** guarantee basic connectivity even with non-compliant devices
344
355
 
345
- If you need detailed capability information for each service, call `GetServices` with `IncludeCapability=True`:
356
+ **Get detailed service information:**
357
+
358
+ If you need comprehensive service details, you can manually call `GetServices` with capabilities:
346
359
 
347
360
  ```python
348
361
  device = client.devicemgmt()
349
- services = device.GetServices(IncludeCapability=True)
350
-
351
- for service in services:
352
- print(f"Service: {service.Namespace}")
353
- print(f"Endpoint: {service.XAddr}")
354
- print(f"Version: {service.Version.Major}.{service.Version.Minor}")
355
- if hasattr(service, 'Capabilities') and service.Capabilities:
356
- print(f"Capabilities: {service.Capabilities}")
362
+
363
+ try:
364
+ # Try to get detailed service information
365
+ services = device.GetServices(IncludeCapability=True)
366
+ for service in services:
367
+ print(f"Service: {service.Namespace}")
368
+ print(f"XAddr: {service.XAddr}")
369
+ if hasattr(service, 'Capabilities'):
370
+ print(f"Capabilities: {service.Capabilities}")
371
+ except Exception:
372
+ # Fallback to GetCapabilities for legacy devices
373
+ capabilities = device.GetCapabilities()
374
+ print("Capabilities:", capabilities)
357
375
  ```
358
376
 
359
- **Alternative: Use GetCapabilities for legacy compatibility:**
377
+ **Access capabilities information:**
360
378
 
361
- For backward compatibility or when you need a quick overview of major service categories, you can still use `GetCapabilities`:
379
+ When using `GetCapabilities` fallback, you can access capability information:
362
380
 
363
381
  ```python
364
- capabilities = client.devicemgmt().GetCapabilities()
365
- print(capabilities)
366
- # Example output: {'Media': {'XAddr': '...', ...}, 'PTZ': {...}, 'Events': {...}, ...}
382
+ device = client.devicemgmt()
383
+
384
+ try:
385
+ # Get capabilities information from device
386
+ capabilities = device.GetCapabilities()
387
+
388
+ # Main services (always available)
389
+ print("Media XAddr:", getattr(capabilities.Media, 'XAddr', 'Not available'))
390
+ print("PTZ XAddr:", getattr(capabilities.PTZ, 'XAddr', 'Not available'))
391
+
392
+ # Extension services (device-dependent)
393
+ ext = getattr(capabilities, 'Extension', None)
394
+ if ext:
395
+ print("DeviceIO XAddr:", getattr(ext.DeviceIO, 'XAddr', 'Not available'))
396
+ print("Recording XAddr:", getattr(ext.Recording, 'XAddr', 'Not available'))
397
+ print("Search XAddr:", getattr(ext.Search, 'XAddr', 'Not available'))
398
+ print("Replay XAddr:", getattr(ext.Replay, 'XAddr', 'Not available'))
399
+ except Exception as e:
400
+ print(f"Error getting capabilities: {e}")
367
401
  ```
368
402
 
369
403
  > [!TIP]
370
- > The library handles service discovery automatically, so you typically don't need to call `GetServices` manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
404
+ > The library handles service discovery automatically with intelligent fallback. You typically don't need to call discovery methods manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
371
405
 
372
406
  ## Tested Devices
373
407
 
@@ -58,8 +58,6 @@ class ONVIFClient:
58
58
  apply_patch=True,
59
59
  capture_xml=False,
60
60
  ):
61
- self.apply_patch = apply_patch
62
-
63
61
  # Apply or remove zeep patch based on user preference
64
62
  if apply_patch:
65
63
  ZeepPatcher.apply_patch()
@@ -81,22 +79,37 @@ class ONVIFClient:
81
79
  "cache": cache,
82
80
  "use_https": use_https,
83
81
  "verify_ssl": verify_ssl,
84
- "apply_flatten": apply_patch,
82
+ "apply_patch": apply_patch,
85
83
  "plugins": [self.xml_plugin] if self.xml_plugin else None,
86
84
  }
87
85
 
88
86
  # Device Management (Core) service is always available
89
87
  self._devicemgmt = Device(**self.common_args)
90
88
 
91
- # Retrieve device services once and create namespace -> XAddr mapping
92
- self.services = self._devicemgmt.GetServices(IncludeCapability=False)
89
+ # Try to retrieve device services and create namespace -> XAddr mapping
90
+ self.services = None
93
91
  self._service_map = {}
94
- for service in self.services:
95
- namespace = getattr(service, "Namespace", None)
96
- xaddr = getattr(service, "XAddr", None)
92
+
93
+ # Temporary variable to hold capabilities
94
+ self.capabilities = None
95
+
96
+ try:
97
+ # Try GetServices first (preferred method)
98
+ self.services = self._devicemgmt.GetServices(IncludeCapability=False)
99
+ for service in self.services:
100
+ namespace = getattr(service, "Namespace", None)
101
+ xaddr = getattr(service, "XAddr", None)
97
102
 
98
- if namespace and xaddr:
99
- self._service_map[namespace] = xaddr
103
+ if namespace and xaddr:
104
+ self._service_map[namespace] = xaddr
105
+
106
+ except Exception:
107
+ # Fallback to GetCapabilities if GetServices is not supported on device
108
+ try:
109
+ self.capabilities = self._devicemgmt.GetCapabilities(Category="All")
110
+ except Exception:
111
+ # If both fail, we'll use default URLs
112
+ pass
100
113
 
101
114
  # Lazy init for other services
102
115
 
@@ -158,49 +171,106 @@ class ONVIFClient:
158
171
 
159
172
  def _get_xaddr(self, service_name: str, service_path: str):
160
173
  """
161
- Resolve XAddr from GetServices based on namespace mapping.
162
- Falls back to default URL if service not found.
174
+ Resolve XAddr for ONVIF services using a comprehensive 3-tier discovery approach.
175
+
176
+ 1. GetServices: Try to resolve from GetServices response using namespace mapping
177
+ 2. GetCapabilities: Fall back to GetCapabilities response with multiple lookup strategies:
178
+ - Direct capabilities.service_path
179
+ - Extension capabilities.Extension.service_path
180
+ - Nested Extension capabilities.Extension.Extension.service_path
181
+ 3. Default URL: Generate default ONVIF URL as final fallback
182
+
183
+ Args:
184
+ service_name: Internal service name (e.g., 'imaging', 'media', 'deviceio')
185
+ service_path: ONVIF service path (e.g., 'Imaging', 'Media', 'DeviceIO')
186
+
187
+ Returns:
188
+ str: The resolved XAddr URL for the service
189
+
190
+ Notes:
191
+ - GetServices is the preferred method as it provides the most accurate service endpoints.
192
+ But not all devices implement it because it's optional in the ONVIF spec.
193
+ - GetCapabilities lookup tries multiple strategies to maximize chances of finding the XAddr.
194
+ And GetCapabilities is mandatory for ONVIF devices, so it's more widely supported.
195
+ - Fallback to default URL ensures basic connectivity even if the device lacks proper service discovery.
163
196
  """
164
- # Get the namespace for this service from WSDL_MAP
165
- try:
166
- # Try to get the service definition from WSDL_MAP
167
- # Most services use ver10, some use ver20
168
- wsdl_def = None
169
197
 
170
- # Try ver10 first, then ver20
171
- if service_name in ONVIFWSDL.WSDL_MAP:
172
- if "ver10" in ONVIFWSDL.WSDL_MAP[service_name]:
173
- wsdl_def = ONVIFWSDL.WSDL_MAP[service_name]["ver10"]
174
- elif "ver20" in ONVIFWSDL.WSDL_MAP[service_name]:
175
- wsdl_def = ONVIFWSDL.WSDL_MAP[service_name]["ver20"]
198
+ # First try to get from GetServices mapping
199
+ if self.services:
200
+ # Get the namespace for this service from WSDL_MAP
201
+ try:
202
+ # Try to get the service definition from WSDL_MAP
203
+ # Most services use ver10, some use ver20
204
+ wsdl_def = None
205
+
206
+ # Try ver10 first, then ver20
207
+ if service_name in ONVIFWSDL.WSDL_MAP:
208
+ if "ver10" in ONVIFWSDL.WSDL_MAP[service_name]:
209
+ wsdl_def = ONVIFWSDL.WSDL_MAP[service_name]["ver10"]
210
+ elif "ver20" in ONVIFWSDL.WSDL_MAP[service_name]:
211
+ wsdl_def = ONVIFWSDL.WSDL_MAP[service_name]["ver20"]
212
+
213
+ if wsdl_def:
214
+ namespace = wsdl_def["namespace"]
215
+ xaddr = self._service_map.get(namespace)
216
+
217
+ if xaddr:
218
+ # Rewrite host/port if needed
219
+ rewritten = self._rewrite_xaddr_if_needed(xaddr)
220
+ return rewritten
221
+ except Exception:
222
+ pass
176
223
 
177
- if wsdl_def:
178
- namespace = wsdl_def["namespace"]
179
- xaddr = self._service_map.get(namespace)
224
+ # If not found in service map and we have capabilities, try to get it dynamically from GetCapabilities
225
+ if self.capabilities:
226
+ try:
227
+ svc = getattr(self.capabilities, service_path, None)
228
+ # Step 1: check direct attribute capabilities.service_path (e.g. capabilities.Media)
229
+ if svc and hasattr(svc, "XAddr"):
230
+ xaddr = svc.XAddr
231
+ else:
232
+ # Step 2: try capabilities.Extension.service_path (e.g. capabilities.Extension.DeviceIO)
233
+ ext = getattr(self.capabilities, "Extension", None)
234
+ if ext and hasattr(ext, service_path):
235
+ svc = getattr(ext, service_path, None)
236
+ xaddr = getattr(svc, "XAddr", None) if svc else None
237
+ else:
238
+ # Step 3: try capabilities.Extension.Extension.service_path (e.g. capabilities.Extension.Extension.Provisioning)
239
+ ext_ext = getattr(ext, "Extension", None)
240
+ if ext_ext and hasattr(ext_ext, service_path):
241
+ svc = getattr(ext_ext, service_path, None)
242
+ xaddr = getattr(svc, "XAddr", None) if svc else None
180
243
 
181
244
  if xaddr:
182
245
  # Rewrite host/port if needed
183
- parsed = urlparse(xaddr)
184
- device_host = parsed.hostname
185
- device_port = parsed.port
186
- connect_host = self.common_args["host"]
187
- connect_port = self.common_args["port"]
188
-
189
- if (device_host != connect_host) or (device_port != connect_port):
190
- protocol = "https" if self.common_args["use_https"] else "http"
191
- new_netloc = f"{connect_host}:{connect_port}"
192
- rewritten = urlunparse(
193
- (protocol, new_netloc, parsed.path, "", "", "")
194
- )
195
- return rewritten
196
- return xaddr
197
- except Exception:
198
- pass
246
+ rewritten = self._rewrite_xaddr_if_needed(xaddr)
247
+ return rewritten
248
+ except Exception:
249
+ pass
199
250
 
200
251
  # Fallback to default URL
201
252
  protocol = "https" if self.common_args["use_https"] else "http"
202
253
  return f"{protocol}://{self.common_args['host']}:{self.common_args['port']}/onvif/{service_path}"
203
254
 
255
+ def _rewrite_xaddr_if_needed(self, xaddr: str):
256
+ """
257
+ Rewrite XAddr to use client's host/port if different from device's.
258
+ """
259
+ try:
260
+ parsed = urlparse(xaddr)
261
+ device_host = parsed.hostname
262
+ device_port = parsed.port
263
+ connect_host = self.common_args["host"]
264
+ connect_port = self.common_args["port"]
265
+
266
+ if (device_host != connect_host) or (device_port != connect_port):
267
+ protocol = "https" if self.common_args["use_https"] else "http"
268
+ new_netloc = f"{connect_host}:{connect_port}"
269
+ return urlunparse((protocol, new_netloc, parsed.path, "", "", ""))
270
+ return xaddr
271
+ except Exception:
272
+ return xaddr
273
+
204
274
  # Core (Device Management)
205
275
 
206
276
  def devicemgmt(self):
@@ -459,7 +529,7 @@ class ONVIFClient:
459
529
  def search(self):
460
530
  if self._search is None:
461
531
  self._search = Search(
462
- xaddr=self._get_xaddr("search", "SearchRecording"),
532
+ xaddr=self._get_xaddr("search", "Search"),
463
533
  **self.common_args,
464
534
  )
465
535
  return self._search
@@ -51,7 +51,7 @@ class ONVIFOperator:
51
51
  cache_path: str = None,
52
52
  use_https: bool = False,
53
53
  verify_ssl: bool = True,
54
- apply_flatten: bool = True,
54
+ apply_patch: bool = True,
55
55
  plugins: list = None,
56
56
  ):
57
57
  self.wsdl_path = wsdl_path
@@ -60,7 +60,7 @@ class ONVIFOperator:
60
60
  self.username = username
61
61
  self.password = password
62
62
  self.timeout = timeout
63
- self.apply_flatten = apply_flatten
63
+ self.apply_patch = apply_patch
64
64
 
65
65
  if xaddr:
66
66
  self.address = xaddr
@@ -127,7 +127,7 @@ class ONVIFOperator:
127
127
  try:
128
128
  result = func(*args, **kwargs)
129
129
  # Post-process to flatten xsd:any fields if enabled (> v0.0.4 patch)
130
- if self.apply_flatten:
130
+ if self.apply_patch:
131
131
  return ZeepPatcher.flatten_xsd_any_fields(result)
132
132
  return result
133
133
  except Fault as e:
@@ -30,4 +30,4 @@ class Uplink:
30
30
  return self.operator.call("SetUplink", Configuration=Configuration)
31
31
 
32
32
  def DeleteUplink(self, RemoteAddress):
33
- return self.operator.call("SetUplink", RemoteAddress=RemoteAddress)
33
+ return self.operator.call("DeleteUplink", RemoteAddress=RemoteAddress)
@@ -1,14 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onvif-python
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  Summary: A comprehensive and developer-friendly Python library for working with ONVIF-compliant devices
5
5
  Author-email: Nirsimetri Technologies® <open@nirsimetri.com>
6
6
  Maintainer: Kaburagi
7
7
  License-Expression: MIT
8
- Project-URL: Homepage, https://github.com/nirsimetri/onvif-python
9
- Project-URL: Issues, https://github.com/nirsimetri/onvif-python/issues
10
8
  Project-URL: Documentation, https://deepwiki.com/nirsimetri/onvif-python
11
- Project-URL: Repository, https://github.com/nirsimetri/onvif-python.git
12
9
  Project-URL: Changelog, https://github.com/nirsimetri/onvif-python/releases
13
10
  Keywords: onvif,Camera,onvif-python,ip-camera,cctv,soap,surveillance,cctv cameras,soap-client,onvif-client,onvif-library,onvif-specifications
14
11
  Classifier: Development Status :: 5 - Production/Stable
@@ -43,9 +40,9 @@ Dynamic: license-file
43
40
 
44
41
  [![License](https://img.shields.io/badge/License-MIT-blue)](https://github.com/nirsimetri/onvif-python?tab=MIT-1-ov-file)
45
42
  [![DeepWiki](https://img.shields.io/badge/DeepWiki-AI%20Wiki-orange)](https://deepwiki.com/nirsimetri/onvif-python)
46
- [![Release](https://img.shields.io/badge/Release-v0.0.8-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
43
+ [![Release](https://img.shields.io/badge/Release-v0.0.9-red?logo=archive)](https://github.com/nirsimetri/onvif-python/releases)
47
44
  <br>
48
- [![PyPI](https://img.shields.io/badge/PyPI-0.0.8-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
45
+ [![PyPI](https://img.shields.io/badge/PyPI-0.0.9-yellow?logo=archive)](https://pypi.org/project/onvif-python/)
49
46
  [![Downloads](https://img.shields.io/pypi/dm/onvif-python?label=PyPI%20Downloads)](https://clickpy.clickhouse.com/dashboard/onvif-python)
50
47
 
51
48
  **This project provides a comprehensive and developer-friendly Python library for working with ONVIF-compliant devices.** It is designed to be reliable, easy to integrate, and flexible enough to support a wide range of ONVIF profiles and services.
@@ -354,7 +351,7 @@ client = ONVIFClient(
354
351
  ## Service Discovery: Understanding Device Capabilities
355
352
 
356
353
  > [!WARNING]
357
- > Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically uses `GetServices` during initialization to discover service endpoints, but you can also query services manually for detailed information including capabilities.
354
+ > Before performing any operations on an ONVIF device, it is highly recommended to discover which services are available and supported by the device. This library automatically performs comprehensive service discovery during initialization using a robust fallback mechanism.
358
355
 
359
356
  **Why discover device services?**
360
357
 
@@ -365,50 +362,84 @@ client = ONVIFClient(
365
362
 
366
363
  **How service discovery works in this library:**
367
364
 
368
- The `ONVIFClient` automatically calls `GetServices` during initialization to build a service map. This map is used internally to resolve service endpoints:
365
+ The `ONVIFClient` uses a **3-tier discovery approach** to maximize device compatibility:
366
+
367
+ 1. **GetServices (Preferred)** - Tries `GetServices` first for detailed service information
368
+ 2. **GetCapabilities (Fallback)** - Falls back to `GetCapabilities` if `GetServices` is not supported
369
+ 3. **Default URLs (Final Fallback)** - Uses standard ONVIF URLs as last resort
369
370
 
370
371
  ```python
371
372
  from onvif import ONVIFClient
372
373
 
373
374
  client = ONVIFClient("192.168.1.17", 8000, "admin", "admin123")
374
375
 
375
- # Access the discovered services
376
- print(client.services)
377
- # Example output: [{'Namespace': 'http://www.onvif.org/ver10/device/wsdl', 'XAddr': '...', 'Version': {...}}, ...]
378
-
379
- # Check the service map (namespace -> XAddr mapping)
380
- print(client._service_map)
381
- # Example output: {'http://www.onvif.org/ver10/media/wsdl': 'http://192.168.1.17:8000/onvif/Media', ...}
376
+ # Check what discovery method was used
377
+ if client.services:
378
+ print("Service discovery: GetServices (preferred)")
379
+ print("Discovered services:", len(client.services))
380
+ print("Service map:", client._service_map)
381
+ elif client.capabilities:
382
+ print("Service discovery: GetCapabilities (fallback)")
383
+ print("Available capabilities:", client.capabilities)
384
+ else:
385
+ print("Service discovery: Using default URLs")
382
386
  ```
383
387
 
384
- **Get detailed service information with capabilities:**
388
+ **Why this approach?**
389
+
390
+ - **GetServices** provides the most accurate and detailed service information, but it's **optional** in the ONVIF specification
391
+ - **GetCapabilities** is **mandatory** for all ONVIF-compliant devices, ensuring broader compatibility
392
+ - **Default URLs** guarantee basic connectivity even with non-compliant devices
385
393
 
386
- If you need detailed capability information for each service, call `GetServices` with `IncludeCapability=True`:
394
+ **Get detailed service information:**
395
+
396
+ If you need comprehensive service details, you can manually call `GetServices` with capabilities:
387
397
 
388
398
  ```python
389
399
  device = client.devicemgmt()
390
- services = device.GetServices(IncludeCapability=True)
391
-
392
- for service in services:
393
- print(f"Service: {service.Namespace}")
394
- print(f"Endpoint: {service.XAddr}")
395
- print(f"Version: {service.Version.Major}.{service.Version.Minor}")
396
- if hasattr(service, 'Capabilities') and service.Capabilities:
397
- print(f"Capabilities: {service.Capabilities}")
400
+
401
+ try:
402
+ # Try to get detailed service information
403
+ services = device.GetServices(IncludeCapability=True)
404
+ for service in services:
405
+ print(f"Service: {service.Namespace}")
406
+ print(f"XAddr: {service.XAddr}")
407
+ if hasattr(service, 'Capabilities'):
408
+ print(f"Capabilities: {service.Capabilities}")
409
+ except Exception:
410
+ # Fallback to GetCapabilities for legacy devices
411
+ capabilities = device.GetCapabilities()
412
+ print("Capabilities:", capabilities)
398
413
  ```
399
414
 
400
- **Alternative: Use GetCapabilities for legacy compatibility:**
415
+ **Access capabilities information:**
401
416
 
402
- For backward compatibility or when you need a quick overview of major service categories, you can still use `GetCapabilities`:
417
+ When using `GetCapabilities` fallback, you can access capability information:
403
418
 
404
419
  ```python
405
- capabilities = client.devicemgmt().GetCapabilities()
406
- print(capabilities)
407
- # Example output: {'Media': {'XAddr': '...', ...}, 'PTZ': {...}, 'Events': {...}, ...}
420
+ device = client.devicemgmt()
421
+
422
+ try:
423
+ # Get capabilities information from device
424
+ capabilities = device.GetCapabilities()
425
+
426
+ # Main services (always available)
427
+ print("Media XAddr:", getattr(capabilities.Media, 'XAddr', 'Not available'))
428
+ print("PTZ XAddr:", getattr(capabilities.PTZ, 'XAddr', 'Not available'))
429
+
430
+ # Extension services (device-dependent)
431
+ ext = getattr(capabilities, 'Extension', None)
432
+ if ext:
433
+ print("DeviceIO XAddr:", getattr(ext.DeviceIO, 'XAddr', 'Not available'))
434
+ print("Recording XAddr:", getattr(ext.Recording, 'XAddr', 'Not available'))
435
+ print("Search XAddr:", getattr(ext.Search, 'XAddr', 'Not available'))
436
+ print("Replay XAddr:", getattr(ext.Replay, 'XAddr', 'Not available'))
437
+ except Exception as e:
438
+ print(f"Error getting capabilities: {e}")
408
439
  ```
409
440
 
410
441
  > [!TIP]
411
- > The library handles service discovery automatically, so you typically don't need to call `GetServices` manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
442
+ > The library handles service discovery automatically with intelligent fallback. You typically don't need to call discovery methods manually unless you need detailed capability information or want to refresh the service list after device configuration changes.
412
443
 
413
444
  ## Tested Devices
414
445
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "onvif-python"
7
- version = "0.0.8"
7
+ version = "0.0.9"
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"
@@ -66,8 +66,5 @@ extend-exclude = '''
66
66
  '''
67
67
 
68
68
  [project.urls]
69
- Homepage = "https://github.com/nirsimetri/onvif-python"
70
- Issues = "https://github.com/nirsimetri/onvif-python/issues"
71
69
  Documentation = "https://deepwiki.com/nirsimetri/onvif-python"
72
- Repository = "https://github.com/nirsimetri/onvif-python.git"
73
70
  Changelog = "https://github.com/nirsimetri/onvif-python/releases"
File without changes
File without changes
File without changes
File without changes