3f64c20e103f82343ff10146bb409b28e4239c2c
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

1) #! /usr/bin/env python3
2) 
3) import argparse
4) import struct
5) import sys
6) 
7) 
8) class Movie(object):
9)     class Frame(object):
10) 
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

11)         # offsets (y * width(66) + x) of the pixels whose green value
12)         # is taken from the *.bbm frames for the LEDs
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

13)         LED_COORDS = [
14)             408, 414, 420, 468, 492, 677, 684, 863, 945, 951, 957, 963, 971,
15)             975, 1034, 1209, 1215, 1221, 1227, 1259, 1299, 1304, 1373, 1469,
16)             1476, 1497, 1572, 1633, 1656, 1680, 1728, 1734, 1740, 1832, 1968,
17)             2106, 2163, 2225, 2365, 2559, 2620, 2956
18)         ]
19) 
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

20)         # offsets (y * width(66) + x) of the pixels whose color is set to
21)         # the LED brightness in the *.bbm frames on output
22)         # (multiple (-> list) per LED)
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

23)         LED_COORDS_OUT = [
24)             [408, 407, 409], [414, 413, 415], [420, 419, 421], [468, 533, 403],
25)             [492, 425, 559], [677, 610, 744], [684, 617, 751], [863, 797, 929],
26)             [945, 944, 946], [951, 950, 952], [957, 956, 958], [963, 962, 964],
27)             [971, 904, 1038], [975, 908, 1042], [1034, 967, 1101],
28)             [1209, 1208, 1210], [1215, 1214, 1216], [1221, 1220, 1222],
29)             [1227, 1226, 1228], [1259, 1193, 1325], [1299, 1298, 1300],
30)             [1304, 1369, 1239], [1373, 1438, 1308], [1469, 1534, 1404],
31)             [1476, 1541, 1411], [1497, 1496, 1498], [1572, 1637, 1507],
32)             [1633, 1698, 1568], [1656, 1589, 1723], [1680, 1745, 1615],
33)             [1728, 1727, 1729], [1734, 1733, 1735], [1740, 1739, 1741],
34)             [1832, 1897, 1767], [1968, 1901, 2035], [2106, 2105, 2107],
35)             [2163, 2097, 2229], [2225, 2290, 2160], [2365, 2299, 2431],
36)             [2559, 2493, 2625], [2620, 2619, 2621], [2956, 2889, 3023]
37)         ]
38) 
39)         def __init__(self):
40)             self.duration = 100
41)             self.leds = len(self.LED_COORDS) * [0]
42) 
43)         def from_frame_data(self, duration, data):
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

44)             """parse frame from duration and *.bbm pixel data"""
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

45)             self.duration = duration
46)             for i in range(len(self.LED_COORDS)):
47)                 ledno = self.LED_COORDS[i]
48)                 self.leds[i] = data[ledno * 3 + 1]
49) 
50)         def to_frame_data(self):
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

51)             """convert frame to *.bbm frame data,
52)                return duation and pixel data"""
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

53)             pixels = 51 * 66 * [0, 0, 255]
54)             for i in range(len(self.LED_COORDS_OUT)):
55)                 for ledno in self.LED_COORDS_OUT[i]:
56)                     pixels[ledno * 3 + 0] = self.leds[i]
57)                     pixels[ledno * 3 + 1] = self.leds[i]
58)                     pixels[ledno * 3 + 2] = 0
59)             data = bytes(pixels)
60)             return self.duration, data
61) 
62)         def to_firmware_data(self):
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

63)             """convert a frame to firmware data"""
64)             # duration: in 6ms steps, 12 bits
Stefan Schuermans frame time unit is 6 ms

Stefan Schuermans authored 5 years ago

65)             duration = (self.duration + 3) // 6
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

66)             if duration < 1:
67)                 duration = 1
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

68)             if duration > 0x3FF:
69)                 duration = 0x3FF
70)             # use shorter encoding
71)             plain = self._fw_pix_data_plain()
72)             rle = self._fw_pix_data_rle()
73)             if len(rle) < len(plain):
74)                 code = 0x10 # rle compressed
75)                 data = rle
76)             else:
77)                 code = 0x00 # plain
78)                 data = plain
79)             # encode code and duration at begin of data
80)             dur_h = (duration >> 8) & 0x0F
81)             dur_l = duration & 0xFF
82)             return [code | dur_h, dur_l] + data
83) 
84)         def _fw_pix_data_plain(self):
85)             """return pixel data, plain, no compression"""
86)             data = []
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

87)             half = None
88)             for led in self.leds:
89)                 val = (led >> 4) & 0x0F
90)                 if half is None:
91)                     half = val
92)                 else:
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

93)                     data.append(half << 4 | val)
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

94)                     half = None
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

