onvif-tui 1.0.1__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.
onvif_tui-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onvif-tui
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Author: Stephen Rhodes
|
|
5
|
+
Author-email: Stephen Rhodes <sr99622@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Requires-Dist: libonvif
|
|
8
|
+
Requires-Dist: niquests
|
|
9
|
+
Requires-Dist: psutil
|
|
10
|
+
Requires-Dist: textual
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Project-URL: Bug Tracker, https://github.com/sr99622/libonvif/issues
|
|
13
|
+
Project-URL: Homepage, https://github.com/sr99622/libonvif
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<h3>onvif-tui</h3>
|
|
17
|
+
|
|
18
|
+
This is a Terminal User Interface application that demonstrates libonvif abilities and can be used to evaluate and control camera settings. The application is launched using the command line arguments:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
-u username for camera authentication
|
|
22
|
+
-p passwword for camera authentication
|
|
23
|
+
-m camera ip address for manual camera discovery
|
|
24
|
+
-i host local ip address for binding discovery broadcast
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Please visit the [github repository](https://github.com/sr99622/libonvif) for more detailed information.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<h3>onvif-tui</h3>
|
|
2
|
+
|
|
3
|
+
This is a Terminal User Interface application that demonstrates libonvif abilities and can be used to evaluate and control camera settings. The application is launched using the command line arguments:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
-u username for camera authentication
|
|
7
|
+
-p passwword for camera authentication
|
|
8
|
+
-m camera ip address for manual camera discovery
|
|
9
|
+
-i host local ip address for binding discovery broadcast
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Please visit the [github repository](https://github.com/sr99622/libonvif) for more detailed information.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "onvif-tui"
|
|
3
|
+
version = "1.0.1"
|
|
4
|
+
requires-python = ">=3.10"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"libonvif",
|
|
7
|
+
"niquests",
|
|
8
|
+
"psutil",
|
|
9
|
+
"textual",
|
|
10
|
+
]
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Stephen Rhodes", email = "sr99622@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
license = "Apache-2.0"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/sr99622/libonvif"
|
|
19
|
+
"Bug Tracker" = "https://github.com/sr99622/libonvif/issues"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
onvif-tui = "onvif_tui.main:main"
|
|
23
|
+
|
|
24
|
+
[tool.uv.sources]
|
|
25
|
+
libonvif = { workspace = true }
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["uv_build>=0.8.0,<0.9.0"]
|
|
29
|
+
build-backend = "uv_build"
|
|
File without changes
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from textual.widgets import Tree
|
|
2
|
+
from textual.widgets.tree import TreeNode
|
|
3
|
+
from dataclasses import is_dataclass, fields
|
|
4
|
+
from rich.text import Text
|
|
5
|
+
from .fields import UNUSED_FIELDS, HIDDEN_FIELDS, field_descriptions, join_fqn, is_editable_field, main_screen_text
|
|
6
|
+
from libonvif.devices.camera import Camera
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
class CameraTree(Tree):
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
super().__init__("Cameras")
|
|
12
|
+
self.show_root = True
|
|
13
|
+
|
|
14
|
+
def get_fqn(self, node: TreeNode) -> str:
|
|
15
|
+
parts = []
|
|
16
|
+
current = node
|
|
17
|
+
while current is not None:
|
|
18
|
+
parent = current.parent
|
|
19
|
+
if parent is None:
|
|
20
|
+
break
|
|
21
|
+
data = getattr(current, "data", None)
|
|
22
|
+
if data and "field" in data:
|
|
23
|
+
parts.append(data["field"])
|
|
24
|
+
current = parent
|
|
25
|
+
return ".".join(reversed(parts))
|
|
26
|
+
|
|
27
|
+
def capture_expanded_nodes(self, node) -> set[str]:
|
|
28
|
+
expanded: set[str] = set()
|
|
29
|
+
|
|
30
|
+
def walk(n):
|
|
31
|
+
data = n.data or {}
|
|
32
|
+
fqn = data.get("fqn")
|
|
33
|
+
|
|
34
|
+
if fqn and n.is_expanded:
|
|
35
|
+
expanded.add(fqn)
|
|
36
|
+
|
|
37
|
+
for child in n.children:
|
|
38
|
+
walk(child)
|
|
39
|
+
|
|
40
|
+
walk(node)
|
|
41
|
+
return expanded
|
|
42
|
+
|
|
43
|
+
def restore_expanded_nodes(self, node, expanded: set[str]) -> None:
|
|
44
|
+
def walk(n):
|
|
45
|
+
data = n.data or {}
|
|
46
|
+
fqn = data.get("fqn")
|
|
47
|
+
|
|
48
|
+
if fqn in expanded:
|
|
49
|
+
n.allow_expand = True
|
|
50
|
+
n.expand()
|
|
51
|
+
|
|
52
|
+
for child in n.children:
|
|
53
|
+
walk(child)
|
|
54
|
+
|
|
55
|
+
walk(node)
|
|
56
|
+
|
|
57
|
+
def on_tree_node_highlighted(self, event: Tree.NodeHighlighted) -> None:
|
|
58
|
+
|
|
59
|
+
if event.node.parent is None:
|
|
60
|
+
self.app.debug_log.clear()
|
|
61
|
+
self.app.debug_log.write(main_screen_text)
|
|
62
|
+
|
|
63
|
+
if not event.node.data: return
|
|
64
|
+
|
|
65
|
+
if camera := event.node.data.get("camera"):
|
|
66
|
+
if event.node.label.plain == camera.name:
|
|
67
|
+
self.app.debug_log.clear()
|
|
68
|
+
self.app.debug_log.write("The camera can be rebooted using the 'r' key")
|
|
69
|
+
|
|
70
|
+
if not (fqn := event.node.data.get("fqn")): return
|
|
71
|
+
|
|
72
|
+
sfqn = re.sub(r"\[\d+\]", "[*]", fqn)
|
|
73
|
+
self.app.debug_log.clear()
|
|
74
|
+
self.app.debug_log.write(fqn)
|
|
75
|
+
if desc := field_descriptions.get(sfqn):
|
|
76
|
+
self.app.debug_log.write(desc)
|
|
77
|
+
if fqn == "errors" and camera.errors:
|
|
78
|
+
self.app.debug_log.clear()
|
|
79
|
+
self.app.debug_log.write(camera.errors)
|
|
80
|
+
|
|
81
|
+
def _make_editable_label(self, field: str, value: str) -> Text:
|
|
82
|
+
label = Text()
|
|
83
|
+
label.append("✎ ", style="#66cc66")
|
|
84
|
+
label.append(f"{field}: ")
|
|
85
|
+
label.append(str(value))
|
|
86
|
+
return label
|
|
87
|
+
|
|
88
|
+
def add_camera(self, camera: Camera) -> None:
|
|
89
|
+
label = camera.name
|
|
90
|
+
camera_node = self.root.add(label, expand=False)
|
|
91
|
+
camera_node.data = { "camera": camera }
|
|
92
|
+
for field in fields(camera):
|
|
93
|
+
value = getattr(camera, field.name)
|
|
94
|
+
self._add_value(camera_node, field.name, value, camera)
|
|
95
|
+
if len(self.root.children) == 1:
|
|
96
|
+
self.root.expand()
|
|
97
|
+
|
|
98
|
+
def _add_value(self, parent: TreeNode, name: str, value: object, camera: Camera) -> TreeNode | None:
|
|
99
|
+
|
|
100
|
+
fqn = join_fqn(self.get_fqn(parent), name)
|
|
101
|
+
|
|
102
|
+
if fqn in HIDDEN_FIELDS:
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
if is_editable_field(fqn) and value is not None:
|
|
106
|
+
node = parent.add_leaf(self._make_editable_label(name, str(value)))
|
|
107
|
+
node.data = {"camera": camera, "field": name, "fqn": fqn}
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if value is None:
|
|
111
|
+
if fqn in UNUSED_FIELDS:
|
|
112
|
+
return
|
|
113
|
+
node = parent.add_leaf(Text(f"{name}: None", style="dim"))
|
|
114
|
+
node.data = {"camera": camera, "field": name, "fqn": fqn}
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
if is_dataclass(value):
|
|
118
|
+
node = parent.add(name, expand=False)
|
|
119
|
+
node.data = {"camera": camera, "field": name, "fqn": fqn}
|
|
120
|
+
for field in fields(value):
|
|
121
|
+
child_value = getattr(value, field.name)
|
|
122
|
+
self._add_value(node, field.name, child_value, camera)
|
|
123
|
+
|
|
124
|
+
elif isinstance(value, list):
|
|
125
|
+
if not value:
|
|
126
|
+
if fqn in UNUSED_FIELDS:
|
|
127
|
+
return
|
|
128
|
+
label = Text(f"{name}: list[0]", style="dim")
|
|
129
|
+
node = parent.add_leaf(label)
|
|
130
|
+
node.data = {"camera": camera, "field": name, "fqn": fqn}
|
|
131
|
+
return
|
|
132
|
+
node = parent.add(f"{name}: [{len(value)}]", expand=False)
|
|
133
|
+
node.data = {"camera": camera, "field": name, "fqn": fqn}
|
|
134
|
+
for index, item in enumerate(value):
|
|
135
|
+
self._add_value(node, f"[{index}]", item, camera)
|
|
136
|
+
|
|
137
|
+
elif isinstance(value, dict):
|
|
138
|
+
node = parent.add(f"{name}: dict[{len(value)}]", expand=False)
|
|
139
|
+
node.data = {"camera": camera, "field": name, "fqn": fqn}
|
|
140
|
+
for key, item in value.items():
|
|
141
|
+
self._add_value(node, str(key), item, camera)
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
node = parent.add_leaf(f"{name}: {value}")
|
|
145
|
+
node.data = {"camera": camera, "field": name, "fqn": fqn}
|
|
146
|
+
|
|
147
|
+
return node
|