forked from QBobWatson/python-ebml
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtags.py
More file actions
322 lines (286 loc) · 12.6 KB
/
tags.py
File metadata and controls
322 lines (286 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
"""
Matroska tag processing.
"""
from collections.abc import Mapping
__all__ = ['Tag', 'TagDict', 'MATROSKA_TAGS', 'INTERNAL_ID']
class Tag:
"""Class representing a specific EBML tag.
This class encodes all of the defining data of an EBML tag, originating from
Matroska's specdata.xml file. It serves mainly as an attribute dictionary
for the tag properties, but it also keeps track of parent-child
relationships.
Note that many different tags will correspond to the same Element class.
Attributes that are always defined:
+ ebml_id: The EBML ID of this tag, an integer.
+ name: The name of this tag, a string.
+ cls: The Element subclass to instantiate in self.__call__().
+ parent: The parent Tag instance. For level-zero tags this is None, and
for global tags it is "*".
+ mandatory: Whether this Tag must appear as a child of its parent. Used
in consistency checking. This is always False if the child has a default
value.
+ multiple: Whether this Tag may appear multiple times. Used in
consistency checking.
+ webm: Whether this Tag is part of webm. Unused by this module.
+ minver, maxver: The range of versions of the Matroska EBML specification
in which this tag appears. Unused by this module.
Attributes with default values:
+ header_size_min: Becomes the Element attribute of the same name.
+ data_size_min: Becomes the Element attribute of the same name.
Attributes that are sometimes defined:
+ min_val: For numeric types, valid values are greater than this value.
The inequality is strict for floats.
+ max_val: For numeric types, valid values are smaller than this value.
The inequality is strict for floats.
+ default: Default value. This may be a Tag instance, in which case it is
a sibling tag from which to inherit the default value.
+ recursive: Whether the tag can be a child of itself.
+ values: For Enum types, a dict whose keys are integers and whose values
are string representations of that value. For BitField types, a list of
strings whose ith element is the string representation of the ith bit.
Properties:
+ required_children: Iterator for required children.
+ unique_children: Iterator for unique children.
"""
#pylint: disable=too-many-instance-attributes
def __init__(self, ebml_id, name, cls, parent, mandatory, multiple,
webm, minver, maxver, **kwargs):
#pylint: disable=too-many-arguments
self.ebml_id = ebml_id
self.name = name
self.cls = cls
if parent is None or parent == "*":
self.parent = parent
else:
self.parent = MATROSKA_TAGS[parent]
self.parent.children.append(self)
self._mandatory = mandatory
self.multiple = multiple
# Defaults
self.header_size_min = 0
self.data_size_min = 0
# Unused
self.webm = webm
self.minver = minver
self.maxver = maxver
# The rest
for name, val in kwargs.items():
setattr(self, name, val)
self.children = []
@property
def required_children(self):
"Iterate over required children."
return (child for child in self.children if child.mandatory)
@property
def unique_children(self):
"Iterate over unique children."
return (child for child in self.children if not child.multiple)
@property
def mandatory(self):
"Override mandatory property for tags with default values."
return False if hasattr(self, 'default') else self._mandatory
def __eq__(self, other):
return isinstance(other, Tag) and self.ebml_id == other.ebml_id
def __ne__(self, other):
return not self == other
def __hash__(self):
return self.ebml_id
def __call__(self, header):
"""Create a new Element instance for this tag.
Args:
+ header: as in Element.__init__().
"""
return self.cls(header, name=self.name)
def is_child(self, tag):
"Decide if this tag is allowed to be contained in tag."
if tag is None:
# Level zero
return self.parent is None or self.parent == "*"
else:
if self.parent == tag or self.parent == "*":
return True
return hasattr(self, 'recursive') and tag == self
class TagDict(Mapping):
"""Dictionary for storing Tag instances.
This dictionary does not support __setitem__(); instead Tag instances must
be added using insert(). They are stored under both their name and ID.
The tags are loaded on demand to prevent circular import dependencies.
"""
def __init__(self):
self._dict = {}
self._initialized = False
def __getitem__(self, key):
self.delayed_init()
try:
return self._dict[key]
except KeyError:
if isinstance(key, int):
return Tag(key, 'Unknown', 'ElementUnsupported', "*",
False, True, True, 1, 4)
raise
def __len__(self):
self.delayed_init()
return len(self._dict)
def __iter__(self):
self.delayed_init()
return iter(self._dict)
def __contains__(self, item):
self.delayed_init()
return self._dict.__contains__(item)
def keys(self):
self.delayed_init()
return self._dict.keys()
def items(self):
self.delayed_init()
return self._dict.items()
def values(self):
self.delayed_init()
return self._dict.values()
def get(self, key, default=None):
self.delayed_init()
return self._dict.get(key, default)
def insert(self, tag):
"""Store tag in self under its name and ebml_id.
This is roughly equivalent to:
self[tag.ebml_id] = tag
self[tag.name] = tag
Args:
+ tag: A Tag instance.
Returns:
tag
Raises:
+ EbmlException, if tag is not an instance of Tag.
"""
if isinstance(tag, Tag):
self._dict[tag.ebml_id] = tag
self._dict[tag.name] = tag
else:
raise ValueError("Tried to insert() a non-Tag instance {!r}"
.format(tag))
def remove(self, ebml_id):
"Remove a tag. For testing purposes."
tag = self._dict[ebml_id]
del self._dict[ebml_id]
del self._dict[tag.name]
def level0s(self):
"Iterate over Tag instances with parent equal to None."
for ebml_id, val in self.items():
if isinstance(ebml_id, int) and val.parent is None:
yield val
def delayed_init(self):
"Initialize the Tags."
#pylint: disable=too-many-statements,too-many-locals,unused-variable
if self._initialized:
return
self._initialized = True
from .tagdata import MATROSKA_TAG_DATA
from .element import ElementUnsupported, ElementPlaceholder, \
ElementVoid, ElementMaster, ElementMasterDefer
from .atomic import ElementAtomic, ElementRaw, ElementUnsigned, \
ElementSigned, ElementBoolean, ElementEnum, ElementBitField, \
ElementFloat, ElementString, ElementUnicode, ElementDate, ElementID
from .data_elements import ElementEBML, ElementSegment, ElementSeek, \
ElementInfo, ElementTrackEntry, ElementVideo, ElementAudio, \
ElementAttachedFile, ElementTag, ElementTargets, ElementSimpleTag, \
ElementEditionEntry, ElementChapterAtom
# For internal use
MATROSKA_TAG_DATA.append(
dict(ebml_id=INTERNAL_ID, name='LibInternal',
cls_name='ElementPlaceholder', parent='*', mandatory=False,
multiple=True, webm=True, minver=1, maxver=4))
MATROSKA_TAG_DATA.append(
dict(ebml_id=INTERNAL_ID, name='LibInternal2',
cls_name='ElementPlaceholder', parent='*', mandatory=False,
multiple=True, webm=True, minver=1, maxver=4))
for tag_data in MATROSKA_TAG_DATA:
cls_name = tag_data['cls_name']
del tag_data['cls_name']
tag_data['cls'] = locals()[cls_name]
self.insert(Tag(**tag_data))
# Override some automatically generated values
self['EBML'].cls = ElementEBML
self['Void'].cls = ElementVoid
self['SignedElement'].cls = ElementID
self['Segment'].cls = ElementSegment
self['Segment'].header_size_min = 8
self['Seek'].cls = ElementSeek
self['SeekID'].cls = ElementID
self['SeekPosition'].data_size_min = 8
self['Info'].cls = ElementInfo
self['Title'].data_size_min = 100
self['EditionEntry'].cls = ElementEditionEntry
self['ChapterAtom'].cls = ElementChapterAtom
self['ChapterTranslateCodec'].cls = ElementEnum
self['ChapterTranslateCodec'].values \
= {0 : 'Matroska Script', 1 : 'DVD-menu'}
self['SimpleBlock'].cls = ElementUnsupported
self['Block'].cls = ElementUnsupported
self['BlockVirtual'].cls = ElementUnsupported
self['BlockAdditional'].cls = ElementUnsupported
self['CodecState'].cls = ElementUnsupported
self['EncryptedBlock'].cls = ElementUnsupported
self['TrackEntry'].cls = ElementTrackEntry
self['TrackType'].cls = ElementEnum
self['TrackType'].values \
= {0x1 : 'video', 0x2 : 'audio', 0x3 : 'complex', 0x10 : 'logo',
0x11 : 'subtitle', 0x12 : 'buttons', 0x20 : 'control'}
self['TrackTranslateCodec'].cls = ElementEnum
self['TrackTranslateCodec'].values \
= {0 : 'Matroska Script', 1 : 'DVD-menu'}
self['Video'].cls = ElementVideo
self['StereoMode'].cls = ElementEnum
self['StereoMode'].values \
= {0 : 'mono', 1 : 'side-by-side (left)', 2 : 'top-bottom (right)',
3 : 'top-bottom (left)', 4 : 'checkerboard (right)',
5 : 'checkerboard (left)', 6 : 'row interleaved (right)',
7 : 'row interleaved (left)', 8 : 'col interleaved (right)',
9 : 'col interleaved (left)', 10 : 'anaglyph (cyan/red)',
11 : 'side-by-side (right)', 12 : 'anaglyph (green/magenta)',
13 : 'both (left)', 14 : 'both (right)'}
self['DisplayWidth'].default = self['PixelWidth']
self['DisplayHeight'].default = self['PixelHeight']
self['DisplayUnit'].cls = ElementEnum
self['DisplayUnit'].values \
= {0 : 'pixels', 1 : 'centimeters',
2 : 'inches', 3 : 'Display Aspect Ratio'}
self['AspectRatioType'].cls = ElementEnum
self['AspectRatioType'].values \
= {0 : 'free resizing', 1 : 'keep aspect ratio', 2 : 'fixed'}
self['Audio'].cls = ElementAudio
self['OutputSamplingFrequency'].default \
= self['SamplingFrequency']
self['TrackPlaneType'].cls = ElementEnum
self['TrackPlaneType'].values \
= {0 : 'left eye', 1 : 'right eye', 2 : 'background'}
self['ContentEncodingScope'].cls = ElementBitField
self['ContentEncodingScope'].values \
= ['all-frame-contents', 'track-private-data',
'the-next-ContentEncoding']
self['ContentEncodingType'].cls = ElementEnum
self['ContentEncodingType'].values \
= {0 : 'compression', 1 : 'encryption'}
self['ContentCompAlgo'].cls = ElementEnum
self['ContentCompAlgo'].values \
= {0 : 'zlib', 1 : 'bzlib', 2 : 'lzo1x', 3 : 'Header Stripping'}
self['ContentEncAlgo'].cls = ElementEnum
self['ContentEncAlgo'].values \
= {0 : 'signed only', 1 : 'DES', 2 : '3DES', 3 : 'Twofish',
4 : 'Blowfish', 5 : 'AES'}
self['ContentSigAlgo'].cls = ElementEnum
self['ContentSigAlgo'].values \
= {0 : 'signed only', 1 : 'RSA'}
self['ContentSigHashAlgo'].cls = ElementEnum
self['ContentSigHashAlgo'].values \
= {0 : 'signed only', 1 : 'SHA1-160', 2 : 'MD5'}
self['Cues'].cls = ElementMasterDefer
self['Attachments'].header_size_min = 4
self['AttachedFile'].cls = ElementAttachedFile
self['AttachedFile'].header_size_min = 4
self['FileUID'].cls = ElementRaw
self['Tag'].cls = ElementTag
self['Targets'].cls = ElementTargets
self['SimpleTag'].cls = ElementSimpleTag
# This dictionary contains the tags that the parser recognizes. The tags are
# Tag instances and the keys are the tag IDs and names.
MATROSKA_TAGS = TagDict()
INTERNAL_ID = 66