""" 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