onvif-python 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

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 (143) hide show
  1. onvif/__init__.py +29 -29
  2. onvif/cli/__init__.py +5 -5
  3. onvif/cli/__main__.py +7 -7
  4. onvif/cli/helpers/__init__.py +21 -21
  5. onvif/cli/helpers/discovery.py +181 -156
  6. onvif/cli/helpers/execution.py +57 -57
  7. onvif/cli/helpers/files.py +191 -191
  8. onvif/cli/helpers/messages.py +220 -220
  9. onvif/cli/helpers/products.py +336 -294
  10. onvif/cli/helpers/wsdl_parser.py +973 -973
  11. onvif/cli/interactive.py +1335 -1319
  12. onvif/cli/main.py +352 -342
  13. onvif/cli/utils.py +510 -510
  14. onvif/client.py +969 -969
  15. onvif/meta.py +6 -6
  16. onvif/operator.py +450 -450
  17. onvif/services/__init__.py +79 -79
  18. onvif/services/accesscontrol.py +292 -292
  19. onvif/services/accessrules.py +128 -128
  20. onvif/services/actionengine.py +109 -109
  21. onvif/services/analytics/__init__.py +1 -1
  22. onvif/services/analytics/analytics.py +124 -124
  23. onvif/services/analytics/ruleengine.py +78 -78
  24. onvif/services/analyticsdevice.py +159 -159
  25. onvif/services/appmgmt.py +74 -74
  26. onvif/services/authenticationbehavior.py +219 -219
  27. onvif/services/credential.py +321 -321
  28. onvif/services/deviceio.py +328 -328
  29. onvif/services/devicemgmt.py +970 -970
  30. onvif/services/display.py +132 -132
  31. onvif/services/doorcontrol.py +267 -267
  32. onvif/services/events/__init__.py +1 -1
  33. onvif/services/events/events.py +87 -87
  34. onvif/services/events/notification.py +57 -57
  35. onvif/services/events/pausable_subscription.py +76 -76
  36. onvif/services/events/pullpoint.py +80 -80
  37. onvif/services/events/subscription.py +53 -53
  38. onvif/services/imaging.py +145 -145
  39. onvif/services/media.py +886 -886
  40. onvif/services/media2.py +667 -667
  41. onvif/services/provisioning.py +70 -70
  42. onvif/services/ptz.py +380 -380
  43. onvif/services/receiver.py +94 -94
  44. onvif/services/recording.py +289 -289
  45. onvif/services/replay.py +60 -60
  46. onvif/services/schedule.py +182 -182
  47. onvif/services/search.py +410 -410
  48. onvif/services/security/__init__.py +1 -1
  49. onvif/services/security/advancedsecurity.py +35 -35
  50. onvif/services/security/authorizationserver.py +53 -53
  51. onvif/services/security/dot1x.py +161 -161
  52. onvif/services/security/jwt.py +37 -37
  53. onvif/services/security/keystore.py +612 -612
  54. onvif/services/security/mediasigning.py +78 -78
  55. onvif/services/security/tlsserver.py +262 -262
  56. onvif/services/thermal.py +88 -88
  57. onvif/services/uplink.py +56 -56
  58. onvif/utils/__init__.py +26 -26
  59. onvif/utils/discovery.py +526 -521
  60. onvif/utils/error_handlers.py +241 -237
  61. onvif/utils/exceptions.py +184 -184
  62. onvif/utils/plugins.py +500 -500
  63. onvif/utils/service.py +363 -363
  64. onvif/utils/wsdl.py +788 -785
  65. onvif/utils/zeep.py +685 -683
  66. onvif/wsdl/LICENSE.md +231 -231
  67. onvif/wsdl/schema/common/b-2.xsd +579 -579
  68. onvif/wsdl/schema/common/bf-2.xsd +84 -84
  69. onvif/wsdl/schema/common/bw-2.wsdl +447 -447
  70. onvif/wsdl/schema/common/include +13 -13
  71. onvif/wsdl/schema/common/mpqf.xsd +1109 -1109
  72. onvif/wsdl/schema/common/r-2.xsd +50 -50
  73. onvif/wsdl/schema/common/rw-2.wsdl +54 -54
  74. onvif/wsdl/schema/common/soap-envelope +165 -165
  75. onvif/wsdl/schema/common/t-1.xsd +184 -184
  76. onvif/wsdl/schema/common/ws-addr.xsd +144 -144
  77. onvif/wsdl/schema/common/xml.xsd +287 -287
  78. onvif/wsdl/schema/common/xmlmime +48 -48
  79. onvif/wsdl/ver10/accesscontrol/wsdl/index.html +6 -6
  80. onvif/wsdl/ver10/accessrules/wsdl/accessrules.wsdl +702 -702
  81. onvif/wsdl/ver10/accessrules/wsdl/index.html +6 -6
  82. onvif/wsdl/ver10/actionengine.wsdl +1265 -1265
  83. onvif/wsdl/ver10/advancedsecurity/wsdl/advancedsecurity.wsdl +4205 -4205
  84. onvif/wsdl/ver10/advancedsecurity/wsdl/index.html +6 -6
  85. onvif/wsdl/ver10/analyticsdevice.wsdl +709 -709
  86. onvif/wsdl/ver10/appmgmt/wsdl/appmgmt.wsdl +502 -502
  87. onvif/wsdl/ver10/appmgmt/wsdl/index.html +6 -6
  88. onvif/wsdl/ver10/authenticationbehavior/wsdl/authenticationbehavior.wsdl +1382 -1382
  89. onvif/wsdl/ver10/authenticationbehavior/wsdl/index.html +6 -6
  90. onvif/wsdl/ver10/credential/wsdl/credential.wsdl +2018 -2018
  91. onvif/wsdl/ver10/credential/wsdl/index.html +6 -6
  92. onvif/wsdl/ver10/device/wsdl/devicemgmt.wsdl +4786 -4786
  93. onvif/wsdl/ver10/deviceio.wsdl +1414 -1414
  94. onvif/wsdl/ver10/display/display.wsdl +383 -383
  95. onvif/wsdl/ver10/display/wsdl/index.htm +383 -383
  96. onvif/wsdl/ver10/display.wsdl +521 -521
  97. onvif/wsdl/ver10/doorcontrol/wsdl/index.html +6 -6
  98. onvif/wsdl/ver10/events/wsdl/bw-2-vs-mod.wsdl +448 -448
  99. onvif/wsdl/ver10/events/wsdl/event-vs.wsdl +947 -947
  100. onvif/wsdl/ver10/events/wsdl/event.wsdl +947 -947
  101. onvif/wsdl/ver10/events/wsdl/index.html +6 -6
  102. onvif/wsdl/ver10/federatedsearch.wsdl +344 -344
  103. onvif/wsdl/ver10/media/wsdl/media.wsdl +3684 -3684
  104. onvif/wsdl/ver10/pacs/accesscontrol.wsdl +1942 -1942
  105. onvif/wsdl/ver10/pacs/doorcontrol.wsdl +1792 -1792
  106. onvif/wsdl/ver10/pacs/types.xsd +181 -181
  107. onvif/wsdl/ver10/provisioning/wsdl/provisioning.wsdl +588 -588
  108. onvif/wsdl/ver10/receiver.wsdl +398 -398
  109. onvif/wsdl/ver10/recording.wsdl +1442 -1442
  110. onvif/wsdl/ver10/replay.wsdl +221 -221
  111. onvif/wsdl/ver10/schedule/wsdl/index.html +6 -6
  112. onvif/wsdl/ver10/schedule/wsdl/schedule.wsdl +1383 -1383
  113. onvif/wsdl/ver10/schema/common.xsd +431 -431
  114. onvif/wsdl/ver10/schema/metadatastream.xsd +534 -534
  115. onvif/wsdl/ver10/schema/onvif.xsd +9743 -9743
  116. onvif/wsdl/ver10/search.wsdl +1114 -1114
  117. onvif/wsdl/ver10/thermal/wsdl/index.html +6 -6
  118. onvif/wsdl/ver10/thermal/wsdl/thermal.wsdl +777 -777
  119. onvif/wsdl/ver10/topics/topicns.xml +13 -13
  120. onvif/wsdl/ver10/uplink/wsdl/index.html +6 -6
  121. onvif/wsdl/ver10/uplink/wsdl/uplink.wsdl +256 -256
  122. onvif/wsdl/ver20/analytics/humanbody.xsd +490 -490
  123. onvif/wsdl/ver20/analytics/humanface.xsd +501 -501
  124. onvif/wsdl/ver20/analytics/index.html +6 -6
  125. onvif/wsdl/ver20/analytics/radiometry.xsd +388 -388
  126. onvif/wsdl/ver20/analytics/rules.xsd +126 -126
  127. onvif/wsdl/ver20/analytics/wsdl/analytics.wsdl +757 -757
  128. onvif/wsdl/ver20/analytics/wsdl/index.html +6 -6
  129. onvif/wsdl/ver20/imaging/wsdl/imaging.wsdl +603 -603
  130. onvif/wsdl/ver20/media/wsdl/index.html +6 -6
  131. onvif/wsdl/ver20/media/wsdl/media.wsdl +3126 -3126
  132. onvif/wsdl/ver20/ptz/wsdl/ptz.wsdl +1450 -1450
  133. onvif/wsdl/ver20/util/federated_search_documentation.html +1647 -1647
  134. onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl +8 -8
  135. onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl +2579 -2579
  136. onvif/wsdl/ver20/util/operationIndex.html +97 -97
  137. {onvif_python-0.4.0.dist-info → onvif_python-0.4.2.dist-info}/METADATA +115 -113
  138. onvif_python-0.4.2.dist-info/RECORD +144 -0
  139. {onvif_python-0.4.0.dist-info → onvif_python-0.4.2.dist-info}/licenses/LICENSE.md +20 -20
  140. onvif_python-0.4.0.dist-info/RECORD +0 -144
  141. {onvif_python-0.4.0.dist-info → onvif_python-0.4.2.dist-info}/WHEEL +0 -0
  142. {onvif_python-0.4.0.dist-info → onvif_python-0.4.2.dist-info}/entry_points.txt +0 -0
  143. {onvif_python-0.4.0.dist-info → onvif_python-0.4.2.dist-info}/top_level.txt +0 -0