95)             return data
96) 
97)         def _fw_pix_data_rle(self):
98)             """return pixel data, compressed using run length encoding"""
99)             data = []
100)             val = (self.leds[0] >> 4) & 0x0F
101)             cnt = 0
102)             for led in self.leds:
103)                 ledval = (led >> 4) & 0x0F
104)                 if val == ledval and cnt < 0x10:  # same value -> count
105)                     cnt += 1
106)                 else:
107)                     data.append((cnt - 1) << 4 | val)  # append RLE item
108)                     val = ledval
109)                     cnt = 1
110)             data.append((cnt - 1) << 4 | val)  # last RLE item
111)             return data
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

112) 
113)     def __init__(self):
114)         self.frames = []
115)         self.main_hdr = struct.Struct("!LHHHH")
116)         self.main_info = struct.Struct("!LLL")
117)         self.subhdr_magic = struct.Struct("!L")
118)         self.subhdr_size = struct.Struct("!H")
119)         self.frame_hdr = struct.Struct("!H")
120) 
121)     def read_bbm(self, filename):
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

122)         """read movie from *.bbm file"""
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

123)         try:
124)             with open(filename, "rb") as f:
125)                 # main header
126)                 magic, height, width, channels, maxval = self.main_hdr.unpack(
127)                     f.read(12))
128)                 if magic != 0x23542666:
129)                     raise ValueError(
130)                         "invalid magic 0x%X != 0x23542666".format(magic))
131)                 if height != 51 or width != 66 or channels != 3 or maxval != 255:
132)                     raise ValueError(
133)                         "invalid format {:d}x{:d}-{:d}/{:d} != 66x51-3/256".
134)                         format(width, height, channels, maxval - 1))
135)                 # main information
136)                 framecnt, duration, frameptr = self.main_info.unpack(
137)                     f.read(12))
138)                 # skip additional headers until frame start marker
139)                 while True:
140)                     subhdr_magic, = self.subhdr_magic.unpack(f.read(4))
141)                     if subhdr_magic == 0x66726d73:
142)                         break
143)                     subhdr_size, = self.subhdr_size.unpack(f.read(2))
144)                     if subhdr_size < 6:
145)                         raise ValueError("truncated sub-header")
146)                     f.read(subhdr_size - 6)
147)                 # read frames
148)                 frames = []
149)                 for frameno in range(framecnt):
150)                     duration, = self.frame_hdr.unpack(f.read(2))
151)                     n = height * width * channels
152)                     framedata = f.read(n)
153)                     if len(framedata) != n:
154)                         raise ValueError("truncated frame")
155)                     frame = self.Frame()
156)                     frame.from_frame_data(duration, framedata)
157)                     frames.append(frame)
158)             self.frames = frames
159)             return True
160)         except Exception as e:
161)             print(str(e), file=sys.stderr)
162)             return False
163) 
164)     def write_bbm(self, filename):
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

165)         """write movie as *.bbm file"""
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

166)         with open(filename, "wb") as f:
167)             # main header
168)             f.write(self.main_hdr.pack(0x23542666, 51, 66, 3, 255))
169)             # main information
170)             duration = 0
171)             for frame in self.frames:
172)                 duration += frame.duration
173)             f.write(self.main_info.pack(len(self.frames), duration, 24))
174)             # frame start marker
175)             f.write(self.subhdr_magic.pack(0x66726d73))
176)             # write frames
177)             for frame in self.frames:
178)                 duration, data = frame.to_frame_data()
179)                 f.write(self.frame_hdr.pack(duration))
180)                 f.write(data)
181) 
182)     def write_firmware(self, filename):
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

183)         """write movie as firmware (assembly include file)"""
184)         # convert all frames to firware data
185)         fw_frames = []
186)         fw_len = 0
187)         for frame in self.frames:
188)             fw_data = frame.to_firmware_data()
189)             # search for identical frame before
190)             id_len = 0
191)             for id_frame in fw_frames:
192)                 if id_frame == fw_data and id_frame[0] & 0xE0 == 0x00:
193)                     # identical frame found (and code is 0x00 or 0x10)
194)                     # -> replace data with back reference
195)                     back = fw_len - id_len + 2
196)                     if back <= 0x3FF:
197)                         back_h = (back >> 8) & 0x0F
198)                         back_l = back & 0xFF
199)                         fw_data = [0x20 | back_h, back_l]
200)                         print("DEBUG back", fw_data)
201)                         break
202)                 id_len += len(id_frame)
203)             # append frame to list
204)             fw_frames.append(fw_data)
205)             fw_len += len(fw_data)
206)         # build firmware data
207)         fw_data = []
208)         for fw_frame in fw_frames:
209)             fw_data += fw_frame
210)         fw_data.append(0xF0) # end marker
211)         if len(fw_data) & 1 != 0:
212)             fw_data.append(0) # ensure even length
213)         # write firmware data as assembly
Stefan Schuermans bbm2Chaosknoten

Stefan Schuermans authored 5 years ago

214)         with open(filename, "w") as f:
Stefan Schuermans movie compression, add anot...

Stefan Schuermans authored 5 years ago

215)             for i in range(0, len(fw_data), 8):
216)                 vals = ["0x{:02X}".format(v) for v in fw_data[i:i + 8]]
217)                 print("        .db     {:s}".format(",".join(vals)), file=f)