onvif-python 0.2.4__py3-none-any.whl → 0.2.5__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.
- onvif/__init__.py +1 -2
- onvif/cli/interactive.py +2 -2
- onvif/cli/main.py +213 -5
- {onvif_python-0.2.4.dist-info → onvif_python-0.2.5.dist-info}/METADATA +41 -24
- {onvif_python-0.2.4.dist-info → onvif_python-0.2.5.dist-info}/RECORD +9 -9
- {onvif_python-0.2.4.dist-info → onvif_python-0.2.5.dist-info}/WHEEL +0 -0
- {onvif_python-0.2.4.dist-info → onvif_python-0.2.5.dist-info}/entry_points.txt +0 -0
- {onvif_python-0.2.4.dist-info → onvif_python-0.2.5.dist-info}/licenses/LICENSE.md +0 -0
- {onvif_python-0.2.4.dist-info → onvif_python-0.2.5.dist-info}/top_level.txt +0 -0
onvif/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# onvif/__init__.py
|
|
2
2
|
|
|
3
3
|
from .client import ONVIFClient
|
|
4
|
-
from .operator import
|
|
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",
|
onvif/cli/interactive.py
CHANGED
|
@@ -194,7 +194,7 @@ class InteractiveShell(cmd.Cmd):
|
|
|
194
194
|
" / __ \\/ | / / | / / _/ ____/",
|
|
195
195
|
" / / / / |/ /| | / // // /_ ",
|
|
196
196
|
"/ /_/ / /| / | |/ // // __/ ",
|
|
197
|
-
"\\____/_/ |_/ |___/___/_/ v0.2.
|
|
197
|
+
"\\____/_/ |_/ |___/___/_/ v0.2.5",
|
|
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.
|
|
1403
|
+
{colorize('ONVIF Interactive Shell — v0.2.5', '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
|
onvif/cli/main.py
CHANGED
|
@@ -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.
|
|
25
|
+
description=f"{colorize('ONVIF Terminal Client', 'yellow')} — v0.2.5\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={{
|
|
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.
|
|
191
|
+
print(colorize("0.2.5", "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=
|
|
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
|
-
|
|
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:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onvif-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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"
|
|
48
|
-
<img alt="Ask DeepWiki" src="https://deepwiki.com/badge.svg"
|
|
49
|
-
<img alt="PyPI Version" src="https://img.shields.io/badge/PyPI-0.2.
|
|
50
|
-
<img alt="Pepy Total Downloads" src="https://img.shields.io/pepy/dt/onvif-python?label=Downloads&color=red"
|
|
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 alt="Codacy grade" src="https://img.shields.io/codacy/grade/bff08a94e4d447b690cea49c6594826d?label=Code%20Quality&logo=codacy"></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.5-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
|
-
<
|
|
53
|
-
<
|
|
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
|
-
##
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
-
|
|
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]
|
|
355
|
-
[--
|
|
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.
|
|
365
|
+
ONVIF Terminal Client — v0.2.5
|
|
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={
|
|
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.
|
|
446
|
+
ONVIF Interactive Shell — v0.2.5
|
|
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.
|
|
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,10 +1,10 @@
|
|
|
1
|
-
onvif/__init__.py,sha256=
|
|
1
|
+
onvif/__init__.py,sha256=0wZNZnISPA9kZQ0J2UIDHCTvfHKDPgupHM32WTJeKLw,463
|
|
2
2
|
onvif/client.py,sha256=_G00dKSvRkLKq5tDGm4XS_3fhSE3WJtYckK9WVzNj9c,27703
|
|
3
3
|
onvif/operator.py,sha256=fBaoWYC3ZVFdgd2S_cmztWiLAA-HaWwSflq278YNlTs,13970
|
|
4
4
|
onvif/cli/__init__.py,sha256=y3hc-mo-Iwwxf8m5vo9P_0m2ISzGuOy-jaRPkdRY05A,68
|
|
5
5
|
onvif/cli/__main__.py,sha256=IZyZcevWVKE1BzdFY53a3MjOOfybncGVVXQ_7lZ3TOQ,165
|
|
6
|
-
onvif/cli/interactive.py,sha256=
|
|
7
|
-
onvif/cli/main.py,sha256=
|
|
6
|
+
onvif/cli/interactive.py,sha256=on9is0R25waYsyDcBwEpUvsBSUvHLyEvsC8uMVprKqs,59756
|
|
7
|
+
onvif/cli/main.py,sha256=qqd1Y7-21lAsNYO9jDdHYft2KwMKuQB059whl2yc6SY,35751
|
|
8
8
|
onvif/cli/utils.py,sha256=o3lGeJ3yCgaINgXiq7wF0wB9KlR1qpvhNrW92onP4B4,60477
|
|
9
9
|
onvif/db/companies.db,sha256=yJ8bSHfGpxNnA5NRBhYOFS_1Pix-KgH8GnUFV3ytzPo,270336
|
|
10
10
|
onvif/db/products.db,sha256=4BVPfy6i6pLMLQyfue238uRp6_NIDodu4QXd9iCoooY,81559552
|
|
@@ -114,9 +114,9 @@ onvif/wsdl/ver20/util/federated_search_documentation.html,sha256=GR6a4SPBsRDisaW
|
|
|
114
114
|
onvif/wsdl/ver20/util/fedsearch-docu-viewer.xsl,sha256=8eP-uMZgsTtuU5WaVyCzs_DJZqWIFD7iRz_5-3ComPg,325
|
|
115
115
|
onvif/wsdl/ver20/util/onvif-wsdl-viewer.xsl,sha256=Emauxs6rh69IxlxlU3k_LEH_i3SsRbwIKUk-VcMlVgk,94525
|
|
116
116
|
onvif/wsdl/ver20/util/operationIndex.html,sha256=mUsAGgNUFCIQjVV9Rx_O5YUNW3EdWNhUkAbBY-xVMhQ,39290
|
|
117
|
-
onvif_python-0.2.
|
|
118
|
-
onvif_python-0.2.
|
|
119
|
-
onvif_python-0.2.
|
|
120
|
-
onvif_python-0.2.
|
|
121
|
-
onvif_python-0.2.
|
|
122
|
-
onvif_python-0.2.
|
|
117
|
+
onvif_python-0.2.5.dist-info/licenses/LICENSE.md,sha256=ya8FumJeUkFWwCMkG9KdMxHWRWMNhtI3vHzzAWPQS8s,1091
|
|
118
|
+
onvif_python-0.2.5.dist-info/METADATA,sha256=h4xV5fZWSdE7tLcddDSV8rnSFczc2PGWU7Qpa1Zhlu8,62291
|
|
119
|
+
onvif_python-0.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
120
|
+
onvif_python-0.2.5.dist-info/entry_points.txt,sha256=7xUdI64yzWh7YfNPQDKCW9cT8XIgWuNCloikP9-yjqY,46
|
|
121
|
+
onvif_python-0.2.5.dist-info/top_level.txt,sha256=MqCi1uJmIvtVFJDz2IxLbpwq6FwDtWhqrWQtKwLaQg4,6
|
|
122
|
+
onvif_python-0.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|