Initial commit. Implemented reading of variable size integers, element sizes, and...
[~jspiros/python-ebml.git] / ebml / core.py
1 import warnings
2 from .exceptions import *
3
4
5 EBMLMaxSizeLength = 8
6 EBMLMaxIDLength = 4
7
8
9 def _read_vint_to_bytearray(stream, max_width=EBMLMaxSizeLength):
10         """
11         
12         Reads a vint from stream and returns a bytearray containing all of the bytes without doing any decoding.
13         
14         :arg stream: the source of the bytes
15         :type stream: a file-like object
16         :arg max_width: the maximum length, in bytes, of the vint (defaults to :data:`EBMLMaxSizeLength`)
17         :type max_width: int
18         :returns: bytearray
19         
20         """
21         
22         marker_found = False
23         vint_bytes = bytearray()
24         vint_len = -7
25         while not marker_found:
26                 vint_len += 8
27                 if vint_len > max_width:
28                         raise ParseError('vint exceeds max_width (%(max_width)i)' % {
29                                 'max_width': max_width
30                         })
31                 byte = ord(stream.read(1))
32                 vint_bytes.append(byte)
33                 for pos in range(0, 8):
34                         mask = 0b10000000 >> pos
35                         if byte & mask:
36                                 vint_len += pos
37                                 marker_found = True
38                                 break
39         
40         remaining_bytes_len = vint_len - len(vint_bytes)
41         if remaining_bytes_len > 0:
42                 vint_bytes.extend(ord(remaining_byte) for remaining_byte in stream.read(remaining_bytes_len))
43         
44         if len(vint_bytes) != vint_len:
45                 raise ParseError('Unable to read truncated vint of width %(vint_len)s from stream (%(vint_bytes)s bytes available)' % {
46                         'vint_len': vint_len,
47                         'vint_bytes': len(vint_bytes)
48                 })
49         
50         return vint_bytes
51
52
53 def read_element_size(stream, max_width=EBMLMaxSizeLength):
54         """
55         
56         Reads an EBML element size vint from stream and returns a tuple containing:
57         
58                 * the size as an integer, or None if the size is undefined
59                 * the length in bytes of the size descriptor (the vint) itself
60         
61         :arg stream: the source of the bytes
62         :type stream: a file-like object
63         :arg max_width: the maximum length, in bytes, of the vint storing the element size (defaults to :data:`EBMLMaxSizeLength`)
64         :type max_width: int
65         :returns: tuple
66         
67         """
68         
69         vint_bytes = _read_vint_to_bytearray(stream, max_width)
70         vint_len = len(vint_bytes)
71         
72         int_bytes = vint_bytes[((vint_len - 1) // 8):]
73         first_byte_mask = 0b10000000 >> ((vint_len - 1) % 8)
74         max_bytes = 0
75         
76         value = int_bytes[0] & (first_byte_mask - 1)
77         
78         if value == (first_byte_mask - 1):
79                 max_bytes += 1
80         
81         for int_byte in int_bytes[1:]:
82                 if int_byte == 0b11111111:
83                         max_bytes += 1
84                 value = (value << 8) | int_byte
85         
86         if max_bytes == len(int_bytes):
87                 value = None
88         
89         return value, vint_len
90
91
92 def read_element_id(stream, max_width=EBMLMaxIDLength):
93         """
94         
95         Reads an EBML element ID vint from stream and returns a tuple containing:
96         
97                 * the ID as an integer
98                 * the length in bytes of the ID descriptor (the vint) itself
99         
100         :arg stream: the source of the bytes
101         :type stream: a file-like object
102         :arg max_width: the maximum length, in bytes, of the vint storing the element ID (defaults to :data:`EBMLMaxIDLength`)
103         :type max_width: int
104         :returns: tuple
105         
106         """
107         
108         vint_bytes = _read_vint_to_bytearray(stream, max_width)
109         vint_len = len(vint_bytes)
110         
111         value = 0
112         max_bytes = 0
113         min_bytes = 0
114         
115         for vint_byte in vint_bytes:
116                 if vint_byte == 0b11111111:
117                         max_bytes += 1
118                 elif vint_byte == 0:
119                         min_bytes += 1
120                 value = (value << 8) | vint_byte
121         
122         if max_bytes == vint_len:
123                 raise ReservedElementIDError('All value bits set to 1')
124         elif min_bytes == vint_len:
125                 raise ReservedElementIDError('All value bits set to 0')
126         
127         return value, vint_len