python-didl-lite 1.2.4__py3-none-any.whl → 1.4.1__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.
- didl_lite/didl_lite.py +595 -417
- didl_lite/utils.py +31 -20
- python_didl_lite-1.4.1.dist-info/METADATA +44 -0
- python_didl_lite-1.4.1.dist-info/RECORD +9 -0
- {python_didl_lite-1.2.4.dist-info → python_didl_lite-1.4.1.dist-info}/WHEEL +1 -1
- python_didl_lite-1.2.4.dist-info/METADATA +0 -28
- python_didl_lite-1.2.4.dist-info/RECORD +0 -9
- {python_didl_lite-1.2.4.dist-info → python_didl_lite-1.4.1.dist-info}/LICENSE.md +0 -0
- {python_didl_lite-1.2.4.dist-info → python_didl_lite-1.4.1.dist-info}/top_level.txt +0 -0
didl_lite/didl_lite.py
CHANGED
|
@@ -1,79 +1,132 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""DIDL-Lite (Digital Item Declaration Language) tools for Python."""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
# pylint: disable=too-many-lines
|
|
4
|
+
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Dict,
|
|
8
|
+
Iterable,
|
|
9
|
+
List,
|
|
10
|
+
Mapping,
|
|
11
|
+
Optional,
|
|
12
|
+
Sequence,
|
|
13
|
+
Tuple,
|
|
14
|
+
Type,
|
|
15
|
+
TypeVar,
|
|
16
|
+
Union,
|
|
17
|
+
)
|
|
11
18
|
from xml.etree import ElementTree as ET
|
|
12
19
|
|
|
13
|
-
import defusedxml.ElementTree
|
|
20
|
+
import defusedxml.ElementTree
|
|
21
|
+
|
|
22
|
+
from .utils import (
|
|
23
|
+
NAMESPACES,
|
|
24
|
+
didl_property_def_key,
|
|
25
|
+
didl_property_key,
|
|
26
|
+
expand_namespace_tag,
|
|
27
|
+
split_namespace_tag,
|
|
28
|
+
to_camel_case,
|
|
29
|
+
)
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
from .utils import to_camel_case
|
|
31
|
+
TDO = TypeVar("TDO", bound="DidlObject") # pylint: disable=invalid-name
|
|
32
|
+
TC = TypeVar("TC", bound="Container") # pylint: disable=invalid-name
|
|
33
|
+
TD = TypeVar("TD", bound="Descriptor") # pylint: disable=invalid-name
|
|
34
|
+
TR = TypeVar("TR", bound="Resource") # pylint: disable=invalid-name
|
|
20
35
|
|
|
21
36
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
TD = TypeVar('TD', bound='Descriptor')
|
|
25
|
-
TR = TypeVar('TR', bound='Resource')
|
|
37
|
+
class DidlLiteException(Exception):
|
|
38
|
+
"""DIDL Lite Exception."""
|
|
26
39
|
|
|
27
40
|
|
|
28
41
|
# region: DidlObjects
|
|
42
|
+
|
|
43
|
+
# upnp_class to python type mapping
|
|
44
|
+
_upnp_class_map: Dict[str, Type["DidlObject"]] = {}
|
|
45
|
+
_upnp_class_map_lowercase: Dict[str, Type["DidlObject"]] = {}
|
|
46
|
+
|
|
47
|
+
|
|
29
48
|
class DidlObject:
|
|
30
|
-
"""DIDL
|
|
31
|
-
|
|
32
|
-
tag
|
|
33
|
-
upnp_class =
|
|
34
|
-
didl_properties_defs = [
|
|
35
|
-
(
|
|
36
|
-
(
|
|
37
|
-
(
|
|
38
|
-
(
|
|
39
|
-
(
|
|
40
|
-
(
|
|
41
|
-
(
|
|
42
|
-
(
|
|
49
|
+
"""DIDL Object."""
|
|
50
|
+
|
|
51
|
+
tag: Optional[str] = None
|
|
52
|
+
upnp_class: str = "object"
|
|
53
|
+
didl_properties_defs: List[Tuple[str, str, str]] = [
|
|
54
|
+
("didl_lite", "@id", "R"),
|
|
55
|
+
("didl_lite", "@parentID", "R"),
|
|
56
|
+
("didl_lite", "@restricted", "R"),
|
|
57
|
+
("dc", "title", "R"),
|
|
58
|
+
("upnp", "class", "R"),
|
|
59
|
+
("dc", "creator", "O"),
|
|
60
|
+
("didl_lite", "res", "O"),
|
|
61
|
+
("upnp", "writeStatus", "O"),
|
|
43
62
|
]
|
|
44
63
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
id: str
|
|
65
|
+
parent_id: str
|
|
66
|
+
res: List["Resource"]
|
|
67
|
+
xml_el: Optional[ET.Element]
|
|
68
|
+
descriptors: Sequence["Descriptor"]
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def __init_subclass__(cls: Type["DidlObject"], **kwargs: Any) -> None:
|
|
72
|
+
"""Create mapping of upnp_class to Python type for fast lookup."""
|
|
73
|
+
super().__init_subclass__(**kwargs)
|
|
74
|
+
assert cls.upnp_class not in _upnp_class_map
|
|
75
|
+
assert cls.upnp_class.lower() not in _upnp_class_map_lowercase
|
|
76
|
+
_upnp_class_map[cls.upnp_class] = cls
|
|
77
|
+
_upnp_class_map_lowercase[cls.upnp_class.lower()] = cls
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
id: str = "",
|
|
82
|
+
parent_id: str = "",
|
|
83
|
+
descriptors: Optional[Sequence["Descriptor"]] = None,
|
|
84
|
+
xml_el: Optional[ET.Element] = None,
|
|
85
|
+
strict: bool = True,
|
|
86
|
+
**properties: Any,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Initialize."""
|
|
89
|
+
# pylint: disable=invalid-name,redefined-builtin,too-many-arguments
|
|
90
|
+
properties["id"] = id
|
|
91
|
+
properties["parent_id"] = parent_id
|
|
92
|
+
properties["class"] = self.upnp_class
|
|
93
|
+
properties["res"] = properties.get("res") or properties.get("resources") or []
|
|
94
|
+
if "resources" in properties:
|
|
95
|
+
del properties["resources"]
|
|
96
|
+
self._ensure_required_properties(strict, properties)
|
|
97
|
+
self._set_property_defaults()
|
|
98
|
+
self._set_properties(properties)
|
|
99
|
+
|
|
100
|
+
self.xml_el = xml_el
|
|
56
101
|
self.descriptors = descriptors if descriptors else []
|
|
57
102
|
|
|
58
|
-
def _ensure_required_properties(
|
|
103
|
+
def _ensure_required_properties(
|
|
104
|
+
self, strict: bool, properties: Mapping[str, Any]
|
|
105
|
+
) -> None:
|
|
59
106
|
"""Check if all required properties are given."""
|
|
107
|
+
if not strict:
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
python_property_keys = {didl_property_key(key) for key in properties}
|
|
111
|
+
|
|
60
112
|
for property_def in self.didl_properties_defs:
|
|
61
113
|
key = didl_property_def_key(property_def)
|
|
62
|
-
if property_def[2] ==
|
|
63
|
-
raise
|
|
114
|
+
if property_def[2] == "R" and key not in python_property_keys:
|
|
115
|
+
raise DidlLiteException(key + " is mandatory")
|
|
64
116
|
|
|
65
|
-
def
|
|
66
|
-
"""
|
|
67
|
-
# ensure we have default/known slots
|
|
117
|
+
def _set_property_defaults(self) -> None:
|
|
118
|
+
"""Ensure we have default/known slots, and set them all to None."""
|
|
68
119
|
for property_def in self.didl_properties_defs:
|
|
69
120
|
key = didl_property_def_key(property_def)
|
|
70
121
|
setattr(self, key, None)
|
|
71
122
|
|
|
123
|
+
def _set_properties(self, properties: Mapping[str, Any]) -> None:
|
|
124
|
+
"""Set attributes from properties."""
|
|
72
125
|
for key, value in properties.items():
|
|
73
126
|
setattr(self, key, value)
|
|
74
127
|
|
|
75
128
|
@classmethod
|
|
76
|
-
def from_xml(cls: Type[TDO], xml_el: ET.Element) -> TDO:
|
|
129
|
+
def from_xml(cls: Type[TDO], xml_el: ET.Element, strict: bool = True) -> TDO:
|
|
77
130
|
"""
|
|
78
131
|
Initialize from an XML node.
|
|
79
132
|
|
|
@@ -89,10 +142,10 @@ class DidlObject:
|
|
|
89
142
|
|
|
90
143
|
# child-nodes
|
|
91
144
|
for xml_child_node in xml_el:
|
|
92
|
-
if xml_child_node.tag ==
|
|
145
|
+
if xml_child_node.tag == expand_namespace_tag("didl_lite:res"):
|
|
93
146
|
continue
|
|
94
147
|
|
|
95
|
-
_, tag =
|
|
148
|
+
_, tag = split_namespace_tag(xml_child_node.tag)
|
|
96
149
|
key = to_camel_case(tag)
|
|
97
150
|
value = xml_child_node.text
|
|
98
151
|
properties[key] = value
|
|
@@ -100,41 +153,42 @@ class DidlObject:
|
|
|
100
153
|
# attributes of child nodes
|
|
101
154
|
parent_key = key
|
|
102
155
|
for attr_key, attr_value in xml_child_node.attrib.items():
|
|
103
|
-
key = parent_key +
|
|
156
|
+
key = parent_key + "_" + to_camel_case(attr_key)
|
|
104
157
|
properties[key] = attr_value
|
|
105
158
|
|
|
106
159
|
# resources
|
|
107
160
|
resources = []
|
|
108
|
-
for res_el in xml_el.findall(
|
|
161
|
+
for res_el in xml_el.findall("./didl_lite:res", NAMESPACES):
|
|
109
162
|
resource = Resource.from_xml(res_el)
|
|
110
163
|
resources.append(resource)
|
|
111
|
-
properties[
|
|
164
|
+
properties["res"] = properties["resources"] = resources
|
|
112
165
|
|
|
113
166
|
# descriptors
|
|
114
167
|
descriptors = []
|
|
115
|
-
for desc_el in xml_el.findall(
|
|
168
|
+
for desc_el in xml_el.findall("./didl_lite:desc", NAMESPACES):
|
|
116
169
|
descriptor = Descriptor.from_xml(desc_el)
|
|
117
170
|
descriptors.append(descriptor)
|
|
118
171
|
|
|
119
|
-
return cls(descriptors=descriptors, **properties)
|
|
172
|
+
return cls(xml_el=xml_el, descriptors=descriptors, strict=strict, **properties)
|
|
120
173
|
|
|
121
174
|
def to_xml(self) -> ET.Element:
|
|
122
175
|
"""Convert self to XML Element."""
|
|
123
176
|
assert self.tag is not None
|
|
124
177
|
item_el = ET.Element(self.tag)
|
|
125
|
-
elements = {
|
|
178
|
+
elements = {"": item_el}
|
|
126
179
|
|
|
127
180
|
# properties
|
|
128
181
|
for property_def in self.didl_properties_defs:
|
|
129
|
-
if
|
|
182
|
+
if "@" in property_def[1]:
|
|
130
183
|
continue
|
|
131
184
|
key = didl_property_def_key(property_def)
|
|
132
185
|
|
|
133
|
-
if
|
|
134
|
-
|
|
186
|
+
if (
|
|
187
|
+
getattr(self, key) is None or key == "res"
|
|
188
|
+
): # no resources, handled later on
|
|
135
189
|
continue
|
|
136
190
|
|
|
137
|
-
tag = property_def[0] +
|
|
191
|
+
tag = property_def[0] + ":" + property_def[1]
|
|
138
192
|
property_el = ET.Element(tag, {})
|
|
139
193
|
property_el.text = getattr(self, key)
|
|
140
194
|
item_el.append(property_el)
|
|
@@ -142,7 +196,7 @@ class DidlObject:
|
|
|
142
196
|
|
|
143
197
|
# attributes and property@attributes
|
|
144
198
|
for property_def in self.didl_properties_defs:
|
|
145
|
-
if
|
|
199
|
+
if "@" not in property_def[1]:
|
|
146
200
|
continue
|
|
147
201
|
|
|
148
202
|
key = didl_property_def_key(property_def)
|
|
@@ -150,7 +204,7 @@ class DidlObject:
|
|
|
150
204
|
if value is None:
|
|
151
205
|
continue
|
|
152
206
|
|
|
153
|
-
el_name, attr_name = property_def[1].split(
|
|
207
|
+
el_name, attr_name = property_def[1].split("@")
|
|
154
208
|
property_el = elements[el_name]
|
|
155
209
|
property_el.attrib[attr_name] = value
|
|
156
210
|
|
|
@@ -167,10 +221,35 @@ class DidlObject:
|
|
|
167
221
|
return item_el
|
|
168
222
|
|
|
169
223
|
def __getattr__(self, name: str) -> Any:
|
|
170
|
-
"""Get attribute."""
|
|
171
|
-
if name
|
|
224
|
+
"""Get attribute, modifying case as needed."""
|
|
225
|
+
if name == "resources":
|
|
226
|
+
return getattr(self, "res")
|
|
227
|
+
if name in self.__dict__:
|
|
228
|
+
return self.__dict__[name]
|
|
229
|
+
cleaned_name = didl_property_key(name)
|
|
230
|
+
if cleaned_name not in self.__dict__:
|
|
172
231
|
raise AttributeError(name)
|
|
173
|
-
return self.__dict__[
|
|
232
|
+
return self.__dict__[cleaned_name]
|
|
233
|
+
|
|
234
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
235
|
+
"""Set attribute, modifying case as needed."""
|
|
236
|
+
if name not in self.__dict__:
|
|
237
|
+
# Redirect to the lower_camel_case version if it's already set,
|
|
238
|
+
# which is the case for all defined didl properties.
|
|
239
|
+
cleaned_name = didl_property_key(name)
|
|
240
|
+
if cleaned_name in self.__dict__:
|
|
241
|
+
name = cleaned_name
|
|
242
|
+
self.__dict__[name] = value
|
|
243
|
+
|
|
244
|
+
def __repr__(self) -> str:
|
|
245
|
+
"""Evaluatable string representation of this object."""
|
|
246
|
+
class_name = type(self).__name__
|
|
247
|
+
attr = ", ".join(
|
|
248
|
+
f"{key}={val!r}"
|
|
249
|
+
for key, val in self.__dict__.items()
|
|
250
|
+
if key not in ("class", "xml_el")
|
|
251
|
+
)
|
|
252
|
+
return f"{class_name}({attr})"
|
|
174
253
|
|
|
175
254
|
|
|
176
255
|
# region: items
|
|
@@ -179,11 +258,11 @@ class Item(DidlObject):
|
|
|
179
258
|
|
|
180
259
|
# pylint: disable=too-few-public-methods
|
|
181
260
|
|
|
182
|
-
tag =
|
|
183
|
-
upnp_class =
|
|
261
|
+
tag = "item"
|
|
262
|
+
upnp_class = "object.item"
|
|
184
263
|
didl_properties_defs = DidlObject.didl_properties_defs + [
|
|
185
|
-
(
|
|
186
|
-
(
|
|
264
|
+
("didl_lite", "@refID", "O"), # actually, R, but ignore for now
|
|
265
|
+
("upnp", "bookmarkID", "O"),
|
|
187
266
|
]
|
|
188
267
|
|
|
189
268
|
|
|
@@ -192,15 +271,15 @@ class ImageItem(Item):
|
|
|
192
271
|
|
|
193
272
|
# pylint: disable=too-few-public-methods
|
|
194
273
|
|
|
195
|
-
upnp_class =
|
|
274
|
+
upnp_class = "object.item.imageItem"
|
|
196
275
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
197
|
-
(
|
|
198
|
-
(
|
|
199
|
-
(
|
|
200
|
-
(
|
|
201
|
-
(
|
|
202
|
-
(
|
|
203
|
-
(
|
|
276
|
+
("upnp", "longDescription", "O"),
|
|
277
|
+
("upnp", "storageMedium", "O"),
|
|
278
|
+
("upnp", "rating", "O"),
|
|
279
|
+
("dc", "description", "O"),
|
|
280
|
+
("dc", "publisher", "O"),
|
|
281
|
+
("dc", "date", "O"),
|
|
282
|
+
("dc", "rights", "O"),
|
|
204
283
|
]
|
|
205
284
|
|
|
206
285
|
|
|
@@ -209,9 +288,9 @@ class Photo(ImageItem):
|
|
|
209
288
|
|
|
210
289
|
# pylint: disable=too-few-public-methods
|
|
211
290
|
|
|
212
|
-
upnp_class =
|
|
291
|
+
upnp_class = "object.item.imageItem.photo"
|
|
213
292
|
didl_properties_defs = ImageItem.didl_properties_defs + [
|
|
214
|
-
(
|
|
293
|
+
("upnp", "album", "O"),
|
|
215
294
|
]
|
|
216
295
|
|
|
217
296
|
|
|
@@ -220,15 +299,15 @@ class AudioItem(Item):
|
|
|
220
299
|
|
|
221
300
|
# pylint: disable=too-few-public-methods
|
|
222
301
|
|
|
223
|
-
upnp_class =
|
|
302
|
+
upnp_class = "object.item.audioItem"
|
|
224
303
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
225
|
-
(
|
|
226
|
-
(
|
|
227
|
-
(
|
|
228
|
-
(
|
|
229
|
-
(
|
|
230
|
-
(
|
|
231
|
-
(
|
|
304
|
+
("upnp", "genre", "O"),
|
|
305
|
+
("dc", "description", "O"),
|
|
306
|
+
("upnp", "longDescription", "O"),
|
|
307
|
+
("dc", "publisher", "O"),
|
|
308
|
+
("dc", "language", "O"),
|
|
309
|
+
("dc", "relation", "O"),
|
|
310
|
+
("dc", "rights", "O"),
|
|
232
311
|
]
|
|
233
312
|
|
|
234
313
|
|
|
@@ -237,15 +316,15 @@ class MusicTrack(AudioItem):
|
|
|
237
316
|
|
|
238
317
|
# pylint: disable=too-few-public-methods
|
|
239
318
|
|
|
240
|
-
upnp_class =
|
|
319
|
+
upnp_class = "object.item.audioItem.musicTrack"
|
|
241
320
|
didl_properties_defs = AudioItem.didl_properties_defs + [
|
|
242
|
-
(
|
|
243
|
-
(
|
|
244
|
-
(
|
|
245
|
-
(
|
|
246
|
-
(
|
|
247
|
-
(
|
|
248
|
-
(
|
|
321
|
+
("upnp", "artist", "O"),
|
|
322
|
+
("upnp", "album", "O"),
|
|
323
|
+
("upnp", "originalTrackNumber", "O"),
|
|
324
|
+
("upnp", "playlist", "O"),
|
|
325
|
+
("upnp", "storageMedium", "O"),
|
|
326
|
+
("dc", "contributor", "O"),
|
|
327
|
+
("dc", "date", "O"),
|
|
249
328
|
]
|
|
250
329
|
|
|
251
330
|
|
|
@@ -254,17 +333,17 @@ class AudioBroadcast(AudioItem):
|
|
|
254
333
|
|
|
255
334
|
# pylint: disable=too-few-public-methods
|
|
256
335
|
|
|
257
|
-
upnp_class =
|
|
336
|
+
upnp_class = "object.item.audioItem.audioBroadcast"
|
|
258
337
|
didl_properties_defs = AudioItem.didl_properties_defs + [
|
|
259
|
-
(
|
|
260
|
-
(
|
|
261
|
-
(
|
|
262
|
-
(
|
|
263
|
-
(
|
|
264
|
-
(
|
|
265
|
-
(
|
|
266
|
-
(
|
|
267
|
-
(
|
|
338
|
+
("upnp", "region", "O"),
|
|
339
|
+
("upnp", "radioCallSign", "O"),
|
|
340
|
+
("upnp", "radioStationID", "O"),
|
|
341
|
+
("upnp", "radioBand", "O"),
|
|
342
|
+
("upnp", "channelNr", "O"),
|
|
343
|
+
("upnp", "signalStrength", "O"),
|
|
344
|
+
("upnp", "signalLocked", "O"),
|
|
345
|
+
("upnp", "tuned", "O"),
|
|
346
|
+
("upnp", "recordable", "O"),
|
|
268
347
|
]
|
|
269
348
|
|
|
270
349
|
|
|
@@ -273,12 +352,12 @@ class AudioBook(AudioItem):
|
|
|
273
352
|
|
|
274
353
|
# pylint: disable=too-few-public-methods
|
|
275
354
|
|
|
276
|
-
upnp_class =
|
|
355
|
+
upnp_class = "object.item.audioItem.audioBook"
|
|
277
356
|
didl_properties_defs = AudioItem.didl_properties_defs + [
|
|
278
|
-
(
|
|
279
|
-
(
|
|
280
|
-
(
|
|
281
|
-
(
|
|
357
|
+
("upnp", "storageMedium", "O"),
|
|
358
|
+
("upnp", "producer", "O"),
|
|
359
|
+
("dc", "contributor", "O"),
|
|
360
|
+
("dc", "date", "O"),
|
|
282
361
|
]
|
|
283
362
|
|
|
284
363
|
|
|
@@ -287,25 +366,25 @@ class VideoItem(Item):
|
|
|
287
366
|
|
|
288
367
|
# pylint: disable=too-few-public-methods
|
|
289
368
|
|
|
290
|
-
upnp_class =
|
|
369
|
+
upnp_class = "object.item.videoItem"
|
|
291
370
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
292
|
-
(
|
|
293
|
-
(
|
|
294
|
-
(
|
|
295
|
-
(
|
|
296
|
-
(
|
|
297
|
-
(
|
|
298
|
-
(
|
|
299
|
-
(
|
|
300
|
-
(
|
|
301
|
-
(
|
|
302
|
-
(
|
|
303
|
-
(
|
|
304
|
-
(
|
|
305
|
-
(
|
|
306
|
-
(
|
|
307
|
-
(
|
|
308
|
-
(
|
|
371
|
+
("upnp", "genre", "O"),
|
|
372
|
+
("upnp", "genre@id", "O"),
|
|
373
|
+
("upnp", "genre@type", "O"),
|
|
374
|
+
("upnp", "longDescription", "O"),
|
|
375
|
+
("upnp", "producer", "O"),
|
|
376
|
+
("upnp", "rating", "O"),
|
|
377
|
+
("upnp", "actor", "O"),
|
|
378
|
+
("upnp", "director", "O"),
|
|
379
|
+
("dc", "description", "O"),
|
|
380
|
+
("dc", "publisher", "O"),
|
|
381
|
+
("dc", "language", "O"),
|
|
382
|
+
("dc", "relation", "O"),
|
|
383
|
+
("upnp", "playbackCount", "O"),
|
|
384
|
+
("upnp", "lastPlaybackTime", "O"),
|
|
385
|
+
("upnp", "lastPlaybackPosition", "O"),
|
|
386
|
+
("upnp", "recordedDayOfWeek", "O"),
|
|
387
|
+
("upnp", "srsRecordScheduleID", "O"),
|
|
309
388
|
]
|
|
310
389
|
|
|
311
390
|
|
|
@@ -314,17 +393,18 @@ class Movie(VideoItem):
|
|
|
314
393
|
|
|
315
394
|
# pylint: disable=too-few-public-methods
|
|
316
395
|
|
|
317
|
-
upnp_class =
|
|
396
|
+
upnp_class = "object.item.videoItem.movie"
|
|
318
397
|
didl_properties_defs = VideoItem.didl_properties_defs + [
|
|
319
|
-
(
|
|
320
|
-
(
|
|
321
|
-
(
|
|
322
|
-
(
|
|
323
|
-
(
|
|
324
|
-
(
|
|
325
|
-
(
|
|
326
|
-
(
|
|
327
|
-
(
|
|
398
|
+
("upnp", "storageMedium", "O"),
|
|
399
|
+
("upnp", "DVDRegionCode", "O"),
|
|
400
|
+
("upnp", "channelName", "O"),
|
|
401
|
+
("upnp", "scheduledStartTime", "O"),
|
|
402
|
+
("upnp", "scheduledEndTime", "O"),
|
|
403
|
+
("upnp", "programTitle", "O"),
|
|
404
|
+
("upnp", "seriesTitle", "O"),
|
|
405
|
+
("upnp", "episodeCount", "O"),
|
|
406
|
+
("upnp", "episodeNumber", "O"),
|
|
407
|
+
("upnp", "episodeSeason", "O"),
|
|
328
408
|
]
|
|
329
409
|
|
|
330
410
|
|
|
@@ -333,18 +413,18 @@ class VideoBroadcast(VideoItem):
|
|
|
333
413
|
|
|
334
414
|
# pylint: disable=too-few-public-methods
|
|
335
415
|
|
|
336
|
-
upnp_class =
|
|
416
|
+
upnp_class = "object.item.videoItem.videoBroadcast"
|
|
337
417
|
didl_properties_defs = VideoItem.didl_properties_defs + [
|
|
338
|
-
(
|
|
339
|
-
(
|
|
340
|
-
(
|
|
341
|
-
(
|
|
342
|
-
(
|
|
343
|
-
(
|
|
344
|
-
(
|
|
345
|
-
(
|
|
346
|
-
(
|
|
347
|
-
(
|
|
418
|
+
("upnp", "icon", "O"),
|
|
419
|
+
("upnp", "region", "O"),
|
|
420
|
+
("upnp", "channelNr", "O"),
|
|
421
|
+
("upnp", "signalStrength", "O"),
|
|
422
|
+
("upnp", "signalLocked", "O"),
|
|
423
|
+
("upnp", "tuned", "O"),
|
|
424
|
+
("upnp", "recordable", "O"),
|
|
425
|
+
("upnp", "callSign", "O"),
|
|
426
|
+
("upnp", "price", "O"),
|
|
427
|
+
("upnp", "payPerView", "O"),
|
|
348
428
|
]
|
|
349
429
|
|
|
350
430
|
|
|
@@ -353,16 +433,16 @@ class MusicVideoClip(VideoItem):
|
|
|
353
433
|
|
|
354
434
|
# pylint: disable=too-few-public-methods
|
|
355
435
|
|
|
356
|
-
upnp_class =
|
|
436
|
+
upnp_class = "object.item.videoItem.musicVideoClip"
|
|
357
437
|
didl_properties_defs = VideoItem.didl_properties_defs + [
|
|
358
|
-
(
|
|
359
|
-
(
|
|
360
|
-
(
|
|
361
|
-
(
|
|
362
|
-
(
|
|
438
|
+
("upnp", "artist", "O"),
|
|
439
|
+
("upnp", "storageMedium", "O"),
|
|
440
|
+
("upnp", "album", "O"),
|
|
441
|
+
("upnp", "scheduledStartTime", "O"),
|
|
442
|
+
("upnp", "scheduledStopTime", "O"),
|
|
363
443
|
# ('upnp', 'director', 'O'), # duplicate in standard
|
|
364
|
-
(
|
|
365
|
-
(
|
|
444
|
+
("dc", "contributor", "O"),
|
|
445
|
+
("dc", "date", "O"),
|
|
366
446
|
]
|
|
367
447
|
|
|
368
448
|
|
|
@@ -371,15 +451,15 @@ class PlaylistItem(Item):
|
|
|
371
451
|
|
|
372
452
|
# pylint: disable=too-few-public-methods
|
|
373
453
|
|
|
374
|
-
upnp_class =
|
|
454
|
+
upnp_class = "object.item.playlistItem"
|
|
375
455
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
376
|
-
(
|
|
377
|
-
(
|
|
378
|
-
(
|
|
379
|
-
(
|
|
380
|
-
(
|
|
381
|
-
(
|
|
382
|
-
(
|
|
456
|
+
("upnp", "artist", "O"),
|
|
457
|
+
("upnp", "genre", "O"),
|
|
458
|
+
("upnp", "longDescription", "O"),
|
|
459
|
+
("upnp", "storageMedium", "O"),
|
|
460
|
+
("dc", "description", "O"),
|
|
461
|
+
("dc", "date", "O"),
|
|
462
|
+
("dc", "language", "O"),
|
|
383
463
|
]
|
|
384
464
|
|
|
385
465
|
|
|
@@ -388,20 +468,20 @@ class TextItem(Item):
|
|
|
388
468
|
|
|
389
469
|
# pylint: disable=too-few-public-methods
|
|
390
470
|
|
|
391
|
-
upnp_class =
|
|
471
|
+
upnp_class = "object.item.textItem"
|
|
392
472
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
393
|
-
(
|
|
394
|
-
(
|
|
395
|
-
(
|
|
396
|
-
(
|
|
397
|
-
(
|
|
398
|
-
(
|
|
399
|
-
(
|
|
400
|
-
(
|
|
401
|
-
(
|
|
402
|
-
(
|
|
403
|
-
(
|
|
404
|
-
(
|
|
473
|
+
("upnp", "author", "O"),
|
|
474
|
+
("upnp", "res@protection", "O"),
|
|
475
|
+
("upnp", "longDescription", "O"),
|
|
476
|
+
("upnp", "storageMedium", "O"),
|
|
477
|
+
("upnp", "rating", "O"),
|
|
478
|
+
("dc", "description", "O"),
|
|
479
|
+
("dc", "publisher", "O"),
|
|
480
|
+
("dc", "contributor", "O"),
|
|
481
|
+
("dc", "date", "O"),
|
|
482
|
+
("dc", "relation", "O"),
|
|
483
|
+
("dc", "language", "O"),
|
|
484
|
+
("dc", "rights", "O"),
|
|
405
485
|
]
|
|
406
486
|
|
|
407
487
|
|
|
@@ -410,15 +490,15 @@ class BookmarkItem(Item):
|
|
|
410
490
|
|
|
411
491
|
# pylint: disable=too-few-public-methods
|
|
412
492
|
|
|
413
|
-
upnp_class =
|
|
493
|
+
upnp_class = "object.item.bookmarkItem"
|
|
414
494
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
415
|
-
(
|
|
416
|
-
(
|
|
417
|
-
(
|
|
418
|
-
(
|
|
419
|
-
(
|
|
420
|
-
(
|
|
421
|
-
(
|
|
495
|
+
("upnp", "bookmarkedObjectID", "R"),
|
|
496
|
+
("upnp", "neverPlayable", "O"),
|
|
497
|
+
("upnp", "deviceUDN", "R"),
|
|
498
|
+
("upnp", "serviceType", "R"),
|
|
499
|
+
("upnp", "serviceId", "R"),
|
|
500
|
+
("dc", "date", "O"),
|
|
501
|
+
("dc", "stateVariableCollection", "R"),
|
|
422
502
|
]
|
|
423
503
|
|
|
424
504
|
|
|
@@ -427,57 +507,67 @@ class EpgItem(Item):
|
|
|
427
507
|
|
|
428
508
|
# pylint: disable=too-few-public-methods
|
|
429
509
|
|
|
430
|
-
upnp_class =
|
|
510
|
+
upnp_class = "object.item.epgItem"
|
|
431
511
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
432
|
-
(
|
|
433
|
-
(
|
|
434
|
-
(
|
|
435
|
-
(
|
|
436
|
-
(
|
|
437
|
-
(
|
|
438
|
-
(
|
|
439
|
-
(
|
|
440
|
-
(
|
|
441
|
-
(
|
|
442
|
-
(
|
|
443
|
-
(
|
|
444
|
-
(
|
|
445
|
-
(
|
|
446
|
-
(
|
|
447
|
-
(
|
|
448
|
-
(
|
|
449
|
-
(
|
|
450
|
-
(
|
|
451
|
-
(
|
|
452
|
-
(
|
|
453
|
-
(
|
|
454
|
-
(
|
|
455
|
-
(
|
|
456
|
-
(
|
|
457
|
-
(
|
|
458
|
-
(
|
|
459
|
-
(
|
|
460
|
-
(
|
|
461
|
-
(
|
|
462
|
-
(
|
|
463
|
-
(
|
|
464
|
-
(
|
|
465
|
-
(
|
|
466
|
-
(
|
|
512
|
+
("upnp", "channelGroupName", "O"),
|
|
513
|
+
("upnp", "channelGroupName@id", "O"),
|
|
514
|
+
("upnp", "epgProviderName", "O"),
|
|
515
|
+
("upnp", "serviceProvider", "O"),
|
|
516
|
+
("upnp", "channelName", "O"),
|
|
517
|
+
("upnp", "channelNr", "O"),
|
|
518
|
+
("upnp", "programTitle", "O"),
|
|
519
|
+
("upnp", "seriesTitle", "O"),
|
|
520
|
+
("upnp", "programID", "O"),
|
|
521
|
+
("upnp", "programID@type", "O"),
|
|
522
|
+
("upnp", "seriesID", "O"),
|
|
523
|
+
("upnp", "seriesID@type", "O"),
|
|
524
|
+
("upnp", "channelID", "O"),
|
|
525
|
+
("upnp", "channelID@type", "O"),
|
|
526
|
+
("upnp", "channelID@distriNetworkName", "O"),
|
|
527
|
+
("upnp", "channelID@distriNetworkID", "O"),
|
|
528
|
+
("upnp", "episodeType", "O"),
|
|
529
|
+
("upnp", "episodeCount", "O"),
|
|
530
|
+
("upnp", "episodeNumber", "O"),
|
|
531
|
+
("upnp", "episodeSeason", "O"),
|
|
532
|
+
("upnp", "programCode", "O"),
|
|
533
|
+
("upnp", "programCode@type", "O"),
|
|
534
|
+
("upnp", "rating", "O"),
|
|
535
|
+
("upnp", "rating@type", "O"),
|
|
536
|
+
("upnp", "rating@advice", "O"),
|
|
537
|
+
("upnp", "rating@equivalentAge", "O"),
|
|
538
|
+
("upnp", "recommendationID", "O"),
|
|
539
|
+
("upnp", "recommendationID@type", "O"),
|
|
540
|
+
("upnp", "genre", "O"),
|
|
541
|
+
("upnp", "genre@id", "O"),
|
|
542
|
+
("upnp", "genre@extended", "O"),
|
|
543
|
+
("upnp", "artist", "O"),
|
|
544
|
+
("upnp", "artist@role", "O"),
|
|
545
|
+
("upnp", "actor", "O"),
|
|
546
|
+
("upnp", "actor@role", "O"),
|
|
547
|
+
("upnp", "author", "O"),
|
|
548
|
+
("upnp", "author@role", "O"),
|
|
549
|
+
("upnp", "producer", "O"),
|
|
550
|
+
("upnp", "director", "O"),
|
|
551
|
+
("dc", "publisher", "O"),
|
|
552
|
+
("dc", "contributor", "O"),
|
|
553
|
+
("upnp", "callSign", "O"),
|
|
554
|
+
("upnp", "networkAffiliation", "O"),
|
|
467
555
|
# ('upnp', 'serviceProvider', 'O'), # duplicate in standard
|
|
468
|
-
(
|
|
469
|
-
(
|
|
470
|
-
(
|
|
556
|
+
("upnp", "price", "O"),
|
|
557
|
+
("upnp", "price@currency", "O"),
|
|
558
|
+
("upnp", "payPerView", "O"),
|
|
471
559
|
# ('upnp', 'epgProviderName', 'O'), # duplicate in standard
|
|
472
|
-
(
|
|
473
|
-
(
|
|
474
|
-
(
|
|
475
|
-
(
|
|
476
|
-
(
|
|
477
|
-
(
|
|
478
|
-
(
|
|
479
|
-
(
|
|
480
|
-
(
|
|
560
|
+
("dc", "description", "O"),
|
|
561
|
+
("upnp", "longDescription", "O"),
|
|
562
|
+
("upnp", "icon", "O"),
|
|
563
|
+
("upnp", "region", "O"),
|
|
564
|
+
("upnp", "rights", "O"),
|
|
565
|
+
("dc", "language", "O"),
|
|
566
|
+
("dc", "relation", "O"),
|
|
567
|
+
("upnp", "scheduledStartTime", "O"),
|
|
568
|
+
("upnp", "scheduledEndTime", "O"),
|
|
569
|
+
("upnp", "recordable", "O"),
|
|
570
|
+
("upnp", "foreignMetadata", "O"),
|
|
481
571
|
]
|
|
482
572
|
|
|
483
573
|
|
|
@@ -486,11 +576,11 @@ class AudioProgram(EpgItem):
|
|
|
486
576
|
|
|
487
577
|
# pylint: disable=too-few-public-methods
|
|
488
578
|
|
|
489
|
-
upnp_class =
|
|
579
|
+
upnp_class = "object.item.epgItem.audioProgram"
|
|
490
580
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
491
|
-
(
|
|
492
|
-
(
|
|
493
|
-
(
|
|
581
|
+
("upnp", "radioCallSign", "O"),
|
|
582
|
+
("upnp", "radioStationID", "O"),
|
|
583
|
+
("upnp", "radioBand", "O"),
|
|
494
584
|
]
|
|
495
585
|
|
|
496
586
|
|
|
@@ -499,12 +589,14 @@ class VideoProgram(EpgItem):
|
|
|
499
589
|
|
|
500
590
|
# pylint: disable=too-few-public-methods
|
|
501
591
|
|
|
502
|
-
upnp_class =
|
|
592
|
+
upnp_class = "object.item.epgItem.videoProgram"
|
|
503
593
|
didl_properties_defs = Item.didl_properties_defs + [
|
|
504
|
-
(
|
|
505
|
-
(
|
|
506
|
-
(
|
|
594
|
+
("upnp", "price", "O"),
|
|
595
|
+
("upnp", "price@currency", "O"),
|
|
596
|
+
("upnp", "payPerView", "O"),
|
|
507
597
|
]
|
|
598
|
+
|
|
599
|
+
|
|
508
600
|
# endregion
|
|
509
601
|
|
|
510
602
|
|
|
@@ -514,27 +606,42 @@ class Container(DidlObject, list):
|
|
|
514
606
|
|
|
515
607
|
# pylint: disable=too-few-public-methods
|
|
516
608
|
|
|
517
|
-
tag =
|
|
518
|
-
upnp_class =
|
|
609
|
+
tag = "container"
|
|
610
|
+
upnp_class = "object.container"
|
|
519
611
|
didl_properties_defs = DidlObject.didl_properties_defs + [
|
|
520
|
-
(
|
|
521
|
-
(
|
|
522
|
-
(
|
|
523
|
-
(
|
|
524
|
-
(
|
|
612
|
+
("didl_lite", "@childCount", "O"),
|
|
613
|
+
("upnp", "createClass", "O"),
|
|
614
|
+
("upnp", "searchClass", "O"),
|
|
615
|
+
("didl_lite", "@searchable", "O"),
|
|
616
|
+
("didl_lite", "@neverPlayable", "O"),
|
|
525
617
|
]
|
|
526
618
|
|
|
619
|
+
def __init__(
|
|
620
|
+
self,
|
|
621
|
+
id: str = "",
|
|
622
|
+
parent_id: str = "",
|
|
623
|
+
descriptors: Optional[Sequence["Descriptor"]] = None,
|
|
624
|
+
xml_el: Optional[ET.Element] = None,
|
|
625
|
+
strict: bool = True,
|
|
626
|
+
children: Iterable[DidlObject] = (),
|
|
627
|
+
**properties: Any,
|
|
628
|
+
) -> None:
|
|
629
|
+
"""Initialize."""
|
|
630
|
+
# pylint: disable=redefined-builtin,too-many-arguments
|
|
631
|
+
super().__init__(id, parent_id, descriptors, xml_el, strict, **properties)
|
|
632
|
+
self.extend(children)
|
|
633
|
+
|
|
527
634
|
@classmethod
|
|
528
|
-
def from_xml(cls: Type[TC], xml_el: ET.Element) -> TC:
|
|
635
|
+
def from_xml(cls: Type[TC], xml_el: ET.Element, strict: bool = True) -> TC:
|
|
529
636
|
"""
|
|
530
637
|
Initialize from an XML node.
|
|
531
638
|
|
|
532
639
|
I.e., parse XML and return instance.
|
|
533
640
|
"""
|
|
534
|
-
instance = super().from_xml(xml_el)
|
|
641
|
+
instance = super().from_xml(xml_el, strict)
|
|
535
642
|
|
|
536
643
|
# add all children
|
|
537
|
-
didl_objects = from_xml_el(xml_el)
|
|
644
|
+
didl_objects = from_xml_el(xml_el, strict)
|
|
538
645
|
instance.extend(didl_objects) # pylint: disable=no-member
|
|
539
646
|
|
|
540
647
|
return instance
|
|
@@ -549,15 +656,26 @@ class Container(DidlObject, list):
|
|
|
549
656
|
|
|
550
657
|
return container_el
|
|
551
658
|
|
|
659
|
+
def __repr__(self) -> str:
|
|
660
|
+
"""Evaluatable string representation of this object."""
|
|
661
|
+
class_name = type(self).__name__
|
|
662
|
+
attr = ", ".join(
|
|
663
|
+
f"{key}={val!r}"
|
|
664
|
+
for key, val in self.__dict__.items()
|
|
665
|
+
if key not in ("class", "xml_el")
|
|
666
|
+
)
|
|
667
|
+
children_repr = ", ".join(repr(child) for child in self)
|
|
668
|
+
return f"{class_name}({attr}, children=[{children_repr}])"
|
|
669
|
+
|
|
552
670
|
|
|
553
671
|
class Person(Container):
|
|
554
672
|
"""DIDL Person."""
|
|
555
673
|
|
|
556
674
|
# pylint: disable=too-few-public-methods
|
|
557
675
|
|
|
558
|
-
upnp_class =
|
|
676
|
+
upnp_class = "object.container.person"
|
|
559
677
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
560
|
-
(
|
|
678
|
+
("dc", "language", "O"),
|
|
561
679
|
]
|
|
562
680
|
|
|
563
681
|
|
|
@@ -566,10 +684,10 @@ class MusicArtist(Person):
|
|
|
566
684
|
|
|
567
685
|
# pylint: disable=too-few-public-methods
|
|
568
686
|
|
|
569
|
-
upnp_class =
|
|
687
|
+
upnp_class = "object.container.person.musicArtist"
|
|
570
688
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
571
|
-
(
|
|
572
|
-
(
|
|
689
|
+
("upnp", "genre", "O"),
|
|
690
|
+
("upnp", "artistDiscographyURI", "O"),
|
|
573
691
|
]
|
|
574
692
|
|
|
575
693
|
|
|
@@ -578,18 +696,18 @@ class PlaylistContainer(Container):
|
|
|
578
696
|
|
|
579
697
|
# pylint: disable=too-few-public-methods
|
|
580
698
|
|
|
581
|
-
upnp_class =
|
|
699
|
+
upnp_class = "object.container.playlistContainer"
|
|
582
700
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
583
|
-
(
|
|
584
|
-
(
|
|
585
|
-
(
|
|
586
|
-
(
|
|
587
|
-
(
|
|
588
|
-
(
|
|
589
|
-
(
|
|
590
|
-
(
|
|
591
|
-
(
|
|
592
|
-
(
|
|
701
|
+
("upnp", "artist", "O"),
|
|
702
|
+
("upnp", "genre", "O"),
|
|
703
|
+
("upnp", "longDescription", "O"),
|
|
704
|
+
("upnp", "producer", "O"),
|
|
705
|
+
("upnp", "storageMedium", "O"),
|
|
706
|
+
("dc", "description", "O"),
|
|
707
|
+
("dc", "contributor", "O"),
|
|
708
|
+
("dc", "date", "O"),
|
|
709
|
+
("dc", "language", "O"),
|
|
710
|
+
("dc", "rights", "O"),
|
|
593
711
|
]
|
|
594
712
|
|
|
595
713
|
|
|
@@ -598,16 +716,16 @@ class Album(Container):
|
|
|
598
716
|
|
|
599
717
|
# pylint: disable=too-few-public-methods
|
|
600
718
|
|
|
601
|
-
upnp_class =
|
|
719
|
+
upnp_class = "object.container.album"
|
|
602
720
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
603
|
-
(
|
|
604
|
-
(
|
|
605
|
-
(
|
|
606
|
-
(
|
|
607
|
-
(
|
|
608
|
-
(
|
|
609
|
-
(
|
|
610
|
-
(
|
|
721
|
+
("upnp", "storageMedium", "O"),
|
|
722
|
+
("dc", "longDescription", "O"),
|
|
723
|
+
("dc", "description", "O"),
|
|
724
|
+
("dc", "publisher", "O"),
|
|
725
|
+
("dc", "contributor", "O"),
|
|
726
|
+
("dc", "date", "O"),
|
|
727
|
+
("dc", "relation", "O"),
|
|
728
|
+
("dc", "rights", "O"),
|
|
611
729
|
]
|
|
612
730
|
|
|
613
731
|
|
|
@@ -616,13 +734,13 @@ class MusicAlbum(Album):
|
|
|
616
734
|
|
|
617
735
|
# pylint: disable=too-few-public-methods
|
|
618
736
|
|
|
619
|
-
upnp_class =
|
|
737
|
+
upnp_class = "object.container.album.musicAlbum"
|
|
620
738
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
621
|
-
(
|
|
622
|
-
(
|
|
623
|
-
(
|
|
624
|
-
(
|
|
625
|
-
(
|
|
739
|
+
("upnp", "artist", "O"),
|
|
740
|
+
("upnp", "genre", "O"),
|
|
741
|
+
("upnp", "producer", "O"),
|
|
742
|
+
("upnp", "albumArtURI", "O"),
|
|
743
|
+
("upnp", "toc", "O"),
|
|
626
744
|
]
|
|
627
745
|
|
|
628
746
|
|
|
@@ -631,9 +749,8 @@ class PhotoAlbum(Album):
|
|
|
631
749
|
|
|
632
750
|
# pylint: disable=too-few-public-methods
|
|
633
751
|
|
|
634
|
-
upnp_class =
|
|
635
|
-
didl_properties_defs = Container.didl_properties_defs + [
|
|
636
|
-
]
|
|
752
|
+
upnp_class = "object.container.album.photoAlbum"
|
|
753
|
+
didl_properties_defs = Container.didl_properties_defs + []
|
|
637
754
|
|
|
638
755
|
|
|
639
756
|
class Genre(Container):
|
|
@@ -641,11 +758,11 @@ class Genre(Container):
|
|
|
641
758
|
|
|
642
759
|
# pylint: disable=too-few-public-methods
|
|
643
760
|
|
|
644
|
-
upnp_class =
|
|
761
|
+
upnp_class = "object.container.genre"
|
|
645
762
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
646
|
-
(
|
|
647
|
-
(
|
|
648
|
-
(
|
|
763
|
+
("upnp", "genre", "O"),
|
|
764
|
+
("upnp", "longDescription", "O"),
|
|
765
|
+
("dc", "description", "O"),
|
|
649
766
|
]
|
|
650
767
|
|
|
651
768
|
|
|
@@ -654,9 +771,8 @@ class MusicGenre(Genre):
|
|
|
654
771
|
|
|
655
772
|
# pylint: disable=too-few-public-methods
|
|
656
773
|
|
|
657
|
-
upnp_class =
|
|
658
|
-
didl_properties_defs = Container.didl_properties_defs + [
|
|
659
|
-
]
|
|
774
|
+
upnp_class = "object.container.genre.musicGenre"
|
|
775
|
+
didl_properties_defs = Container.didl_properties_defs + []
|
|
660
776
|
|
|
661
777
|
|
|
662
778
|
class MovieGenre(Genre):
|
|
@@ -664,9 +780,8 @@ class MovieGenre(Genre):
|
|
|
664
780
|
|
|
665
781
|
# pylint: disable=too-few-public-methods
|
|
666
782
|
|
|
667
|
-
upnp_class =
|
|
668
|
-
didl_properties_defs = Container.didl_properties_defs + [
|
|
669
|
-
]
|
|
783
|
+
upnp_class = "object.container.genre.movieGenre"
|
|
784
|
+
didl_properties_defs = Container.didl_properties_defs + []
|
|
670
785
|
|
|
671
786
|
|
|
672
787
|
class ChannelGroup(Container):
|
|
@@ -674,14 +789,14 @@ class ChannelGroup(Container):
|
|
|
674
789
|
|
|
675
790
|
# pylint: disable=too-few-public-methods
|
|
676
791
|
|
|
677
|
-
upnp_class =
|
|
792
|
+
upnp_class = "object.container.channelGroup"
|
|
678
793
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
679
|
-
(
|
|
680
|
-
(
|
|
681
|
-
(
|
|
682
|
-
(
|
|
683
|
-
(
|
|
684
|
-
(
|
|
794
|
+
("upnp", "channelGroupName", "O"),
|
|
795
|
+
("upnp", "channelGroupName@id", "O"),
|
|
796
|
+
("upnp", "epgProviderName", "O"),
|
|
797
|
+
("upnp", "serviceProvider", "O"),
|
|
798
|
+
("upnp", "icon", "O"),
|
|
799
|
+
("upnp", "region", "O"),
|
|
685
800
|
]
|
|
686
801
|
|
|
687
802
|
|
|
@@ -690,9 +805,8 @@ class AudioChannelGroup(ChannelGroup):
|
|
|
690
805
|
|
|
691
806
|
# pylint: disable=too-few-public-methods
|
|
692
807
|
|
|
693
|
-
upnp_class =
|
|
694
|
-
didl_properties_defs = Container.didl_properties_defs + [
|
|
695
|
-
]
|
|
808
|
+
upnp_class = "object.container.channelGroup.audioChannelGroup"
|
|
809
|
+
didl_properties_defs = Container.didl_properties_defs + []
|
|
696
810
|
|
|
697
811
|
|
|
698
812
|
class VideoChannelGroup(ChannelGroup):
|
|
@@ -700,9 +814,8 @@ class VideoChannelGroup(ChannelGroup):
|
|
|
700
814
|
|
|
701
815
|
# pylint: disable=too-few-public-methods
|
|
702
816
|
|
|
703
|
-
upnp_class =
|
|
704
|
-
didl_properties_defs = Container.didl_properties_defs + [
|
|
705
|
-
]
|
|
817
|
+
upnp_class = "object.container.channelGroup.videoChannelGroup"
|
|
818
|
+
didl_properties_defs = Container.didl_properties_defs + []
|
|
706
819
|
|
|
707
820
|
|
|
708
821
|
class EpgContainer(Container):
|
|
@@ -710,31 +823,31 @@ class EpgContainer(Container):
|
|
|
710
823
|
|
|
711
824
|
# pylint: disable=too-few-public-methods
|
|
712
825
|
|
|
713
|
-
upnp_class =
|
|
826
|
+
upnp_class = "object.container.epgContainer"
|
|
714
827
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
715
|
-
(
|
|
716
|
-
(
|
|
717
|
-
(
|
|
718
|
-
(
|
|
719
|
-
(
|
|
720
|
-
(
|
|
721
|
-
(
|
|
722
|
-
(
|
|
723
|
-
(
|
|
724
|
-
(
|
|
725
|
-
(
|
|
726
|
-
(
|
|
727
|
-
(
|
|
828
|
+
("upnp", "channelGroupName", "O"),
|
|
829
|
+
("upnp", "channelGroupName@id", "O"),
|
|
830
|
+
("upnp", "epgProviderName", "O"),
|
|
831
|
+
("upnp", "serviceProvider", "O"),
|
|
832
|
+
("upnp", "channelName", "O"),
|
|
833
|
+
("upnp", "channelNr", "O"),
|
|
834
|
+
("upnp", "channelID", "O"),
|
|
835
|
+
("upnp", "channelID@type", "O"),
|
|
836
|
+
("upnp", "radioCallSign", "O"),
|
|
837
|
+
("upnp", "radioStationID", "O"),
|
|
838
|
+
("upnp", "radioBand", "O"),
|
|
839
|
+
("upnp", "callSign", "O"),
|
|
840
|
+
("upnp", "networkAffiliation", "O"),
|
|
728
841
|
# ('upnp', 'serviceProvider', 'O'), # duplicate in standard
|
|
729
|
-
(
|
|
730
|
-
(
|
|
731
|
-
(
|
|
842
|
+
("upnp", "price", "O"),
|
|
843
|
+
("upnp", "price@currency", "O"),
|
|
844
|
+
("upnp", "payPerView", "O"),
|
|
732
845
|
# ('upnp', 'epgProviderName', 'O'), # duplicate in standard
|
|
733
|
-
(
|
|
734
|
-
(
|
|
735
|
-
(
|
|
736
|
-
(
|
|
737
|
-
(
|
|
846
|
+
("upnp", "icon", "O"),
|
|
847
|
+
("upnp", "region", "O"),
|
|
848
|
+
("dc", "language", "O"),
|
|
849
|
+
("dc", "relation", "O"),
|
|
850
|
+
("upnp", "dateTimeRange", "O"),
|
|
738
851
|
]
|
|
739
852
|
|
|
740
853
|
|
|
@@ -743,13 +856,13 @@ class StorageSystem(Container):
|
|
|
743
856
|
|
|
744
857
|
# pylint: disable=too-few-public-methods
|
|
745
858
|
|
|
746
|
-
upnp_class =
|
|
859
|
+
upnp_class = "object.container.storageSystem"
|
|
747
860
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
748
|
-
(
|
|
749
|
-
(
|
|
750
|
-
(
|
|
751
|
-
(
|
|
752
|
-
(
|
|
861
|
+
("upnp", "storageTotal", "R"),
|
|
862
|
+
("upnp", "storageUsed", "R"),
|
|
863
|
+
("upnp", "storageFree", "R"),
|
|
864
|
+
("upnp", "storageMaxPartition", "R"),
|
|
865
|
+
("upnp", "storageMedium", "R"),
|
|
753
866
|
]
|
|
754
867
|
|
|
755
868
|
|
|
@@ -758,12 +871,12 @@ class StorageVolume(Container):
|
|
|
758
871
|
|
|
759
872
|
# pylint: disable=too-few-public-methods
|
|
760
873
|
|
|
761
|
-
upnp_class =
|
|
874
|
+
upnp_class = "object.container.storageVolume"
|
|
762
875
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
763
|
-
(
|
|
764
|
-
(
|
|
765
|
-
(
|
|
766
|
-
(
|
|
876
|
+
("upnp", "storageTotal", "R"),
|
|
877
|
+
("upnp", "storageUsed", "R"),
|
|
878
|
+
("upnp", "storageFree", "R"),
|
|
879
|
+
("upnp", "storageMedium", "R"),
|
|
767
880
|
]
|
|
768
881
|
|
|
769
882
|
|
|
@@ -772,9 +885,9 @@ class StorageFolder(Container):
|
|
|
772
885
|
|
|
773
886
|
# pylint: disable=too-few-public-methods
|
|
774
887
|
|
|
775
|
-
upnp_class =
|
|
888
|
+
upnp_class = "object.container.storageFolder"
|
|
776
889
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
777
|
-
(
|
|
890
|
+
("upnp", "storageUsed", "R"),
|
|
778
891
|
]
|
|
779
892
|
|
|
780
893
|
|
|
@@ -783,12 +896,14 @@ class BookmarkFolder(Container):
|
|
|
783
896
|
|
|
784
897
|
# pylint: disable=too-few-public-methods
|
|
785
898
|
|
|
786
|
-
upnp_class =
|
|
899
|
+
upnp_class = "object.container.bookmarkFolder"
|
|
787
900
|
didl_properties_defs = Container.didl_properties_defs + [
|
|
788
|
-
(
|
|
789
|
-
(
|
|
790
|
-
(
|
|
901
|
+
("upnp", "genre", "O"),
|
|
902
|
+
("upnp", "longDescription", "O"),
|
|
903
|
+
("dc", "description", "O"),
|
|
791
904
|
]
|
|
905
|
+
|
|
906
|
+
|
|
792
907
|
# endregion
|
|
793
908
|
|
|
794
909
|
|
|
@@ -797,13 +912,23 @@ class Resource:
|
|
|
797
912
|
|
|
798
913
|
# pylint: disable=too-few-public-methods,too-many-instance-attributes
|
|
799
914
|
|
|
800
|
-
def __init__(
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
915
|
+
def __init__(
|
|
916
|
+
self,
|
|
917
|
+
uri: Optional[str],
|
|
918
|
+
protocol_info: Optional[str],
|
|
919
|
+
import_uri: Optional[str] = None,
|
|
920
|
+
size: Optional[str] = None,
|
|
921
|
+
duration: Optional[str] = None,
|
|
922
|
+
bitrate: Optional[str] = None,
|
|
923
|
+
sample_frequency: Optional[str] = None,
|
|
924
|
+
bits_per_sample: Optional[str] = None,
|
|
925
|
+
nr_audio_channels: Optional[str] = None,
|
|
926
|
+
resolution: Optional[str] = None,
|
|
927
|
+
color_depth: Optional[str] = None,
|
|
928
|
+
protection: Optional[str] = None,
|
|
929
|
+
xml_el: Optional[ET.Element] = None,
|
|
930
|
+
) -> None:
|
|
931
|
+
"""Initialize."""
|
|
807
932
|
# pylint: disable=too-many-arguments
|
|
808
933
|
self.uri = uri
|
|
809
934
|
self.protocol_info = protocol_info
|
|
@@ -817,69 +942,96 @@ class Resource:
|
|
|
817
942
|
self.resolution = resolution
|
|
818
943
|
self.color_depth = color_depth
|
|
819
944
|
self.protection = protection
|
|
945
|
+
self.xml_el = xml_el
|
|
820
946
|
|
|
821
947
|
@classmethod
|
|
822
|
-
def from_xml(cls: Type[TR],
|
|
948
|
+
def from_xml(cls: Type[TR], xml_el: ET.Element) -> TR:
|
|
823
949
|
"""Initialize from an XML node."""
|
|
824
|
-
uri =
|
|
825
|
-
protocol_info =
|
|
826
|
-
import_uri =
|
|
827
|
-
size =
|
|
828
|
-
duration =
|
|
829
|
-
bitrate =
|
|
830
|
-
sample_frequency =
|
|
831
|
-
bits_per_sample =
|
|
832
|
-
nr_audio_channels =
|
|
833
|
-
resolution =
|
|
834
|
-
color_depth =
|
|
835
|
-
protection =
|
|
836
|
-
return cls(
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
950
|
+
uri = xml_el.text
|
|
951
|
+
protocol_info = xml_el.attrib.get("protocolInfo")
|
|
952
|
+
import_uri = xml_el.attrib.get("importUri")
|
|
953
|
+
size = xml_el.attrib.get("size")
|
|
954
|
+
duration = xml_el.attrib.get("duration")
|
|
955
|
+
bitrate = xml_el.attrib.get("bitrate")
|
|
956
|
+
sample_frequency = xml_el.attrib.get("sampleFrequency")
|
|
957
|
+
bits_per_sample = xml_el.attrib.get("bitsPerSample")
|
|
958
|
+
nr_audio_channels = xml_el.attrib.get("nrAudioChannels")
|
|
959
|
+
resolution = xml_el.attrib.get("resolution")
|
|
960
|
+
color_depth = xml_el.attrib.get("colorDepth")
|
|
961
|
+
protection = xml_el.attrib.get("protection")
|
|
962
|
+
return cls(
|
|
963
|
+
uri,
|
|
964
|
+
protocol_info=protocol_info,
|
|
965
|
+
import_uri=import_uri,
|
|
966
|
+
size=size,
|
|
967
|
+
duration=duration,
|
|
968
|
+
bitrate=bitrate,
|
|
969
|
+
sample_frequency=sample_frequency,
|
|
970
|
+
bits_per_sample=bits_per_sample,
|
|
971
|
+
nr_audio_channels=nr_audio_channels,
|
|
972
|
+
resolution=resolution,
|
|
973
|
+
color_depth=color_depth,
|
|
974
|
+
protection=protection,
|
|
975
|
+
xml_el=xml_el,
|
|
976
|
+
)
|
|
843
977
|
|
|
844
978
|
def to_xml(self) -> ET.Element:
|
|
845
979
|
"""Convert self to XML."""
|
|
846
980
|
attribs = {
|
|
847
|
-
|
|
981
|
+
"protocolInfo": self.protocol_info or "",
|
|
848
982
|
}
|
|
849
|
-
res_el = ET.Element(
|
|
983
|
+
res_el = ET.Element("res", attribs)
|
|
850
984
|
res_el.text = self.uri
|
|
851
985
|
return res_el
|
|
852
986
|
|
|
987
|
+
def __repr__(self) -> str:
|
|
988
|
+
"""Evaluatable string representation of this object."""
|
|
989
|
+
class_name = type(self).__name__
|
|
990
|
+
attr = ", ".join(
|
|
991
|
+
f"{key}={val!r}"
|
|
992
|
+
for key, val in self.__dict__.items()
|
|
993
|
+
if val is not None and key != "xml_el"
|
|
994
|
+
)
|
|
995
|
+
return f"{class_name}({attr})"
|
|
996
|
+
|
|
853
997
|
|
|
854
998
|
class Descriptor:
|
|
855
999
|
"""DIDL Descriptor."""
|
|
856
1000
|
|
|
857
|
-
def __init__(
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1001
|
+
def __init__(
|
|
1002
|
+
self,
|
|
1003
|
+
id: str,
|
|
1004
|
+
name_space: str,
|
|
1005
|
+
type: Optional[str] = None,
|
|
1006
|
+
text: Optional[str] = None,
|
|
1007
|
+
xml_el: Optional[ET.Element] = None,
|
|
1008
|
+
) -> None:
|
|
1009
|
+
"""Initialize."""
|
|
1010
|
+
# pylint: disable=invalid-name,redefined-builtin,too-many-arguments
|
|
861
1011
|
self.id = id
|
|
862
1012
|
self.name_space = name_space
|
|
863
1013
|
self.type = type
|
|
864
1014
|
self.text = text
|
|
1015
|
+
self.xml_el = xml_el
|
|
865
1016
|
|
|
866
1017
|
@classmethod
|
|
867
|
-
def from_xml(cls: Type[TD],
|
|
1018
|
+
def from_xml(cls: Type[TD], xml_el: ET.Element) -> TD:
|
|
868
1019
|
"""Initialize from an XML node."""
|
|
869
|
-
id_ =
|
|
870
|
-
name_space =
|
|
871
|
-
type_ =
|
|
872
|
-
|
|
1020
|
+
id_ = xml_el.attrib["id"]
|
|
1021
|
+
name_space = xml_el.attrib["nameSpace"]
|
|
1022
|
+
type_ = xml_el.attrib.get("type")
|
|
1023
|
+
text = xml_el.text
|
|
1024
|
+
return cls(id_, name_space, type=type_, text=text, xml_el=xml_el)
|
|
873
1025
|
|
|
874
1026
|
def to_xml(self) -> ET.Element:
|
|
875
1027
|
"""Convert self to XML."""
|
|
876
1028
|
attribs = {
|
|
877
|
-
|
|
878
|
-
|
|
1029
|
+
"id": self.id,
|
|
1030
|
+
"nameSpace": self.name_space,
|
|
879
1031
|
}
|
|
880
1032
|
if self.type is not None:
|
|
881
|
-
attribs[
|
|
882
|
-
desc_el = ET.Element(
|
|
1033
|
+
attribs["type"] = self.type
|
|
1034
|
+
desc_el = ET.Element("desc", attribs)
|
|
883
1035
|
desc_el.text = self.text
|
|
884
1036
|
return desc_el
|
|
885
1037
|
|
|
@@ -888,52 +1040,77 @@ class Descriptor:
|
|
|
888
1040
|
if name not in self.__dict__:
|
|
889
1041
|
raise AttributeError(name)
|
|
890
1042
|
return self.__dict__[name]
|
|
1043
|
+
|
|
1044
|
+
def __repr__(self) -> str:
|
|
1045
|
+
"""Evaluatable string representation of this object."""
|
|
1046
|
+
class_name = type(self).__name__
|
|
1047
|
+
attr = ", ".join(
|
|
1048
|
+
f"{key}={val!r}"
|
|
1049
|
+
for key, val in self.__dict__.items()
|
|
1050
|
+
if val is not None and key != "xml_el"
|
|
1051
|
+
)
|
|
1052
|
+
return f"{class_name}({attr})"
|
|
1053
|
+
|
|
1054
|
+
|
|
891
1055
|
# endregion
|
|
892
1056
|
|
|
893
1057
|
|
|
894
1058
|
def to_xml_string(*objects: DidlObject) -> bytes:
|
|
895
1059
|
"""Convert items to DIDL-Lite XML string."""
|
|
896
|
-
root_el = ET.Element(
|
|
897
|
-
root_el.attrib[
|
|
898
|
-
root_el.attrib[
|
|
899
|
-
root_el.attrib[
|
|
900
|
-
root_el.attrib[
|
|
1060
|
+
root_el = ET.Element("DIDL-Lite", {})
|
|
1061
|
+
root_el.attrib["xmlns"] = NAMESPACES["didl_lite"]
|
|
1062
|
+
root_el.attrib["xmlns:dc"] = NAMESPACES["dc"]
|
|
1063
|
+
root_el.attrib["xmlns:upnp"] = NAMESPACES["upnp"]
|
|
1064
|
+
root_el.attrib["xmlns:sec"] = NAMESPACES["sec"]
|
|
901
1065
|
|
|
902
1066
|
for didl_object in objects:
|
|
903
1067
|
didl_object_el = didl_object.to_xml()
|
|
904
1068
|
root_el.append(didl_object_el)
|
|
905
1069
|
|
|
906
|
-
return
|
|
1070
|
+
return ET.tostring(root_el)
|
|
907
1071
|
|
|
908
1072
|
|
|
909
|
-
def from_xml_string(
|
|
1073
|
+
def from_xml_string(
|
|
1074
|
+
xml_string: str, strict: bool = True
|
|
1075
|
+
) -> List[Union[DidlObject, Descriptor]]:
|
|
910
1076
|
"""Convert XML string to DIDL Objects."""
|
|
911
1077
|
xml_el = defusedxml.ElementTree.fromstring(xml_string)
|
|
912
|
-
return from_xml_el(xml_el)
|
|
1078
|
+
return from_xml_el(xml_el, strict)
|
|
913
1079
|
|
|
914
1080
|
|
|
915
|
-
def from_xml_el(
|
|
1081
|
+
def from_xml_el(
|
|
1082
|
+
xml_el: ET.Element, strict: bool = True
|
|
1083
|
+
) -> List[Union[DidlObject, Descriptor]]:
|
|
916
1084
|
"""Convert XML Element to DIDL Objects."""
|
|
917
1085
|
didl_objects = [] # type: List[Union[DidlObject, Descriptor]]
|
|
918
1086
|
|
|
919
1087
|
# items and containers, in order
|
|
920
1088
|
for child_el in xml_el:
|
|
921
|
-
if child_el.tag !=
|
|
922
|
-
|
|
1089
|
+
if child_el.tag != expand_namespace_tag(
|
|
1090
|
+
"didl_lite:item"
|
|
1091
|
+
) and child_el.tag != expand_namespace_tag("didl_lite:container"):
|
|
923
1092
|
continue
|
|
924
1093
|
|
|
925
1094
|
# construct item
|
|
926
|
-
upnp_class = child_el.find(
|
|
1095
|
+
upnp_class = child_el.find("./upnp:class", NAMESPACES)
|
|
927
1096
|
if upnp_class is None or not upnp_class.text:
|
|
928
|
-
|
|
929
|
-
|
|
1097
|
+
if strict:
|
|
1098
|
+
continue
|
|
1099
|
+
# WiiM Pro and possibly other Linkplay devices emit
|
|
1100
|
+
# upnp_class above the item element instead of inside it
|
|
1101
|
+
upnp_class = xml_el.find("./upnp:class", NAMESPACES)
|
|
1102
|
+
if upnp_class is None or not upnp_class.text:
|
|
1103
|
+
continue
|
|
1104
|
+
didl_object_type = type_by_upnp_class(upnp_class.text, strict)
|
|
930
1105
|
if didl_object_type is None:
|
|
1106
|
+
if strict:
|
|
1107
|
+
raise DidlLiteException(f"upnp:class {upnp_class.text} is unknown")
|
|
931
1108
|
continue
|
|
932
|
-
didl_object = didl_object_type.from_xml(child_el)
|
|
1109
|
+
didl_object = didl_object_type.from_xml(child_el, strict)
|
|
933
1110
|
didl_objects.append(didl_object)
|
|
934
1111
|
|
|
935
1112
|
# descriptors
|
|
936
|
-
for desc_el in xml_el.findall(
|
|
1113
|
+
for desc_el in xml_el.findall("./didl_lite:desc", NAMESPACES):
|
|
937
1114
|
desc = Descriptor.from_xml(desc_el)
|
|
938
1115
|
didl_objects.append(desc)
|
|
939
1116
|
|
|
@@ -941,13 +1118,14 @@ def from_xml_el(xml_el: ET.Element) -> List[Union[DidlObject, Descriptor]]:
|
|
|
941
1118
|
|
|
942
1119
|
|
|
943
1120
|
# upnp_class to python type mapping
|
|
944
|
-
def type_by_upnp_class(
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1121
|
+
def type_by_upnp_class(
|
|
1122
|
+
upnp_class: str, strict: bool = True
|
|
1123
|
+
) -> Optional[Type[DidlObject]]:
|
|
1124
|
+
"""Get DidlObject-type by upnp_class.
|
|
1125
|
+
|
|
1126
|
+
When strict is False, the upnp_class lookup will be done ignoring string
|
|
1127
|
+
case.
|
|
1128
|
+
"""
|
|
1129
|
+
if strict:
|
|
1130
|
+
return _upnp_class_map.get(upnp_class)
|
|
1131
|
+
return _upnp_class_map_lowercase.get(upnp_class.lower())
|