File: helpers.py

package info (click to toggle)
python-yaswfp 0.9.3-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 164 kB
  • sloc: python: 1,415; makefile: 3
file content (157 lines) | stat: -rw-r--r-- 4,686 bytes parent folder | download | duplicates (3)
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
# Copyright 2013-2014 Facundo Batista
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# For further info, check  http://github.com/facundobatista/yaswfp

"""Some helpers for the SWF parser."""

import itertools
import struct


def grouper(n, iterable, fillvalue=None):
    """Collect data into fixed-length chunks or blocks.

    This is taken from the itertools docs.
    """
    # grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.zip_longest(*args, fillvalue=fillvalue)


def unpack_si16(src):
    """Read and unpack signed integer 16b."""
    return struct.unpack("<h", src.read(2))[0]


def unpack_ui8(src):
    """Read and unpack unsigned integer 8b."""
    return struct.unpack("<B", src.read(1))[0]


def unpack_ui16(src):
    """Read and unpack unsigned integer 16b."""
    return struct.unpack("<H", src.read(2))[0]


def unpack_ui32(src):
    """Read and unpack unsigned integer 32b."""
    return struct.unpack("<I", src.read(4))[0]


def unpack_fixed8(src):
    """Get a FIXED8 value."""
    dec_part = unpack_ui8(src)
    int_part = unpack_ui8(src)
    return int_part + dec_part / 256


def unpack_fixed16(src):
    """Get a FIXED16 value (called plainly FIXED in the spec)."""
    dec_part = unpack_ui16(src)
    int_part = unpack_ui16(src)
    return int_part + dec_part / 65536


def unpack_float16(src):
    """Read and unpack a 16b float.

    The structure is:
    - 1 bit for the sign
    . 5 bits for the exponent, with an exponent bias of 16
    - 10 bits for the mantissa
    """
    bc = BitConsumer(src)
    sign = bc.u_get(1)
    exponent = bc.u_get(5)
    mantissa = bc.u_get(10)
    exponent -= 16
    mantissa /= 2 ** 10
    num = (-1 ** sign) * mantissa * (10 ** exponent)
    return num


def unpack_float(src):
    """Read and unpack a 32b float."""
    return struct.unpack("<f", src.read(4))[0]


def unpack_double(src):
    """Read and unpack a 64b float."""
    return struct.unpack("<d", src.read(8))[0]


class BitConsumer:
    """Get a byte source, yield bunch of bits."""
    def __init__(self, src):
        self.src = src
        self._bits = None
        self._count = 0

    def u_get(self, quant):
        """Return a number using the given quantity of unsigned bits."""
        if not quant:
            return
        bits = []
        while quant:
            if self._count == 0:
                byte = self.src.read(1)
                number = struct.unpack("<B", byte)[0]
                self._bits = bin(number)[2:].zfill(8)
                self._count = 8
            if quant > self._count:
                self._count, quant, toget = 0, quant - self._count, self._count
            else:
                self._count, quant, toget = self._count - quant, 0, quant
            read, self._bits = self._bits[:toget], self._bits[toget:]
            bits.append(read)
        data = int("".join(bits), 2)
        return data

    def s_get(self, quant):
        """Return a number using the given quantity of signed bits."""
        if quant == 1:
            # special case, just return that unsigned value
            return self.u_get(1)

        sign = self.u_get(1)
        raw_number = self.u_get(quant - 1)
        if sign == 0:
            # positive, simplest case
            number = raw_number
        else:
            # negative, complemento a 2
            complement = 2 ** (quant - 1) - 1
            number = -1 * ((raw_number ^ complement) + 1)
        return number


class ReadQuantityController:
    """A context manager that will complain if bad quantity is read."""
    def __init__(self, src, should):
        self._src = src
        self._should = should
        self._started = None

    def __enter__(self):
        """Enter the guarded block."""
        self._started = self._src.tell()

    def __exit__(self, *exc):
        """Exit the guarded block."""
        cur_pos = self._src.tell()
        if cur_pos != self._started + self._should:
            t = "Bad reading quantity: started={} should={} ended={}".format(
                self._started, self._should, cur_pos)
            raise ValueError(t)