[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/python-ffmpegio/python-ebml/master/tags.py [Back]  [Original]

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

Web Proxy Viewer  |  New URL  |  Original Page