onvif/__init__.py CHANGED
@@ -1,29 +1,29 @@
1
- """ONVIF Python."""
2
-
3
- from onvif.cli import main as ONVIFCLI
4
- from onvif.client import ONVIFClient
5
- from onvif.operator import CacheMode
6
- from onvif.utils import (
7
- ONVIFWSDL,
8
- ONVIFDiscovery,
9
- ONVIFOperationException,
10
- ONVIFParser,
11
- ZeepPatcher,
12
- ignore_unsupported,
13
- is_action_not_supported,
14
- safe_call,
15
- )
16
-
17
- __all__ = [
18
- "ONVIFClient",
19
- "CacheMode",
20
- "ONVIFWSDL",
21
- "ONVIFOperationException",
22
- "ZeepPatcher",
23
- "ONVIFCLI",
24
- "ONVIFDiscovery",
25
- "ONVIFParser",
26
- "ignore_unsupported",
27
- "is_action_not_supported",
28
- "safe_call",
29
- ]
1
+ """ONVIF Python."""
2
+
3
+ from onvif.cli import main as ONVIFCLI
4
+ from onvif.client import ONVIFClient
5
+ from onvif.operator import CacheMode
6
+ from onvif.utils import (
7
+ ONVIFWSDL,
8
+ ONVIFDiscovery,
9
+ ONVIFOperationException,
10
+ ONVIFParser,
11
+ ZeepPatcher,
12
+ ignore_unsupported,
13
+ is_action_not_supported,
14
+ safe_call,
15
+ )
16
+
17
+ __all__ = [
18
+ "ONVIFClient",
19
+ "CacheMode",
20
+ "ONVIFWSDL",
21
+ "ONVIFOperationException",
22
+ "ZeepPatcher",
23
+ "ONVIFCLI",
24
+ "ONVIFDiscovery",
25
+ "ONVIFParser",
26
+ "ignore_unsupported",
27
+ "is_action_not_supported",
28
+ "safe_call",
29
+ ]
onvif/cli/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
- """ONVIF CLI."""
2
-
3
- from onvif.cli.main import main
4
-
5
- __all__ = ["main"]
1
+ """ONVIF CLI."""
2
+
3
+ from onvif.cli.main import main
4
+
5
+ __all__ = ["main"]
onvif/cli/__main__.py CHANGED
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env python3
2
- """ONVIF CLI entry point for python -m onvif.cli."""
3
-
4
- from onvif.cli.main import main
5
-
6
- if __name__ == "__main__":
7
- main()
1
+ #!/usr/bin/env python3
2
+ """ONVIF CLI entry point for python -m onvif.cli."""
3
+
4
+ from onvif.cli.main import main
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -1,21 +1,21 @@
1
- """ONVIF CLI helpers."""
2
-
3
- from onvif.cli.helpers.discovery import discover_devices, select_device_interactive
4
- from onvif.cli.helpers.execution import execute_command, setup_warning_format
5
- from onvif.cli.helpers.files import save_output_to_file
6
- from onvif.cli.helpers.products import search_products
7
- from onvif.cli.helpers.wsdl_parser import (
8
- get_method_documentation,
9
- get_operation_type_info,
10
- )
11
-
12
- __all__ = [
13
- "search_products",
14
- "save_output_to_file",
15
- "discover_devices",
16
- "select_device_interactive",
17
- "execute_command",
18
- "setup_warning_format",
19
- "get_method_documentation",
20
- "get_operation_type_info",
21
- ]
1
+ """ONVIF CLI helpers."""
2
+
3
+ from onvif.cli.helpers.discovery import discover_devices, select_device_interactive
4
+ from onvif.cli.helpers.execution import execute_command, setup_warning_format
5
+ from onvif.cli.helpers.files import save_output_to_file
6
+ from onvif.cli.helpers.products import search_products
7
+ from onvif.cli.helpers.wsdl_parser import (
8
+ get_method_documentation,
9
+ get_operation_type_info,
10
+ )
11
+
12
+ __all__ = [
13
+ "search_products",
14
+ "save_output_to_file",
15
+ "discover_devices",
16
+ "select_device_interactive",
17
+ "execute_command",
18
+ "setup_warning_format",
19
+ "get_method_documentation",
20
+ "get_operation_type_info",
21
+ ]
@@ -1,156 +1,181 @@
1
- """ONVIF discovery helpers."""
2
-
3
- from datetime import datetime
4
-
5
- from onvif.cli.utils import colorize
6
- from onvif.utils import ONVIFDiscovery
7
-
8
-
9
- def discover_devices(
10
- timeout: int = 4,
11
- interface: str | None = None,
12
- prefer_https: bool = False,
13
- filter_term: str | None = None,
14
- ) -> list:
15
- """Discover ONVIF devices on the network using WS-Discovery.
16
-
17
- Args:
18
- timeout (int): Discovery timeout in seconds
19
- interface (str | None): Network interface to use for discovery
20
- prefer_https (bool): If True, prioritize HTTPS XAddrs when available
21
- filter_term (str | None): Optional search term to filter devices by types or scopes (case-insensitive)
22
-
23
- Returns:
24
- List of discovered devices with connection info
25
- """
26
- # Use ONVIFDiscovery class
27
- discovery = ONVIFDiscovery(timeout=timeout, interface=interface)
28
-
29
- print(f"\n{colorize('Discovering ONVIF devices on network...', 'yellow')}")
30
- print(f"Network interface: {colorize(discovery.get_local_ip(), 'white')}")
31
- print(f"Timeout: {timeout}s")
32
- if filter_term:
33
- print(f"Filter: {colorize(filter_term, 'yellow')}")
34
- print()
35
-
36
- devices = discovery.discover(prefer_https=prefer_https, search=filter_term)
37
-
38
- return devices
39
-
40
-
41
- # pylint: disable=too-many-locals,too-many-statements,too-many-branches
42
- def select_device_interactive(devices: list) -> tuple[str, int, bool] | None:
43
- """Display devices and allow user to select one interactively.
44
-
45
- Args:
46
- devices (list): Discovered devices
47
-
48
- Returns:
49
- Tuple of (host, port, use_https) or None if cancelled
50
- """
51
- if not devices:
52
- print(f"\n{colorize('No ONVIF devices found.', 'red')}")
53
- return None
54
-
55
- print(f"{colorize(f'Found {len(devices)} ONVIF device(s):', 'green')}")
56
-
57
- for idx, device in enumerate(devices, 1):
58
- idx_str = colorize(f"[{idx}]", "yellow")
59
- protocol = "https" if device.get("use_https", False) else "http"
60
- host_port = f"{device['host']}:{device['port']}"
61
- protocol_indicator = (
62
- colorize("🔒 HTTPS", "green")
63
- if device.get("use_https", False)
64
- else colorize("HTTP", "white")
65
- )
66
- print(f"\n{idx_str} {colorize(host_port, 'yellow')} ({protocol_indicator})")
67
-
68
- # Remove uuid: or urn:uuid: prefix from EPR
69
- epr_display = device["epr"]
70
- if epr_display.startswith("urn:uuid:"):
71
- epr_display = epr_display.replace("urn:uuid:", "")
72
- elif epr_display.startswith("uuid:"):
73
- epr_display = epr_display.replace("uuid:", "")
74
- print(f" [uuid] {epr_display}")
75
-
76
- if device.get("hostname"):
77
- print(f" [hostname] {device["hostname"]}")
78
-
79
- if device.get("xaddrs"):
80
- xaddrs_parts = [f"[{xaddr}]" for xaddr in device["xaddrs"]]
81
- print(f" [xaddrs] {' '.join(xaddrs_parts)}")
82
-
83
- if device.get("date_time"):
84
- utc = (
85
- datetime.fromisoformat(device["date_time"].get("utc")).strftime(
86
- "%H:%M:%S %d-%m-%Y"
87
- )
88
- if device["date_time"].get("utc")
89
- else ""
90
- )
91
- local = (
92
- datetime.fromisoformat(device["date_time"].get("local")).strftime(
93
- "%H:%M:%S %d-%m-%Y"
94
- )
95
- if device["date_time"].get("local")
96
- else ""
97
- )
98
- print(
99
- f" [date_time]"
100
- f'{" [UTC: " + utc + "]" if utc else ""}'
101
- f'{" [Local: " + local + "]" if local else ""}'
102
- )
103
-
104
- if device.get("types"):
105
- types_parts = [f"[{t}]" for t in device["types"]]
106
- print(f" [types] {' '.join(types_parts)}")
107
-
108
- if device.get("services"):
109
- service = [colorize(f"[{t}]", "cyan") for t in device["services"]]
110
- print(f" [services] {' '.join(service)}")
111
-
112
- if device.get("scopes"):
113
- scope_parts = []
114
- for scope in device["scopes"]:
115
- # Remove the prefix "onvif://www.onvif.org/" if present
116
- if scope.startswith("onvif://www.onvif.org/"):
117
- simplified = scope.replace("onvif://www.onvif.org/", "")
118
- scope_parts.append(f"[{simplified}]")
119
- else:
120
- # Keep other scopes as-is (e.g., http:123)
121
- scope_parts.append(f"[{scope}]")
122
-
123
- if scope_parts:
124
- print(f" [scopes] {' '.join(scope_parts)}")
125
-
126
- # Simple selection (without arrow keys for cross-platform compatibility)
127
- while True:
128
- try:
129
- selection = input(
130
- f"\nSelect device number {colorize(f'1-{len(devices)}', 'white')} or {colorize('q', 'white')} to quit: "
131
- )
132
-
133
- if selection.lower() == "q":
134
- return None
135
-
136
- idx = int(selection)
137
- if 1 <= idx <= len(devices):
138
- selected = devices[idx - 1]
139
- protocol = "https" if selected.get("use_https", False) else "http"
140
- host_port = f"{selected['host']}:{selected['port']}"
141
- print(
142
- f"\n{colorize('Selected:', 'green')} {colorize(protocol, 'cyan')}://{colorize(host_port, 'yellow')}"
143
- )
144
-
145
- return (
146
- selected["host"],
147
- selected["port"],
148
- selected.get("use_https", False),
149
- )
150
-
151
- print(colorize("Invalid selection. Please try again.", "red"))
152
-
153
- except ValueError:
154
- print(colorize("Invalid input. Please enter a number.", "red"))
155
- except (EOFError, KeyboardInterrupt):
156
- return None
1
+ """ONVIF discovery helpers."""
2
+
3
+ from datetime import datetime
4
+
5
+ from onvif.cli.utils import colorize
6
+ from onvif.utils import ONVIFDiscovery
7
+
8
+
9
+ def discover_devices(
10
+ timeout: int = 4,
11
+ interface: str | None = None,
12
+ prefer_https: bool = False,
13
+ filter_term: str | None = None,
14
+ ) -> list:
15
+ """Discover ONVIF devices on the network using WS-Discovery.
16
+
17
+ Args:
18
+ timeout (int): Discovery timeout in seconds
19
+ interface (str | None): Network interface to use for discovery
20
+ prefer_https (bool): If True, prioritize HTTPS XAddrs when available
21
+ filter_term (str | None): Optional search term to filter devices by
22
+ types or scopes (case-insensitive)
23
+
24
+ Returns:
25
+ List of discovered devices with connection info
26
+ """
27
+ # Use ONVIFDiscovery class
28
+ discovery = ONVIFDiscovery(timeout=timeout, interface=interface)
29
+
30
+ print(f"\n{colorize('Discovering ONVIF devices on network...', 'yellow')}")
31
+ print(f"Network interface: {colorize(discovery.get_local_ip(), 'white')}")
32
+ print(f"Timeout: {timeout}s")
33
+ if filter_term:
34
+ print(f"Filter: {colorize(filter_term, 'yellow')}")
35
+ print()
36
+
37
+ devices = discovery.discover(prefer_https=prefer_https, search=filter_term)
38
+
39
+ return devices
40
+
41
+
42
+ def select_device_interactive(devices: list) -> tuple[str, int, bool] | None:
43
+ """Display devices and allow user to select one interactively.
44
+
45
+ Args:
46
+ devices (list): Discovered devices
47
+
48
+ Returns:
49
+ Tuple of (host, port, use_https) or None if cancelled
50
+ """
51
+ if not devices:
52
+ print(f"\n{colorize('No ONVIF devices found.', 'red')}")
53
+ return None
54
+
55
+ print(f"{colorize(f'Found {len(devices)} ONVIF device(s):', 'green')}")
56
+
57
+ for idx, device in enumerate(devices, 1):
58
+ protocol = "https" if device.get("use_https", False) else "http"
59
+ host_port = f"{device['host']}:{device['port']}"
60
+ protocol_indicator = (
61
+ colorize("🔒 HTTPS", "green")
62
+ if device.get("use_https", False)
63
+ else colorize("HTTP", "white")
64
+ )
65
+ print(
66
+ f"\n{colorize(f'[{idx}]', 'yellow')} {colorize(host_port, 'yellow')} ({protocol_indicator})"
67
+ )
68
+
69
+ # Remove uuid: or urn:uuid: prefix from EPR
70
+ epr_display = device["epr"]
71
+ if epr_display.startswith("urn:uuid:"):
72
+ epr_display = epr_display.replace("urn:uuid:", "")
73
+ elif epr_display.startswith("uuid:"):
74
+ epr_display = epr_display.replace("uuid:", "")
75
+ print(f" [uuid] {epr_display}")
76
+
77
+ _print_optional_field("hostname", device.get("hostname"))
78
+
79
+ _print_optional_field(
80
+ "xaddrs",
81
+ " ".join(f"[{xaddr}]" for xaddr in device.get("xaddrs", [])),
82
+ )
83
+
84
+ if device.get("date_time"):
85
+ utc = (
86
+ datetime.fromisoformat(device["date_time"].get("utc")).strftime(
87
+ "%H:%M:%S %d-%m-%Y"
88
+ )
89
+ if device["date_time"].get("utc")
90
+ else ""
91
+ )
92
+ local = (
93
+ datetime.fromisoformat(device["date_time"].get("local")).strftime(
94
+ "%H:%M:%S %d-%m-%Y"
95
+ )
96
+ if device["date_time"].get("local")
97
+ else ""
98
+ )
99
+ print(
100
+ f" [date_time]"
101
+ f'{" [UTC: " + utc + "]" if utc else ""}'
102
+ f'{" [Local: " + local + "]" if local else ""}'
103
+ )
104
+
105
+ _print_optional_field(
106
+ "types",
107
+ " ".join(f"[{device_type}]" for device_type in device.get("types", [])),
108
+ )
109
+
110
+ _print_optional_field(
111
+ "services",
112
+ " ".join(f"[{service}]" for service in device.get("services", [])),
113
+ "cyan",
114
+ )
115
+
116
+ if device.get("scopes"):
117
+ scope_parts = []
118
+ for scope in device["scopes"]:
119
+ # Remove the prefix "onvif://www.onvif.org/" if present
120
+ if scope.startswith("onvif://www.onvif.org/"):
121
+ simplified = scope.replace("onvif://www.onvif.org/", "")
122
+ scope_parts.append(f"[{simplified}]")
123
+ else:
124
+ # Keep other scopes as-is (e.g., http:123)
125
+ scope_parts.append(f"[{scope}]")
126
+
127
+ if scope_parts:
128
+ print(f" [scopes] {' '.join(scope_parts)}")
129
+
130
+ return _process_device_selection(protocol, devices)
131
+
132
+
133
+ def _print_optional_field(
134
+ label: str,
135
+ value: object,
136
+ color: str | None = None,
137
+ ) -> None:
138
+ """Print an optional device field when it has a value."""
139
+ if not value:
140
+ return
141
+
142
+ if color:
143
+ value = colorize(str(value), color)
144
+
145
+ print(f" [{label}] {value}")
146
+
147
+
148
+ def _process_device_selection(protocol, devices):
149
+ """Simple selection (without arrow keys for cross-platform compatibility)"""
150
+ while True:
151
+ try:
152
+ selection = input(
153
+ f"\nSelect device number {colorize(f'1-{len(devices)}', 'white')} "
154
+ f"or {colorize('q', 'white')} to quit: "
155
+ )
156
+
157
+ if selection.lower() == "q":
158
+ return None
159
+
160
+ idx = int(selection)
161
+ if 1 <= idx <= len(devices):
162
+ selected = devices[idx - 1]
163
+ protocol = "https" if selected.get("use_https", False) else "http"
164
+ host_port = f"{selected['host']}:{selected['port']}"
165
+ print(
166
+ f"\n{colorize('Selected:', 'green')} {colorize(protocol, 'cyan')}:"
167
+ f"//{colorize(host_port, 'yellow')}"
168
+ )
169
+
170
+ return (
171
+ selected["host"],
172
+ selected["port"],
173
+ selected.get("use_https", False),
174
+ )
175
+
176
+ print(colorize("Invalid selection. Please try again.", "red"))
177
+
178
+ except ValueError:
179
+ print(colorize("Invalid input. Please enter a number.", "red"))
180
+ except (EOFError, KeyboardInterrupt):
181
+ return None
@@ -1,57 +1,57 @@
1
- """ONVIF command execution helpers."""
2
-
3
- import warnings
4
- from typing import Any
5
-
6
- from onvif.cli.utils import colorize, parse_json_params
7
- from onvif.client import ONVIFClient
8
-
9
-
10
- def execute_command(
11
- client: ONVIFClient,
12
- service_name: str,
13
- method_name: str,
14
- params_str: str | None = None,
15
- ) -> Any:
16
- """Execute a single ONVIF command.
17
-
18
- Args:
19
- client (ONVIFClient): ONVIFClient instance
20
- service_name (str): Selected service for command execution
21
- method_name (str): Method to execute by service
22
- params_str (str | None): Provided params for method execution
23
-
24
- Returns:
25
- The operation response printed in terminal
26
- """
27
- # Get service instance
28
- try:
29
- service = getattr(client, service_name.lower())()
30
- except AttributeError as e:
31
- raise ValueError(f"{colorize('Unknown service:', 'red')} {service_name}") from e
32
-
33
- # Get method
34
- try:
35
- method = getattr(service, method_name)
36
- except AttributeError as e:
37
- raise ValueError(
38
- f"{colorize('Unknown method', 'red')} '{method_name}' for service '{service_name}'"
39
- ) from e
40
-
41
- # Parse parameters
42
- params = parse_json_params(params_str) if params_str else {}
43
-
44
- # Execute method
45
- return method(**params)
46
-
47
-
48
- def setup_warning_format() -> None:
49
- """Setup custom warning format to show clean, concise warnings."""
50
-
51
- def custom_warning_format(
52
- message, category, filename, lineno, line=None
53
- ): # pylint: disable=unused-argument
54
- # Show only the warning message without file path and line number
55
- return f"{category.__name__}: {message}\n"
56
-
57
- warnings.formatwarning = custom_warning_format
1
+ """ONVIF command execution helpers."""
2
+
3
+ import warnings
4
+ from typing import Any
5
+
6
+ from onvif.cli.utils import colorize, parse_json_params
7
+ from onvif.client import ONVIFClient
8
+
9
+
10
+ def execute_command(
11
+ client: ONVIFClient,
12
+ service_name: str,
13
+ method_name: str,
14
+ params_str: str | None = None,
15
+ ) -> Any:
16
+ """Execute a single ONVIF command.
17
+
18
+ Args:
19
+ client (ONVIFClient): ONVIFClient instance
20
+ service_name (str): Selected service for command execution
21
+ method_name (str): Method to execute by service
22
+ params_str (str | None): Provided params for method execution
23
+
24
+ Returns:
25
+ The operation response printed in terminal
26
+ """
27
+ # Get service instance
28
+ try:
29
+ service = getattr(client, service_name.lower())()
30
+ except AttributeError as e:
31
+ raise ValueError(f"{colorize('Unknown service:', 'red')} {service_name}") from e
32
+
33
+ # Get method
34
+ try:
35
+ method = getattr(service, method_name)
36
+ except AttributeError as e:
37
+ raise ValueError(
38
+ f"{colorize('Unknown method', 'red')} '{method_name}' for service '{service_name}'"
39
+ ) from e
40
+
41
+ # Parse parameters
42
+ params = parse_json_params(params_str) if params_str else {}
43
+
44
+ # Execute method
45
+ return method(**params)
46
+
47
+
48
+ def setup_warning_format() -> None:
49
+ """Setup custom warning format to show clean, concise warnings."""
50
+
51
+ def custom_warning_format(
52
+ message, category, filename, lineno, line=None
53
+ ): # pylint: disable=unused-argument
54
+ # Show only the warning message without file path and line number
55
+ return f"{category.__name__}: {message}\n"
56
+
57
+ warnings.formatwarning = custom_warning_format