SHOW:
|
|
- or go back to the newest paste.
1 | local inflate do | |
2 | ||
3 | local floor = math.floor | |
4 | local ceil = math.ceil | |
5 | local min = math.min | |
6 | local max = math.max | |
7 | ||
8 | -- utility functions | |
9 | ||
10 | local function memoize(f) | |
11 | local cache = {} | |
12 | return function(...) | |
13 | local key = table.concat({...}, "-") | |
14 | if not cache[key] then | |
15 | cache[key] = f(...) | |
16 | end | |
17 | return cache[key] | |
18 | end | |
19 | end | |
20 | ||
21 | local function int(bytes) | |
22 | local n = 0 | |
23 | for i = 1, #bytes do | |
24 | n = 256*n + bytes:sub(i, i):byte() | |
25 | end | |
26 | return n | |
27 | end | |
28 | int = memoize(int) | |
29 | ||
30 | local function bint(bits) | |
31 | return tonumber(bits, 2) or 0 | |
32 | end | |
33 | bint = memoize(bint) | |
34 | ||
35 | local function bits(b, width) | |
36 | local s = "" | |
37 | if type(b) == "number" then | |
38 | for i = 1, width do | |
39 | s = b%2 .. s | |
40 | b = floor(b/2) | |
41 | end | |
42 | else | |
43 | for i = 1, #b do | |
44 | s = s .. bits(b:byte(i), 8):reverse() | |
45 | assert(#s == i * 8, s) | |
46 | end | |
47 | end | |
48 | return s | |
49 | end | |
50 | bits = memoize(bits) | |
51 | ||
52 | local function fill(bytes, len) | |
53 | return bytes:rep(floor(len / #bytes)) .. bytes:sub(1, len % #bytes) | |
54 | end | |
55 | ||
56 | local function zip(t1, t2) | |
57 | local zipped = {} | |
58 | for i = 1, max(#t1, #t2) do | |
59 | zipped[#zipped + 1] = {t1[i], t2[i]} | |
60 | end | |
61 | return zipped | |
62 | end | |
63 | ||
64 | local function unzip(zipped) | |
65 | local t1, t2 = {}, {} | |
66 | for i = 1, #zipped do | |
67 | t1[#t1 + 1] = zipped[i][1] | |
68 | t2[#t2 + 1] = zipped[i][2] | |
69 | end | |
70 | return t1, t2 | |
71 | end | |
72 | ||
73 | local function map(f, t) | |
74 | local mapped = {} | |
75 | for i = 1, #t do | |
76 | mapped[#mapped + 1] = f(t[i], i) | |
77 | end | |
78 | return mapped | |
79 | end | |
80 | ||
81 | local function filter(pred, t) | |
82 | local filtered = {} | |
83 | for i = 1, #t do | |
84 | if pred(t[i], i) then | |
85 | filtered[#filtered + 1] = t[i] | |
86 | end | |
87 | end | |
88 | return filtered | |
89 | end | |
90 | ||
91 | local function find(key, t) | |
92 | if type(key) == "function" then | |
93 | for i = 1, #t do | |
94 | if key(t[i]) then | |
95 | return i | |
96 | end | |
97 | end | |
98 | return nil | |
99 | else | |
100 | return find(function(x) return x == key end, t) | |
101 | end | |
102 | end | |
103 | ||
104 | local function slice(t, i, j, step) | |
105 | local sliced = {} | |
106 | for k = i < 1 and 1 or i, i < 1 and #t + i or j or #t, step or 1 do | |
107 | sliced[#sliced + 1] = t[k] | |
108 | end | |
109 | return sliced | |
110 | end | |
111 | ||
112 | local function range(i, j) | |
113 | local r = {} | |
114 | for k = j and i or 0, j or i - 1 do | |
115 | r[#r + 1] = k | |
116 | end | |
117 | return r | |
118 | end | |
119 | ||
120 | -- streams | |
121 | ||
122 | local function output_stream() | |
123 | local stream, buffer = {}, {} | |
124 | local curr = 0 | |
125 | ||
126 | function stream:write(bytes) | |
127 | for i = 1, #bytes do | |
128 | buffer[#buffer + 1] = bytes:sub(i, i) | |
129 | end | |
130 | curr = curr + #bytes | |
131 | end | |
132 | ||
133 | function stream:back_read(offset, n) | |
134 | local read = {} | |
135 | for i = curr - offset + 1, curr - offset + n do | |
136 | read[#read + 1] = buffer[i] | |
137 | end | |
138 | return table.concat(read) | |
139 | end | |
140 | ||
141 | function stream:back_copy(dist, len) | |
142 | local start, copied = curr - dist + 1, {} | |
143 | for i = start, min(start + len, curr) do | |
144 | copied[#copied + 1] = buffer[i] | |
145 | end | |
146 | self:write(fill(table.concat(copied), len)) | |
147 | end | |
148 | ||
149 | function stream:pos() | |
150 | return curr | |
151 | end | |
152 | ||
153 | function stream:raw() | |
154 | return table.concat(buffer) | |
155 | end | |
156 | ||
157 | return stream | |
158 | end | |
159 | ||
160 | local function bit_stream(raw, offset) | |
161 | local stream = {} | |
162 | local curr = 0 | |
163 | offset = offset or 0 | |
164 | ||
165 | function stream:read(n, reverse) | |
166 | local start = floor(curr/8) + offset + 1 | |
167 | local bb = "" | |
168 | for i = start, start + ceil(n/8) do | |
169 | local b = raw:byte(i) | |
170 | for j = 0, 7 do bb = bb .. bit32.extract(b, j, 1) end | |
171 | end | |
172 | local b = bb:sub(curr%8 + 1, curr%8 + n) | |
173 | curr = curr + n | |
174 | return reverse and b or b:reverse() | |
175 | end | |
176 | ||
177 | function stream:seek(n) | |
178 | if n == "beg" then | |
179 | curr = 0 | |
180 | elseif n == "end" then | |
181 | curr = #raw | |
182 | else | |
183 | curr = curr + n | |
184 | end | |
185 | return self | |
186 | end | |
187 | ||
188 | function stream:is_empty() | |
189 | return curr >= 8*#raw | |
190 | end | |
191 | ||
192 | function stream:pos() | |
193 | return curr | |
194 | end | |
195 | ||
196 | return stream | |
197 | end | |
198 | ||
199 | -- inflate | |
200 | ||
201 | local CL_LENS_ORDER = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15} | |
202 | local MAX_BITS = 15 | |
203 | local PT_WIDTH = 8 | |
204 | ||
205 | local function cl_code_lens(stream, hclen) | |
206 | local code_lens = {} | |
207 | for i = 1, hclen do | |
208 | code_lens[#code_lens + 1] = bint(stream:read(3)) | |
209 | end | |
210 | return code_lens | |
211 | end | |
212 | ||
213 | local function code_tree(lens, alphabet) | |
214 | alphabet = alphabet or range(#lens) | |
215 | local using = filter(function(x, i) return lens[i] and lens[i] > 0 end, alphabet) | |
216 | lens = filter(function(x) return x > 0 end, lens) | |
217 | local tree = zip(lens, using) | |
218 | table.sort(tree, function(a, b) | |
219 | if a[1] == b[1] then | |
220 | return a[2] < b[2] | |
221 | else | |
222 | return a[1] < b[1] | |
223 | end | |
224 | end) | |
225 | return unzip(tree) | |
226 | end | |
227 | ||
228 | local function codes(lens) | |
229 | local codes = {} | |
230 | local code = 0 | |
231 | for i = 1, #lens do | |
232 | codes[#codes + 1] = bits(code, lens[i]) | |
233 | if i < #lens then | |
234 | code = (code + 1)*2^(lens[i + 1] - lens[i]) | |
235 | end | |
236 | end | |
237 | return codes | |
238 | end | |
239 | ||
240 | local prefix_table | |
241 | local function handle_long_codes(codes, alphabet, pt) | |
242 | local i = find(function(x) return #x > PT_WIDTH end, codes) | |
243 | local long = slice(zip(codes, alphabet), i) | |
244 | i = 0 | |
245 | repeat | |
246 | local prefix = long[i + 1][1]:sub(1, PT_WIDTH) | |
247 | local same = filter(function(x) return x[1]:sub(1, PT_WIDTH) == prefix end, long) | |
248 | same = map(function(x) return {x[1]:sub(PT_WIDTH + 1), x[2]} end, same) | |
249 | pt[prefix] = {rest = prefix_table(unzip(same)), unused = 0} | |
250 | i = i + #same | |
251 | until i == #long | |
252 | end | |
253 | ||
254 | function prefix_table(codes, alphabet) | |
255 | local pt = {} | |
256 | if #codes[#codes] > PT_WIDTH then | |
257 | handle_long_codes(codes, alphabet, pt) | |
258 | end | |
259 | for i = 1, #codes do | |
260 | local code = codes[i] | |
261 | if #code > PT_WIDTH then | |
262 | break | |
263 | end | |
264 | local entry = {value = alphabet[i], unused = PT_WIDTH - #code} | |
265 | if entry.unused == 0 then | |
266 | pt[code] = entry | |
267 | else | |
268 | for i = 0, 2^entry.unused - 1 do | |
269 | pt[code .. bits(i, entry.unused)] = entry | |
270 | end | |
271 | end | |
272 | end | |
273 | return pt | |
274 | end | |
275 | ||
276 | local function huffman_decoder(lens, alphabet) | |
277 | local base_codes = prefix_table(codes(lens), alphabet) | |
278 | return function(stream) | |
279 | local codes = base_codes | |
280 | local entry | |
281 | repeat | |
282 | entry = codes[stream:read(PT_WIDTH, true)] | |
283 | stream:seek(-entry.unused) | |
284 | codes = entry.rest | |
285 | until not codes | |
286 | return entry.value | |
287 | end | |
288 | end | |
289 | ||
290 | local function code_lens(stream, decode, n) | |
291 | local lens = {} | |
292 | repeat | |
293 | local value = decode(stream) | |
294 | if value < 16 then | |
295 | lens[#lens + 1] = value | |
296 | elseif value == 16 then | |
297 | for i = 1, bint(stream:read(2)) + 3 do | |
298 | lens[#lens + 1] = lens[#lens] | |
299 | end | |
300 | elseif value == 17 then | |
301 | for i = 1, bint(stream:read(3)) + 3 do | |
302 | lens[#lens + 1] = 0 | |
303 | end | |
304 | elseif value == 18 then | |
305 | for i = 1, bint(stream:read(7)) + 11 do | |
306 | lens[#lens + 1] = 0 | |
307 | end | |
308 | end | |
309 | until #lens == n | |
310 | return lens | |
311 | end | |
312 | ||
313 | local function code_trees(stream) | |
314 | local hlit = bint(stream:read(5)) + 257 | |
315 | local hdist = bint(stream:read(5)) + 1 | |
316 | local hclen = bint(stream:read(4)) + 4 | |
317 | local cl_decode = huffman_decoder(code_tree(cl_code_lens(stream, hclen), CL_LENS_ORDER)) | |
318 | local ll_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hlit))) | |
319 | local d_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hdist))) | |
320 | return ll_decode, d_decode | |
321 | end | |
322 | ||
323 | local function extra_bits(value) | |
324 | if value >= 4 and value <= 29 then | |
325 | return floor(value/2) - 1 | |
326 | elseif value >= 265 and value <= 284 then | |
327 | return ceil(value/4) - 66 | |
328 | else | |
329 | return 0 | |
330 | end | |
331 | end | |
332 | extra_bits = memoize(extra_bits) | |
333 | ||
334 | local function decode_len(value, bits) | |
335 | assert(value >= 257 and value <= 285, "value out of range") | |
336 | assert(#bits == extra_bits(value), "wrong number of extra bits") | |
337 | if value <= 264 then | |
338 | return value - 254 | |
339 | elseif value == 285 then | |
340 | return 258 | |
341 | end | |
342 | local len = 11 | |
343 | for i = 1, #bits - 1 do | |
344 | len = len + 2^(i+2) | |
345 | end | |
346 | return floor(bint(bits) + len + ((value - 1) % 4)*2^#bits) | |
347 | end | |
348 | decode_len = memoize(decode_len) | |
349 | ||
350 | local function a(n) | |
351 | if n <= 3 then | |
352 | return n + 2 | |
353 | else | |
354 | return a(n-1) + 2*a(n-2) - 2*a(n-3) | |
355 | end | |
356 | end | |
357 | a = memoize(a) | |
358 | ||
359 | local function decode_dist(value, bits) | |
360 | assert(value >= 0 and value <= 29, "value out of range") | |
361 | assert(#bits == extra_bits(value), "wrong number of extra bits)") | |
362 | return bint(bits) + a(value - 1) | |
363 | end | |
364 | decode_dist = memoize(decode_dist) | |
365 | ||
366 | function inflate(data) | |
367 | local stream = bit_stream(data) | |
368 | local ostream = output_stream() | |
369 | repeat | |
370 | local bfinal, btype = bint(stream:read(1)), bint(stream:read(2)) | |
371 | assert(btype == 2, "compression method not supported") | |
372 | local ll_decode, d_decode = code_trees(stream) | |
373 | while true do | |
374 | local value = ll_decode(stream) | |
375 | if value < 256 then | |
376 | ostream:write(string.char(value)) | |
377 | elseif value == 256 then | |
378 | break | |
379 | else | |
380 | local len = decode_len(value, stream:read(extra_bits(value))) | |
381 | value = d_decode(stream) | |
382 | local dist = decode_dist(value, stream:read(extra_bits(value))) | |
383 | ostream:back_copy(dist, len) | |
384 | end | |
385 | end | |
386 | until bfinal == 1 | |
387 | return ostream:raw() | |
388 | end | |
389 | ||
390 | end | |
391 | local dfpwm = require "cc.audio.dfpwm" | |
392 | ||
393 | local hexstr = "0123456789abcdef" | |
394 | local file, err = http.get { url = "http://timuzkas.mywire.org:8000/video.32v", binary = true } | |
395 | if not file then error(err) end | |
396 | if file.read(4) ~= "32VD" then file.close() error("Not a 32vid file") end | |
397 | local width, height, fps, nStreams, flags = ("<HHBBH"):unpack(file.read(8)) | |
398 | local video, audio, subtitles | |
399 | for _ = 1, nStreams do | |
400 | local size, nFrames, frameType = ("<IIB"):unpack(file.read(9)) | |
401 | print(("%X"):format(file.seek()), size, nFrames, frameType) | |
402 | if frameType == 0 and not video then | |
403 | local data = file.read(size) | |
404 | if bit32.band(flags, 3) == 2 then data = inflate(data) end | |
405 | video = {} | |
406 | local pos = 1 | |
407 | local start = os.epoch "utc" | |
408 | for i = 1, nFrames do | |
409 | if i % 100 == 0 or i >= nFrames - 10 then print(i, os.epoch "utc" - start) start = os.epoch "utc" sleep(0) end | |
410 | local frame = {palette = {}} | |
411 | local tmp, tmppos = 0, 0 | |
412 | local use5bit, customcompress = bit32.btest(flags, 16), bit32.band(flags, 3) == 3 | |
413 | local codetree = {} | |
414 | local solidchar, runlen | |
415 | local function readField(isColor) | |
416 | if customcompress then | |
417 | if runlen then | |
418 | local c = solidchar | |
419 | runlen = runlen - 1 | |
420 | if runlen == 0 then runlen = nil end | |
421 | return c | |
422 | end | |
423 | if not isColor and solidchar then return solidchar end | |
424 | -- MARK: Huffman decoding | |
425 | local node = codetree | |
426 | while true do | |
427 | local n = bit32.extract(tmp, tmppos, 1) | |
428 | tmppos = tmppos - 1 | |
429 | if tmppos < 0 then tmp, pos, tmppos = data:byte(pos), pos + 1, 7 end | |
430 | if type(node) ~= "table" then error(("Invalid tree state: position %X, frame %d"):format(pos+file.seek()-size-1, i)) end | |
431 | if type(node[n]) == "number" then | |
432 | local c = node[n] | |
433 | if isColor then | |
434 | if c > 15 then runlen = 2^(c-15)-1 return assert(solidchar) | |
435 | else solidchar = c end | |
436 | end | |
437 | return c | |
438 | else node = node[n] end | |
439 | end | |
440 | else | |
441 | local n | |
442 | if tmppos * 5 + 5 > 32 then n = bit32.extract(math.floor(tmp / 0x1000000), tmppos * 5 - 24, 5) | |
443 | else n = bit32.extract(tmp, tmppos * 5, 5) end | |
444 | tmppos = tmppos - 1 | |
445 | if tmppos < 0 then tmp, pos = (">I5"):unpack(data, pos) tmppos = 7 end | |
446 | return n | |
447 | end | |
448 | end | |
449 | if customcompress then | |
450 | -- MARK: Huffman tree reconstruction | |
451 | -- read bit lengths | |
452 | local bitlen = {} | |
453 | if use5bit then | |
454 | for j = 0, 15 do | |
455 | bitlen[j*2+1], bitlen[j*2+2] = {s = j*2, l = bit32.rshift(data:byte(pos+j), 4)}, {s = j*2+1, l = bit32.band(data:byte(pos+j), 0x0F)} | |
456 | end | |
457 | pos = pos + 16 | |
458 | else | |
459 | for j = 0, 7 do | |
460 | tmp, pos = (">I5"):unpack(data, pos) | |
461 | bitlen[j*8+1] = {s = j*8+1, l = math.floor(tmp / 0x800000000)} | |
462 | bitlen[j*8+2] = {s = j*8+2, l = math.floor(tmp / 0x40000000) % 32} | |
463 | for k = 3, 8 do bitlen[j*8+k] = {s = j*8+k, l = bit32.extract(tmp, (8-k)*5, 5)} end | |
464 | end | |
465 | end | |
466 | do | |
467 | local j = 1 | |
468 | while j <= #bitlen do | |
469 | if bitlen[j].l == 0 then table.remove(bitlen, j) | |
470 | else j = j + 1 end | |
471 | end | |
472 | end | |
473 | if #bitlen == 0 then | |
474 | -- screen is solid character | |
475 | solidchar = data:byte(pos) | |
476 | pos = pos + 1 | |
477 | else | |
478 | -- reconstruct codes from bit lengths | |
479 | table.sort(bitlen, function(a, b) if a.l == b.l then return a.s < b.s else return a.l < b.l end end) | |
480 | bitlen[1].c = 0 | |
481 | for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end | |
482 | -- create tree from codes | |
483 | for j = 1, #bitlen do | |
484 | local c = bitlen[j].c | |
485 | local node = codetree | |
486 | for k = bitlen[j].l - 1, 1, -1 do | |
487 | local n = bit32.extract(c, k, 1) | |
488 | if not node[n] then node[n] = {} end | |
489 | node = node[n] | |
490 | if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end | |
491 | end | |
492 | local n = bit32.extract(c, 0, 1) | |
493 | node[n] = bitlen[j].s | |
494 | end | |
495 | -- read first byte | |
496 | tmp, tmppos, pos = data:byte(pos), 7, pos + 1 | |
497 | end | |
498 | else readField() end | |
499 | for y = 1, height do | |
500 | local line = {"", "", "", {}} | |
501 | for x = 1, width do | |
502 | if pos + 5 + 1 >= #data then print(i, pos, x, y) error() end | |
503 | local n = readField() | |
504 | line[1] = line[1] .. string.char(128 + (n % 0x20)) | |
505 | line[4][x] = bit32.btest(n, 0x20) | |
506 | end | |
507 | frame[y] = line | |
508 | end | |
509 | if customcompress then | |
510 | if tmppos == 7 then pos = pos - 1 end | |
511 | codetree = {} | |
512 | -- MARK: Huffman tree reconstruction | |
513 | -- read bit lengths | |
514 | local bitlen = {} | |
515 | for j = 0, 11 do | |
516 | bitlen[j*2+1], bitlen[j*2+2] = {s = j*2, l = bit32.rshift(data:byte(pos+j), 4)}, {s = j*2+1, l = bit32.band(data:byte(pos+j), 0x0F)} | |
517 | end | |
518 | pos = pos + 12 | |
519 | do | |
520 | local j = 1 | |
521 | while j <= #bitlen do | |
522 | if bitlen[j].l == 0 then table.remove(bitlen, j) | |
523 | else j = j + 1 end | |
524 | end | |
525 | end | |
526 | if #bitlen == 0 then | |
527 | -- screen is solid color | |
528 | solidchar = data:byte(pos) | |
529 | pos = pos + 1 | |
530 | runlen = math.huge | |
531 | else | |
532 | -- reconstruct codes from bit lengths | |
533 | table.sort(bitlen, function(a, b) if a.l == b.l then return a.s < b.s else return a.l < b.l end end) | |
534 | bitlen[1].c = 0 | |
535 | for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end | |
536 | -- create tree from codes | |
537 | for j = 1, #bitlen do | |
538 | local c = bitlen[j].c | |
539 | local node = codetree | |
540 | for k = bitlen[j].l - 1, 1, -1 do | |
541 | local n = bit32.extract(c, k, 1) | |
542 | if not node[n] then node[n] = {} end | |
543 | node = node[n] | |
544 | if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end | |
545 | end | |
546 | local n = bit32.extract(c, 0, 1) | |
547 | node[n] = bitlen[j].s | |
548 | end | |
549 | -- read first byte | |
550 | tmp, tmppos, pos = data:byte(pos), 7, pos + 1 | |
551 | end | |
552 | for y = 1, height do | |
553 | local line = frame[y] | |
554 | for x = 1, width do | |
555 | local c = readField(true) | |
556 | line[2] = line[2] .. hexstr:sub(c+1, c+1) | |
557 | end | |
558 | end | |
559 | runlen = nil | |
560 | for y = 1, height do | |
561 | local line = frame[y] | |
562 | for x = 1, width do | |
563 | local c = readField(true) | |
564 | line[3] = line[3] .. hexstr:sub(c+1, c+1) | |
565 | end | |
566 | end | |
567 | if tmppos == 7 then pos = pos - 1 end | |
568 | else | |
569 | for y = 1, height do | |
570 | local line = frame[y] | |
571 | for x = 1, width do | |
572 | local c = data:byte(pos) | |
573 | pos = pos + 1 | |
574 | if line[4][x] then c = bit32.bor(bit32.band(bit32.lshift(c, 4), 0xF0), bit32.rshift(c, 4)) end | |
575 | line[2] = line[2] .. hexstr:sub(bit32.band(c, 15)+1, bit32.band(c, 15)+1) | |
576 | line[3] = line[3] .. hexstr:sub(bit32.rshift(c, 4)+1, bit32.rshift(c, 4)+1) | |
577 | end | |
578 | line[4] = nil | |
579 | end | |
580 | end | |
581 | for n = 1, 16 do frame.palette[n], pos = {data:byte(pos) / 255, data:byte(pos+1) / 255, data:byte(pos+2) / 255}, pos + 3 end | |
582 | video[i] = frame | |
583 | end | |
584 | elseif frameType == 1 and not audio then | |
585 | audio = {} | |
586 | if bit32.band(flags, 12) == 0 then | |
587 | for i = 1, math.ceil(size / 48000) do | |
588 | local data | |
589 | if jit then | |
590 | data = {} | |
591 | for j = 0, 5 do | |
592 | local t = {file.read(math.min(size - (i-1) * 48000 - j * 8000, 8000)):byte(1, -1)} | |
593 | if #t > 0 then for k = 1, #t do data[j*8000+k] = t[k] end end | |
594 | end | |
595 | else data = {file.read(math.min(size - (i-1) * 48000, 48000)):byte(1, -1)} end | |
596 | for j = 1, #data do data[j] = data[j] - 128 end | |
597 | audio[i] = data | |
598 | end | |
599 | elseif bit32.band(flags, 12) == 4 then | |
600 | local decode = dfpwm.make_decoder() | |
601 | for i = 1, math.ceil(size / 6000) do | |
602 | local data = file.read(math.min(size - (i-1)*6000, 6000)) | |
603 | if not data then break end | |
604 | audio[i] = decode(data) | |
605 | end | |
606 | end | |
607 | elseif frameType == 8 and not subtitles then | |
608 | subtitles = {} | |
609 | for _ = 1, nFrames do | |
610 | local start, length, x, y, color, sz = ("<IIHHBxH"):unpack(file.read(16)) | |
611 | local text = file.read(sz) | |
612 | local sub = {text, hexstr:sub(bit32.band(color, 15)+1, bit32.band(color, 15)+1):rep(#text), | |
613 | hexstr:sub(bit32.rshift(color, 4)+1, bit32.rshift(color, 4)+1):rep(#text), x = x, y = y} | |
614 | for n = start, start + length - 1 do | |
615 | subtitles[n] = subtitles[n] or {} | |
616 | subtitles[n][#subtitles[n]+1] = sub | |
617 | end | |
618 | end | |
619 | end | |
620 | end | |
621 | file.close() | |
622 | if not video then error("No video stream found") end | |
623 | sleep(0) | |
624 | ||
625 | local speaker = peripheral.find "speaker" | |
626 | term.clear() | |
627 | local ok, err = pcall(parallel.waitForAll, function() | |
628 | local start = os.epoch "utc" | |
629 | for f, image in ipairs(video) do | |
630 | for i, v in ipairs(image.palette) do term.setPaletteColor(2^(i-1), table.unpack(v)) end | |
631 | for y, r in ipairs(image) do | |
632 | term.setCursorPos(1, y) | |
633 | term.blit(table.unpack(r)) | |
634 | end | |
635 | if subtitles and subtitles[f-1] then | |
636 | for _, v in ipairs(subtitles[f-1]) do | |
637 | term.setCursorPos(v.x, v.y) | |
638 | term.blit(table.unpack(v)) | |
639 | end | |
640 | end | |
641 | while os.epoch "utc" < start + (f + 1) / fps * 1000 do sleep(1 / fps) end | |
642 | end | |
643 | end, function() | |
644 | if not speaker or not speaker.playAudio or not audio then return end | |
645 | for _, chunk in ipairs(audio) do | |
646 | while not speaker.playAudio(chunk) do repeat local ev, sp = os.pullEvent("speaker_audio_empty") until sp == peripheral.getName(speaker) end | |
647 | end | |
648 | end) | |
649 | for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end | |
650 | term.setBackgroundColor(colors.black) | |
651 | term.setTextColor(colors.white) | |
652 | term.setCursorPos(1, 1) | |
653 | term.clear() | |
654 | if not ok then printError(err) end |