Compare commits
No commits in common. "391acc479c449f7b71eea170655fc282f63159d0" and "967a3ad705decf87567545c7c16aa1d0fb471cd3" have entirely different histories.
391acc479c
...
967a3ad705
@ -1,258 +0,0 @@
|
||||
local bit32_band, bit32_lshift, bit32_rshift, math_frexp = bit32.band, bit32.lshift, bit32.rshift, math.frexp
|
||||
local function log2(n) local _, r = math_frexp(n) return r-1 end
|
||||
local dfpwm = require "cc.audio.dfpwm"
|
||||
|
||||
local speaker = peripheral.find "speaker"
|
||||
local file
|
||||
local path = ...
|
||||
if path:match "^https?://" then
|
||||
file = assert(http.get(path, nil, true))
|
||||
else
|
||||
file = assert(fs.open(shell.resolve(path), "rb"))
|
||||
end
|
||||
|
||||
if file.read(4) ~= "32VD" then file.close() error("Not a 32Vid file") end
|
||||
local width, height, fps, nstreams, flags = ("<HHBBH"):unpack(file.read(8))
|
||||
--print(width, height, fps, nstreams, flags)
|
||||
if nstreams ~= 1 then file.close() error("Separate stream files not supported by this tool") end
|
||||
if bit32_band(flags, 1) == 0 then file.close() error("DEFLATE or no compression not supported by this tool") end
|
||||
local _, nframes, ctype = ("<IIB"):unpack(file.read(9))
|
||||
if ctype ~= 0x0C then file.close() error("Stream type not supported by this tool") end
|
||||
|
||||
local monitors, mawidth, maheight
|
||||
if bit32.btest(flags, 0x20) then
|
||||
mawidth, maheight = bit32_band(bit32_rshift(flags, 6), 7) + 1, bit32_band(bit32_rshift(flags, 9), 7) + 1
|
||||
monitors = settings.get('sanjuuni.multimonitor')
|
||||
if not monitors or #monitors < maheight or #monitors[1] < mawidth then
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print('This video needs monitors to be calibrated before being displayed. Please right-click each monitor in order, from the top left corner to the bottom right corner, going right first, then down.\n')
|
||||
monitors = {}
|
||||
local a = {}
|
||||
for b = 1, maheight do
|
||||
monitors[b] = {}
|
||||
for c = 1, mawidth do
|
||||
local d, e = term.getCursorPos()
|
||||
for f = 1, maheight do
|
||||
term.setCursorPos(3, e + f - 1)
|
||||
term.clearLine()
|
||||
for g = 1, mawidth do term.blit('\x8F ', g == c and f == b and '00' or '77', 'ff') end
|
||||
end
|
||||
term.setCursorPos(3, e + maheight)
|
||||
term.write('(' .. c .. ', ' .. b .. ')')
|
||||
term.setCursorPos(1, e)
|
||||
repeat
|
||||
local d, h = os.pullEvent('monitor_touch')
|
||||
monitors[b][c] = h
|
||||
until not a[h]
|
||||
a[monitors[b][c]] = true
|
||||
sleep(0.25)
|
||||
end
|
||||
end
|
||||
settings.set('sanjuuni.multimonitor', monitors)
|
||||
settings.save()
|
||||
print('Calibration complete. Settings have been saved for future use.')
|
||||
end
|
||||
for _, r in ipairs(monitors) do for i, m in ipairs(r) do r[i] = peripheral.wrap(m) peripheral.call(m, "setTextScale", 0.5) peripheral.call(m, "clear") end end
|
||||
end
|
||||
|
||||
local function readDict(size)
|
||||
local retval = {}
|
||||
for i = 0, size - 1, 2 do
|
||||
local b = file.read()
|
||||
retval[i] = bit32.rshift(b, 4)
|
||||
retval[i+1] = bit32.band(b, 15)
|
||||
end
|
||||
return retval
|
||||
end
|
||||
local init, read
|
||||
if bit32_band(flags, 3) == 1 then
|
||||
local decodingTable, X, readbits, isColor
|
||||
function init(c)
|
||||
isColor = c
|
||||
local R = file.read()
|
||||
local L = 2^R
|
||||
local Ls = readDict(c and 24 or 32)
|
||||
if R == 0 then
|
||||
decodingTable = file.read()
|
||||
X = nil
|
||||
return
|
||||
end
|
||||
local a = 0
|
||||
for i = 0, #Ls do Ls[i] = Ls[i] == 0 and 0 or 2^(Ls[i]-1) a = a + Ls[i] end
|
||||
assert(a == L, a)
|
||||
decodingTable = {R = R}
|
||||
local x, step, next, symbol = 0, 0.625 * L + 3, {}, {}
|
||||
for i = 0, #Ls do
|
||||
next[i] = Ls[i]
|
||||
for _ = 1, Ls[i] do
|
||||
while symbol[x] do x = (x + 1) % L end
|
||||
x, symbol[x] = (x + step) % L, i
|
||||
end
|
||||
end
|
||||
for x = 0, L - 1 do
|
||||
local s = symbol[x]
|
||||
local t = {s = s, n = R - log2(next[s])}
|
||||
t.X, decodingTable[x], next[s] = bit32_lshift(next[s], t.n) - L, t, 1 + next[s]
|
||||
end
|
||||
local partial, bits, pos = 0, 0, 1
|
||||
function readbits(n)
|
||||
if not n then n = bits % 8 end
|
||||
if n == 0 then return 0 end
|
||||
while bits < n do pos, bits, partial = pos + 1, bits + 8, bit32_lshift(partial, 8) + file.read() end
|
||||
local retval = bit32_band(bit32_rshift(partial, bits-n), 2^n-1)
|
||||
bits = bits - n
|
||||
return retval
|
||||
end
|
||||
X = readbits(R)
|
||||
end
|
||||
function read(nsym)
|
||||
local retval = {}
|
||||
if X == nil then
|
||||
for i = 1, nsym do retval[i] = decodingTable end
|
||||
return retval
|
||||
end
|
||||
local i = 1
|
||||
local last = 0
|
||||
while i <= nsym do
|
||||
local t = decodingTable[X]
|
||||
if isColor and t.s >= 16 then
|
||||
local l = 2^(t.s - 15)
|
||||
for n = 0, l-1 do retval[i+n] = last end
|
||||
i = i + l
|
||||
else retval[i], last, i = t.s, t.s, i + 1 end
|
||||
X = t.X + readbits(t.n)
|
||||
end
|
||||
--print(X)
|
||||
return retval
|
||||
end
|
||||
else
|
||||
error("Unimplemented!")
|
||||
end
|
||||
|
||||
local blitColors = {[0] = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}
|
||||
local start = os.epoch "utc"
|
||||
local lastyield = start
|
||||
local vframe = 0
|
||||
local subs = {}
|
||||
term.clear()
|
||||
for _ = 1, nframes do
|
||||
local size, ftype = ("<IB"):unpack(file.read(5))
|
||||
--print(size, ftype, file.seek())
|
||||
if ftype == 0 then
|
||||
if os.epoch "utc" - lastyield > 3000 then sleep(0) lastyield = os.epoch "utc" end
|
||||
local dcstart = os.epoch "utc"
|
||||
--print("init screen", vframe, file.seek())
|
||||
init(false)
|
||||
--print("read screen", vframe, file.seek())
|
||||
local screen = read(width * height)
|
||||
--print("init colors", vframe, file.seek())
|
||||
init(true)
|
||||
--print("read bg colors", vframe)
|
||||
local bg = read(width * height)
|
||||
--print("read fg colors", vframe)
|
||||
local fg = read(width * height)
|
||||
local dctime = os.epoch "utc" - dcstart
|
||||
while os.epoch "utc" < start + vframe * 1000 / fps do end
|
||||
local texta, fga, bga = {}, {}, {}
|
||||
for y = 0, height - 1 do
|
||||
local text, fgs, bgs = "", "", ""
|
||||
for x = 1, width do
|
||||
text = text .. string.char(128 + screen[y*width+x])
|
||||
fgs = fgs .. blitColors[fg[y*width+x]]
|
||||
bgs = bgs .. blitColors[bg[y*width+x]]
|
||||
end
|
||||
texta[y+1], fga[y+1], bga[y+1] = text, fgs, bgs
|
||||
end
|
||||
for i = 0, 15 do term.setPaletteColor(2^i, file.read() / 255, file.read() / 255, file.read() / 255) end
|
||||
for y = 1, height do
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(texta[y], fga[y], bga[y])
|
||||
end
|
||||
local delete = {}
|
||||
for i, v in ipairs(subs) do
|
||||
if vframe <= v.frame + v.length then
|
||||
term.setCursorPos(v.x, v.y)
|
||||
term.setBackgroundColor(v.bgColor)
|
||||
term.setTextColor(v.fgColor)
|
||||
term.write(v.text)
|
||||
else delete[#delete+1] = i end
|
||||
end
|
||||
for i, v in ipairs(delete) do table.remove(subs, v - i + 1) end
|
||||
--term.setCursorPos(1, height + 1)
|
||||
--term.clearLine()
|
||||
--print("Frame decode time:", dctime, "ms")
|
||||
vframe = vframe + 1
|
||||
elseif ftype == 1 then
|
||||
local audio = file.read(size)
|
||||
if speaker then
|
||||
if bit32_band(flags, 12) == 0 then
|
||||
local chunk = {audio:byte(1, -1)}
|
||||
for i = 1, #chunk do chunk[i] = chunk[i] - 128 end
|
||||
speaker.playAudio(chunk)
|
||||
else
|
||||
speaker.playAudio(dfpwm.decode(audio))
|
||||
end
|
||||
end
|
||||
elseif ftype == 8 then
|
||||
local data = file.read(size)
|
||||
local sub = {}
|
||||
sub.frame, sub.length, sub.x, sub.y, sub.color, sub.flags, sub.text = ("<IIHHBBs2"):unpack(data)
|
||||
sub.bgColor, sub.fgColor = 2^bit32_rshift(sub.color, 4), 2^bit32_band(sub.color, 15)
|
||||
subs[#subs+1] = sub
|
||||
elseif ftype >= 0x40 and ftype < 0x80 then
|
||||
if ftype == 64 then vframe = vframe + 1 end
|
||||
local mx, my = bit32_band(bit32_rshift(ftype, 3), 7) + 1, bit32_band(ftype, 7) + 1
|
||||
--print("(" .. mx .. ", " .. my .. ")")
|
||||
local term = monitors[my][mx]
|
||||
if os.epoch "utc" - lastyield > 3000 then sleep(0) lastyield = os.epoch "utc" end
|
||||
local width, height = ("<HH"):unpack(file.read(4))
|
||||
local dcstart = os.epoch "utc"
|
||||
--print("init screen", vframe, file.seek())
|
||||
init(false)
|
||||
--print("read screen", vframe, file.seek())
|
||||
local screen = read(width * height)
|
||||
--print("init colors", vframe, file.seek())
|
||||
init(true)
|
||||
--print("read bg colors", vframe)
|
||||
local bg = read(width * height)
|
||||
--print("read fg colors", vframe)
|
||||
local fg = read(width * height)
|
||||
local dctime = os.epoch "utc" - dcstart
|
||||
while os.epoch "utc" < start + vframe * 1000 / fps do end
|
||||
local texta, fga, bga = {}, {}, {}
|
||||
for y = 0, height - 1 do
|
||||
local text, fgs, bgs = "", "", ""
|
||||
for x = 1, width do
|
||||
text = text .. string.char(128 + screen[y*width+x])
|
||||
fgs = fgs .. blitColors[fg[y*width+x]]
|
||||
bgs = bgs .. blitColors[bg[y*width+x]]
|
||||
end
|
||||
texta[y+1], fga[y+1], bga[y+1] = text, fgs, bgs
|
||||
end
|
||||
for i = 0, 15 do term.setPaletteColor(2^i, file.read() / 255, file.read() / 255, file.read() / 255) end
|
||||
for y = 1, height do
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(texta[y], fga[y], bga[y])
|
||||
end
|
||||
--[[local delete = {}
|
||||
for i, v in ipairs(subs) do
|
||||
if vframe <= v.frame + v.length then
|
||||
term.setCursorPos(v.x, v.y)
|
||||
term.setBackgroundColor(v.bgColor)
|
||||
term.setTextColor(v.fgColor)
|
||||
term.write(v.text)
|
||||
else delete[#delete+1] = i end
|
||||
end
|
||||
for i, v in ipairs(delete) do table.remove(subs, v - i + 1) end]]
|
||||
--term.setCursorPos(1, height + 1)
|
||||
--term.clearLine()
|
||||
--print("Frame decode time:", dctime, "ms")
|
||||
else file.close() error("Unknown frame type " .. ftype) end
|
||||
end
|
||||
|
||||
for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.setCursorPos(1, 1)
|
||||
term.clear()
|
@ -1,654 +0,0 @@
|
||||
local inflate do
|
||||
|
||||
local floor = math.floor
|
||||
local ceil = math.ceil
|
||||
local min = math.min
|
||||
local max = math.max
|
||||
|
||||
-- utility functions
|
||||
|
||||
local function memoize(f)
|
||||
local cache = {}
|
||||
return function(...)
|
||||
local key = table.concat({...}, "-")
|
||||
if not cache[key] then
|
||||
cache[key] = f(...)
|
||||
end
|
||||
return cache[key]
|
||||
end
|
||||
end
|
||||
|
||||
local function int(bytes)
|
||||
local n = 0
|
||||
for i = 1, #bytes do
|
||||
n = 256*n + bytes:sub(i, i):byte()
|
||||
end
|
||||
return n
|
||||
end
|
||||
int = memoize(int)
|
||||
|
||||
local function bint(bits)
|
||||
return tonumber(bits, 2) or 0
|
||||
end
|
||||
bint = memoize(bint)
|
||||
|
||||
local function bits(b, width)
|
||||
local s = ""
|
||||
if type(b) == "number" then
|
||||
for i = 1, width do
|
||||
s = b%2 .. s
|
||||
b = floor(b/2)
|
||||
end
|
||||
else
|
||||
for i = 1, #b do
|
||||
s = s .. bits(b:byte(i), 8):reverse()
|
||||
assert(#s == i * 8, s)
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
bits = memoize(bits)
|
||||
|
||||
local function fill(bytes, len)
|
||||
return bytes:rep(floor(len / #bytes)) .. bytes:sub(1, len % #bytes)
|
||||
end
|
||||
|
||||
local function zip(t1, t2)
|
||||
local zipped = {}
|
||||
for i = 1, max(#t1, #t2) do
|
||||
zipped[#zipped + 1] = {t1[i], t2[i]}
|
||||
end
|
||||
return zipped
|
||||
end
|
||||
|
||||
local function unzip(zipped)
|
||||
local t1, t2 = {}, {}
|
||||
for i = 1, #zipped do
|
||||
t1[#t1 + 1] = zipped[i][1]
|
||||
t2[#t2 + 1] = zipped[i][2]
|
||||
end
|
||||
return t1, t2
|
||||
end
|
||||
|
||||
local function map(f, t)
|
||||
local mapped = {}
|
||||
for i = 1, #t do
|
||||
mapped[#mapped + 1] = f(t[i], i)
|
||||
end
|
||||
return mapped
|
||||
end
|
||||
|
||||
local function filter(pred, t)
|
||||
local filtered = {}
|
||||
for i = 1, #t do
|
||||
if pred(t[i], i) then
|
||||
filtered[#filtered + 1] = t[i]
|
||||
end
|
||||
end
|
||||
return filtered
|
||||
end
|
||||
|
||||
local function find(key, t)
|
||||
if type(key) == "function" then
|
||||
for i = 1, #t do
|
||||
if key(t[i]) then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return nil
|
||||
else
|
||||
return find(function(x) return x == key end, t)
|
||||
end
|
||||
end
|
||||
|
||||
local function slice(t, i, j, step)
|
||||
local sliced = {}
|
||||
for k = i < 1 and 1 or i, i < 1 and #t + i or j or #t, step or 1 do
|
||||
sliced[#sliced + 1] = t[k]
|
||||
end
|
||||
return sliced
|
||||
end
|
||||
|
||||
local function range(i, j)
|
||||
local r = {}
|
||||
for k = j and i or 0, j or i - 1 do
|
||||
r[#r + 1] = k
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
-- streams
|
||||
|
||||
local function output_stream()
|
||||
local stream, buffer = {}, {}
|
||||
local curr = 0
|
||||
|
||||
function stream:write(bytes)
|
||||
for i = 1, #bytes do
|
||||
buffer[#buffer + 1] = bytes:sub(i, i)
|
||||
end
|
||||
curr = curr + #bytes
|
||||
end
|
||||
|
||||
function stream:back_read(offset, n)
|
||||
local read = {}
|
||||
for i = curr - offset + 1, curr - offset + n do
|
||||
read[#read + 1] = buffer[i]
|
||||
end
|
||||
return table.concat(read)
|
||||
end
|
||||
|
||||
function stream:back_copy(dist, len)
|
||||
local start, copied = curr - dist + 1, {}
|
||||
for i = start, min(start + len, curr) do
|
||||
copied[#copied + 1] = buffer[i]
|
||||
end
|
||||
self:write(fill(table.concat(copied), len))
|
||||
end
|
||||
|
||||
function stream:pos()
|
||||
return curr
|
||||
end
|
||||
|
||||
function stream:raw()
|
||||
return table.concat(buffer)
|
||||
end
|
||||
|
||||
return stream
|
||||
end
|
||||
|
||||
local function bit_stream(raw, offset)
|
||||
local stream = {}
|
||||
local curr = 0
|
||||
offset = offset or 0
|
||||
|
||||
function stream:read(n, reverse)
|
||||
local start = floor(curr/8) + offset + 1
|
||||
local bb = ""
|
||||
for i = start, start + ceil(n/8) do
|
||||
local b = raw:byte(i)
|
||||
for j = 0, 7 do bb = bb .. bit32.extract(b, j, 1) end
|
||||
end
|
||||
local b = bb:sub(curr%8 + 1, curr%8 + n)
|
||||
curr = curr + n
|
||||
return reverse and b or b:reverse()
|
||||
end
|
||||
|
||||
function stream:seek(n)
|
||||
if n == "beg" then
|
||||
curr = 0
|
||||
elseif n == "end" then
|
||||
curr = #raw
|
||||
else
|
||||
curr = curr + n
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function stream:is_empty()
|
||||
return curr >= 8*#raw
|
||||
end
|
||||
|
||||
function stream:pos()
|
||||
return curr
|
||||
end
|
||||
|
||||
return stream
|
||||
end
|
||||
|
||||
-- inflate
|
||||
|
||||
local CL_LENS_ORDER = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}
|
||||
local MAX_BITS = 15
|
||||
local PT_WIDTH = 8
|
||||
|
||||
local function cl_code_lens(stream, hclen)
|
||||
local code_lens = {}
|
||||
for i = 1, hclen do
|
||||
code_lens[#code_lens + 1] = bint(stream:read(3))
|
||||
end
|
||||
return code_lens
|
||||
end
|
||||
|
||||
local function code_tree(lens, alphabet)
|
||||
alphabet = alphabet or range(#lens)
|
||||
local using = filter(function(x, i) return lens[i] and lens[i] > 0 end, alphabet)
|
||||
lens = filter(function(x) return x > 0 end, lens)
|
||||
local tree = zip(lens, using)
|
||||
table.sort(tree, function(a, b)
|
||||
if a[1] == b[1] then
|
||||
return a[2] < b[2]
|
||||
else
|
||||
return a[1] < b[1]
|
||||
end
|
||||
end)
|
||||
return unzip(tree)
|
||||
end
|
||||
|
||||
local function codes(lens)
|
||||
local codes = {}
|
||||
local code = 0
|
||||
for i = 1, #lens do
|
||||
codes[#codes + 1] = bits(code, lens[i])
|
||||
if i < #lens then
|
||||
code = (code + 1)*2^(lens[i + 1] - lens[i])
|
||||
end
|
||||
end
|
||||
return codes
|
||||
end
|
||||
|
||||
local prefix_table
|
||||
local function handle_long_codes(codes, alphabet, pt)
|
||||
local i = find(function(x) return #x > PT_WIDTH end, codes)
|
||||
local long = slice(zip(codes, alphabet), i)
|
||||
i = 0
|
||||
repeat
|
||||
local prefix = long[i + 1][1]:sub(1, PT_WIDTH)
|
||||
local same = filter(function(x) return x[1]:sub(1, PT_WIDTH) == prefix end, long)
|
||||
same = map(function(x) return {x[1]:sub(PT_WIDTH + 1), x[2]} end, same)
|
||||
pt[prefix] = {rest = prefix_table(unzip(same)), unused = 0}
|
||||
i = i + #same
|
||||
until i == #long
|
||||
end
|
||||
|
||||
function prefix_table(codes, alphabet)
|
||||
local pt = {}
|
||||
if #codes[#codes] > PT_WIDTH then
|
||||
handle_long_codes(codes, alphabet, pt)
|
||||
end
|
||||
for i = 1, #codes do
|
||||
local code = codes[i]
|
||||
if #code > PT_WIDTH then
|
||||
break
|
||||
end
|
||||
local entry = {value = alphabet[i], unused = PT_WIDTH - #code}
|
||||
if entry.unused == 0 then
|
||||
pt[code] = entry
|
||||
else
|
||||
for i = 0, 2^entry.unused - 1 do
|
||||
pt[code .. bits(i, entry.unused)] = entry
|
||||
end
|
||||
end
|
||||
end
|
||||
return pt
|
||||
end
|
||||
|
||||
local function huffman_decoder(lens, alphabet)
|
||||
local base_codes = prefix_table(codes(lens), alphabet)
|
||||
return function(stream)
|
||||
local codes = base_codes
|
||||
local entry
|
||||
repeat
|
||||
entry = codes[stream:read(PT_WIDTH, true)]
|
||||
stream:seek(-entry.unused)
|
||||
codes = entry.rest
|
||||
until not codes
|
||||
return entry.value
|
||||
end
|
||||
end
|
||||
|
||||
local function code_lens(stream, decode, n)
|
||||
local lens = {}
|
||||
repeat
|
||||
local value = decode(stream)
|
||||
if value < 16 then
|
||||
lens[#lens + 1] = value
|
||||
elseif value == 16 then
|
||||
for i = 1, bint(stream:read(2)) + 3 do
|
||||
lens[#lens + 1] = lens[#lens]
|
||||
end
|
||||
elseif value == 17 then
|
||||
for i = 1, bint(stream:read(3)) + 3 do
|
||||
lens[#lens + 1] = 0
|
||||
end
|
||||
elseif value == 18 then
|
||||
for i = 1, bint(stream:read(7)) + 11 do
|
||||
lens[#lens + 1] = 0
|
||||
end
|
||||
end
|
||||
until #lens == n
|
||||
return lens
|
||||
end
|
||||
|
||||
local function code_trees(stream)
|
||||
local hlit = bint(stream:read(5)) + 257
|
||||
local hdist = bint(stream:read(5)) + 1
|
||||
local hclen = bint(stream:read(4)) + 4
|
||||
local cl_decode = huffman_decoder(code_tree(cl_code_lens(stream, hclen), CL_LENS_ORDER))
|
||||
local ll_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hlit)))
|
||||
local d_decode = huffman_decoder(code_tree(code_lens(stream, cl_decode, hdist)))
|
||||
return ll_decode, d_decode
|
||||
end
|
||||
|
||||
local function extra_bits(value)
|
||||
if value >= 4 and value <= 29 then
|
||||
return floor(value/2) - 1
|
||||
elseif value >= 265 and value <= 284 then
|
||||
return ceil(value/4) - 66
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
extra_bits = memoize(extra_bits)
|
||||
|
||||
local function decode_len(value, bits)
|
||||
assert(value >= 257 and value <= 285, "value out of range")
|
||||
assert(#bits == extra_bits(value), "wrong number of extra bits")
|
||||
if value <= 264 then
|
||||
return value - 254
|
||||
elseif value == 285 then
|
||||
return 258
|
||||
end
|
||||
local len = 11
|
||||
for i = 1, #bits - 1 do
|
||||
len = len + 2^(i+2)
|
||||
end
|
||||
return floor(bint(bits) + len + ((value - 1) % 4)*2^#bits)
|
||||
end
|
||||
decode_len = memoize(decode_len)
|
||||
|
||||
local function a(n)
|
||||
if n <= 3 then
|
||||
return n + 2
|
||||
else
|
||||
return a(n-1) + 2*a(n-2) - 2*a(n-3)
|
||||
end
|
||||
end
|
||||
a = memoize(a)
|
||||
|
||||
local function decode_dist(value, bits)
|
||||
assert(value >= 0 and value <= 29, "value out of range")
|
||||
assert(#bits == extra_bits(value), "wrong number of extra bits)")
|
||||
return bint(bits) + a(value - 1)
|
||||
end
|
||||
decode_dist = memoize(decode_dist)
|
||||
|
||||
function inflate(data)
|
||||
local stream = bit_stream(data)
|
||||
local ostream = output_stream()
|
||||
repeat
|
||||
local bfinal, btype = bint(stream:read(1)), bint(stream:read(2))
|
||||
assert(btype == 2, "compression method not supported")
|
||||
local ll_decode, d_decode = code_trees(stream)
|
||||
while true do
|
||||
local value = ll_decode(stream)
|
||||
if value < 256 then
|
||||
ostream:write(string.char(value))
|
||||
elseif value == 256 then
|
||||
break
|
||||
else
|
||||
local len = decode_len(value, stream:read(extra_bits(value)))
|
||||
value = d_decode(stream)
|
||||
local dist = decode_dist(value, stream:read(extra_bits(value)))
|
||||
ostream:back_copy(dist, len)
|
||||
end
|
||||
end
|
||||
until bfinal == 1
|
||||
return ostream:raw()
|
||||
end
|
||||
|
||||
end
|
||||
local dfpwm = require "cc.audio.dfpwm"
|
||||
|
||||
local hexstr = "0123456789abcdef"
|
||||
local file, err = fs.open(shell.resolve(...), "rb")
|
||||
if not file then error(err) end
|
||||
if file.read(4) ~= "32VD" then file.close() error("Not a 32vid file") end
|
||||
local width, height, fps, nStreams, flags = ("<HHBBH"):unpack(file.read(8))
|
||||
local video, audio, subtitles
|
||||
for _ = 1, nStreams do
|
||||
local size, nFrames, frameType = ("<IIB"):unpack(file.read(9))
|
||||
print(("%X"):format(file.seek()), size, nFrames, frameType)
|
||||
if frameType == 0 and not video then
|
||||
local data = file.read(size)
|
||||
if bit32.band(flags, 3) == 2 then data = inflate(data) end
|
||||
video = {}
|
||||
local pos = 1
|
||||
local start = os.epoch "utc"
|
||||
for i = 1, nFrames do
|
||||
if i % 100 == 0 or i >= nFrames - 10 then print(i, os.epoch "utc" - start) start = os.epoch "utc" sleep(0) end
|
||||
local frame = {palette = {}}
|
||||
local tmp, tmppos = 0, 0
|
||||
local use5bit, customcompress = bit32.btest(flags, 16), bit32.band(flags, 3) == 3
|
||||
local codetree = {}
|
||||
local solidchar, runlen
|
||||
local function readField(isColor)
|
||||
if customcompress then
|
||||
if runlen then
|
||||
local c = solidchar
|
||||
runlen = runlen - 1
|
||||
if runlen == 0 then runlen = nil end
|
||||
return c
|
||||
end
|
||||
if not isColor and solidchar then return solidchar end
|
||||
-- MARK: Huffman decoding
|
||||
local node = codetree
|
||||
while true do
|
||||
local n = bit32.extract(tmp, tmppos, 1)
|
||||
tmppos = tmppos - 1
|
||||
if tmppos < 0 then tmp, pos, tmppos = data:byte(pos), pos + 1, 7 end
|
||||
if type(node) ~= "table" then error(("Invalid tree state: position %X, frame %d"):format(pos+file.seek()-size-1, i)) end
|
||||
if type(node[n]) == "number" then
|
||||
local c = node[n]
|
||||
if isColor then
|
||||
if c > 15 then runlen = 2^(c-15)-1 return assert(solidchar)
|
||||
else solidchar = c end
|
||||
end
|
||||
return c
|
||||
else node = node[n] end
|
||||
end
|
||||
else
|
||||
local n
|
||||
if tmppos * 5 + 5 > 32 then n = bit32.extract(math.floor(tmp / 0x1000000), tmppos * 5 - 24, 5)
|
||||
else n = bit32.extract(tmp, tmppos * 5, 5) end
|
||||
tmppos = tmppos - 1
|
||||
if tmppos < 0 then tmp, pos = (">I5"):unpack(data, pos) tmppos = 7 end
|
||||
return n
|
||||
end
|
||||
end
|
||||
if customcompress then
|
||||
-- MARK: Huffman tree reconstruction
|
||||
-- read bit lengths
|
||||
local bitlen = {}
|
||||
if use5bit then
|
||||
for j = 0, 15 do
|
||||
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)}
|
||||
end
|
||||
pos = pos + 16
|
||||
else
|
||||
for j = 0, 7 do
|
||||
tmp, pos = (">I5"):unpack(data, pos)
|
||||
bitlen[j*8+1] = {s = j*8+1, l = math.floor(tmp / 0x800000000)}
|
||||
bitlen[j*8+2] = {s = j*8+2, l = math.floor(tmp / 0x40000000) % 32}
|
||||
for k = 3, 8 do bitlen[j*8+k] = {s = j*8+k, l = bit32.extract(tmp, (8-k)*5, 5)} end
|
||||
end
|
||||
end
|
||||
do
|
||||
local j = 1
|
||||
while j <= #bitlen do
|
||||
if bitlen[j].l == 0 then table.remove(bitlen, j)
|
||||
else j = j + 1 end
|
||||
end
|
||||
end
|
||||
if #bitlen == 0 then
|
||||
-- screen is solid character
|
||||
solidchar = data:byte(pos)
|
||||
pos = pos + 1
|
||||
else
|
||||
-- reconstruct codes from bit lengths
|
||||
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)
|
||||
bitlen[1].c = 0
|
||||
for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end
|
||||
-- create tree from codes
|
||||
for j = 1, #bitlen do
|
||||
local c = bitlen[j].c
|
||||
local node = codetree
|
||||
for k = bitlen[j].l - 1, 1, -1 do
|
||||
local n = bit32.extract(c, k, 1)
|
||||
if not node[n] then node[n] = {} end
|
||||
node = node[n]
|
||||
if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end
|
||||
end
|
||||
local n = bit32.extract(c, 0, 1)
|
||||
node[n] = bitlen[j].s
|
||||
end
|
||||
-- read first byte
|
||||
tmp, tmppos, pos = data:byte(pos), 7, pos + 1
|
||||
end
|
||||
else readField() end
|
||||
for y = 1, height do
|
||||
local line = {"", "", "", {}}
|
||||
for x = 1, width do
|
||||
if pos + 5 + 1 >= #data then print(i, pos, x, y) error() end
|
||||
local n = readField()
|
||||
line[1] = line[1] .. string.char(128 + (n % 0x20))
|
||||
line[4][x] = bit32.btest(n, 0x20)
|
||||
end
|
||||
frame[y] = line
|
||||
end
|
||||
if customcompress then
|
||||
if tmppos == 7 then pos = pos - 1 end
|
||||
codetree = {}
|
||||
-- MARK: Huffman tree reconstruction
|
||||
-- read bit lengths
|
||||
local bitlen = {}
|
||||
for j = 0, 11 do
|
||||
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)}
|
||||
end
|
||||
pos = pos + 12
|
||||
do
|
||||
local j = 1
|
||||
while j <= #bitlen do
|
||||
if bitlen[j].l == 0 then table.remove(bitlen, j)
|
||||
else j = j + 1 end
|
||||
end
|
||||
end
|
||||
if #bitlen == 0 then
|
||||
-- screen is solid color
|
||||
solidchar = data:byte(pos)
|
||||
pos = pos + 1
|
||||
runlen = math.huge
|
||||
else
|
||||
-- reconstruct codes from bit lengths
|
||||
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)
|
||||
bitlen[1].c = 0
|
||||
for j = 2, #bitlen do bitlen[j].c = bit32.lshift(bitlen[j-1].c + 1, bitlen[j].l - bitlen[j-1].l) end
|
||||
-- create tree from codes
|
||||
for j = 1, #bitlen do
|
||||
local c = bitlen[j].c
|
||||
local node = codetree
|
||||
for k = bitlen[j].l - 1, 1, -1 do
|
||||
local n = bit32.extract(c, k, 1)
|
||||
if not node[n] then node[n] = {} end
|
||||
node = node[n]
|
||||
if type(node) == "number" then error(("Invalid tree state: position %X, frame %d, #bitlen = %d, current entry = %d"):format(pos, i, #bitlen, j)) end
|
||||
end
|
||||
local n = bit32.extract(c, 0, 1)
|
||||
node[n] = bitlen[j].s
|
||||
end
|
||||
-- read first byte
|
||||
tmp, tmppos, pos = data:byte(pos), 7, pos + 1
|
||||
end
|
||||
for y = 1, height do
|
||||
local line = frame[y]
|
||||
for x = 1, width do
|
||||
local c = readField(true)
|
||||
line[2] = line[2] .. hexstr:sub(c+1, c+1)
|
||||
end
|
||||
end
|
||||
runlen = nil
|
||||
for y = 1, height do
|
||||
local line = frame[y]
|
||||
for x = 1, width do
|
||||
local c = readField(true)
|
||||
line[3] = line[3] .. hexstr:sub(c+1, c+1)
|
||||
end
|
||||
end
|
||||
if tmppos == 7 then pos = pos - 1 end
|
||||
else
|
||||
for y = 1, height do
|
||||
local line = frame[y]
|
||||
for x = 1, width do
|
||||
local c = data:byte(pos)
|
||||
pos = pos + 1
|
||||
if line[4][x] then c = bit32.bor(bit32.band(bit32.lshift(c, 4), 0xF0), bit32.rshift(c, 4)) end
|
||||
line[2] = line[2] .. hexstr:sub(bit32.band(c, 15)+1, bit32.band(c, 15)+1)
|
||||
line[3] = line[3] .. hexstr:sub(bit32.rshift(c, 4)+1, bit32.rshift(c, 4)+1)
|
||||
end
|
||||
line[4] = nil
|
||||
end
|
||||
end
|
||||
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
|
||||
video[i] = frame
|
||||
end
|
||||
elseif frameType == 1 and not audio then
|
||||
audio = {}
|
||||
if bit32.band(flags, 12) == 0 then
|
||||
for i = 1, math.ceil(size / 48000) do
|
||||
local data
|
||||
if jit then
|
||||
data = {}
|
||||
for j = 0, 5 do
|
||||
local t = {file.read(math.min(size - (i-1) * 48000 - j * 8000, 8000)):byte(1, -1)}
|
||||
if #t > 0 then for k = 1, #t do data[j*8000+k] = t[k] end end
|
||||
end
|
||||
else data = {file.read(math.min(size - (i-1) * 48000, 48000)):byte(1, -1)} end
|
||||
for j = 1, #data do data[j] = data[j] - 128 end
|
||||
audio[i] = data
|
||||
end
|
||||
elseif bit32.band(flags, 12) == 4 then
|
||||
local decode = dfpwm.make_decoder()
|
||||
for i = 1, math.ceil(size / 6000) do
|
||||
local data = file.read(math.min(size - (i-1)*6000, 6000))
|
||||
if not data then break end
|
||||
audio[i] = decode(data)
|
||||
end
|
||||
end
|
||||
elseif frameType == 8 and not subtitles then
|
||||
subtitles = {}
|
||||
for _ = 1, nFrames do
|
||||
local start, length, x, y, color, sz = ("<IIHHBxH"):unpack(file.read(16))
|
||||
local text = file.read(sz)
|
||||
local sub = {text, hexstr:sub(bit32.band(color, 15)+1, bit32.band(color, 15)+1):rep(#text),
|
||||
hexstr:sub(bit32.rshift(color, 4)+1, bit32.rshift(color, 4)+1):rep(#text), x = x, y = y}
|
||||
for n = start, start + length - 1 do
|
||||
subtitles[n] = subtitles[n] or {}
|
||||
subtitles[n][#subtitles[n]+1] = sub
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
file.close()
|
||||
if not video then error("No video stream found") end
|
||||
sleep(0)
|
||||
|
||||
local speaker = peripheral.find "speaker"
|
||||
term.clear()
|
||||
local ok, err = pcall(parallel.waitForAll, function()
|
||||
local start = os.epoch "utc"
|
||||
for f, image in ipairs(video) do
|
||||
for i, v in ipairs(image.palette) do term.setPaletteColor(2^(i-1), table.unpack(v)) end
|
||||
for y, r in ipairs(image) do
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(table.unpack(r))
|
||||
end
|
||||
if subtitles and subtitles[f-1] then
|
||||
for _, v in ipairs(subtitles[f-1]) do
|
||||
term.setCursorPos(v.x, v.y)
|
||||
term.blit(table.unpack(v))
|
||||
end
|
||||
end
|
||||
while os.epoch "utc" < start + (f + 1) / fps * 1000 do sleep(1 / fps) end
|
||||
end
|
||||
end, function()
|
||||
if not speaker or not speaker.playAudio or not audio then return end
|
||||
for _, chunk in ipairs(audio) do
|
||||
while not speaker.playAudio(chunk) do repeat local ev, sp = os.pullEvent("speaker_audio_empty") until sp == peripheral.getName(speaker) end
|
||||
end
|
||||
end)
|
||||
for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.setCursorPos(1, 1)
|
||||
term.clear()
|
||||
if not ok then printError(err) end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,73 +0,0 @@
|
||||
local path = ...
|
||||
if not path then error("Usage: bimg-player <file.bimg>") end
|
||||
local file, err = fs.open(shell.resolve(path), "rb")
|
||||
if not file then error("Could not open file: " .. err) end
|
||||
local img = textutils.unserialize(file.readAll())
|
||||
file.close()
|
||||
local function drawFrame(frame, term)
|
||||
for y, row in ipairs(frame) do
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(table.unpack(row))
|
||||
end
|
||||
if frame.palette then for i = 0, #frame.palette do
|
||||
local c = frame.palette[i]
|
||||
if type(c) == "table" then term.setPaletteColor(2^i, table.unpack(c))
|
||||
else term.setPaletteColor(2^i, c) end
|
||||
end end
|
||||
if img.multiMonitor then term.setTextScale(img.multiMonitor.scale or 0.5) end
|
||||
end
|
||||
if img.multiMonitor then
|
||||
local width, height = img.multiMonitor.width, img.multiMonitor.height
|
||||
local monitors = settings.get('sanjuuni.multimonitor')
|
||||
if not monitors or #monitors < height or #monitors[1] < width then
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print('This image needs monitors to be calibrated before being displayed. Please right-click each monitor in order, from the top left corner to the bottom right corner, going right first, then down.')
|
||||
monitors = {}
|
||||
local names = {}
|
||||
for y = 1, height do
|
||||
monitors[y] = {}
|
||||
for x = 1, width do
|
||||
local _, oy = term.getCursorPos()
|
||||
for ly = 1, height do
|
||||
term.setCursorPos(3, oy + ly - 1)
|
||||
term.clearLine()
|
||||
for lx = 1, width do term.blit('\x8F ', (lx == x and ly == y) and '00' or '77', 'ff') end
|
||||
end
|
||||
term.setCursorPos(3, oy + height)
|
||||
term.write('(' .. x .. ', ' .. y .. ')')
|
||||
term.setCursorPos(1, oy)
|
||||
repeat
|
||||
local _, name = os.pullEvent('monitor_touch')
|
||||
monitors[y][x] = name
|
||||
until not names[name]
|
||||
names[monitors[y][x]] = true
|
||||
sleep(0.25)
|
||||
end
|
||||
end
|
||||
settings.set('sanjuuni.multimonitor', monitors)
|
||||
settings.save()
|
||||
print('Calibration complete. Settings have been saved for future use.')
|
||||
end
|
||||
for i = 1, #img, width * height do
|
||||
for y = 1, height do
|
||||
for x = 1, width do
|
||||
drawFrame(img[i + (y-1) * width + x-1], peripheral.wrap(monitors[y][x]))
|
||||
end
|
||||
end
|
||||
if img.animation then sleep(img[i].duration or img.secondsPerFrame or 0.05)
|
||||
else read() break end
|
||||
end
|
||||
else
|
||||
term.clear()
|
||||
for _, frame in ipairs(img) do
|
||||
drawFrame(frame, term)
|
||||
if img.animation then sleep(frame.duration or img.secondsPerFrame or 0.05)
|
||||
else read() break end
|
||||
end
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end
|
||||
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,80 +0,0 @@
|
||||
local b64str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
local function base64decode(str)
|
||||
local retval = ""
|
||||
for s in str:gmatch "...." do
|
||||
if s:sub(3, 4) == '==' then
|
||||
retval = retval .. string.char(bit32.bor(bit32.lshift(b64str:find(s:sub(1, 1)) - 1, 2), bit32.rshift(b64str:find(s:sub(2, 2)) - 1, 4)))
|
||||
elseif s:sub(4, 4) == '=' then
|
||||
local n = (b64str:find(s:sub(1, 1))-1) * 4096 + (b64str:find(s:sub(2, 2))-1) * 64 + (b64str:find(s:sub(3, 3))-1)
|
||||
retval = retval .. string.char(bit32.extract(n, 10, 8)) .. string.char(bit32.extract(n, 2, 8))
|
||||
else
|
||||
local n = (b64str:find(s:sub(1, 1))-1) * 262144 + (b64str:find(s:sub(2, 2))-1) * 4096 + (b64str:find(s:sub(3, 3))-1) * 64 + (b64str:find(s:sub(4, 4))-1)
|
||||
retval = retval .. string.char(bit32.extract(n, 16, 8)) .. string.char(bit32.extract(n, 8, 8)) .. string.char(bit32.extract(n, 0, 8))
|
||||
end
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
local file, err = fs.open(shell.resolve(...), "r")
|
||||
if not file then error(err) end
|
||||
if file.readLine() ~= "32Vid 1.1" then error("Unsupported file") end
|
||||
local fps = tonumber(file.readLine())
|
||||
local first, second = file.readLine(), file.readLine()
|
||||
if second == "" or second == nil then fps = 0 end
|
||||
term.clear()
|
||||
while true do
|
||||
local frame
|
||||
if first then frame, first = first, nil
|
||||
elseif second then frame, second = second, nil
|
||||
else frame = file.readLine() end
|
||||
if frame == "" or frame == nil then break end
|
||||
local mode = frame:match("^!CP([CD])")
|
||||
if not mode then error("Invalid file") end
|
||||
local b64data
|
||||
if mode == "C" then
|
||||
local len = tonumber(frame:sub(5, 8), 16)
|
||||
b64data = frame:sub(9, len + 8)
|
||||
else
|
||||
local len = tonumber(frame:sub(5, 16), 16)
|
||||
b64data = frame:sub(17, len + 16)
|
||||
end
|
||||
local data = base64decode(b64data)
|
||||
-- TODO: maybe verify checksums?
|
||||
assert(data:sub(1, 4) == "\0\0\0\0" and data:sub(9, 16) == "\0\0\0\0\0\0\0\0", "Invalid file")
|
||||
local width, height = ("HH"):unpack(data, 5)
|
||||
local c, n, pos = string.unpack("c1B", data, 17)
|
||||
local text = {}
|
||||
for y = 1, height do
|
||||
text[y] = ""
|
||||
for x = 1, width do
|
||||
text[y] = text[y] .. c
|
||||
n = n - 1
|
||||
if n == 0 then c, n, pos = string.unpack("c1B", data, pos) end
|
||||
end
|
||||
end
|
||||
c = c:byte()
|
||||
for y = 1, height do
|
||||
local fg, bg = "", ""
|
||||
for x = 1, width do
|
||||
fg, bg = fg .. ("%x"):format(bit32.band(c, 0x0F)), bg .. ("%x"):format(bit32.rshift(c, 4))
|
||||
n = n - 1
|
||||
if n == 0 then c, n, pos = string.unpack("BB", data, pos) end
|
||||
end
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(text[y], fg, bg)
|
||||
end
|
||||
pos = pos - 2
|
||||
local r, g, b
|
||||
for i = 0, 15 do
|
||||
r, g, b, pos = string.unpack("BBB", data, pos)
|
||||
term.setPaletteColor(2^i, r / 255, g / 255, b / 255)
|
||||
end
|
||||
if fps == 0 then read() break
|
||||
else sleep(1 / fps) end
|
||||
end
|
||||
file.close()
|
||||
for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,51 +0,0 @@
|
||||
local ws, err = http.websocket(...)
|
||||
if not ws then error("Could not connect to WebSocket server: " .. err) end
|
||||
ws.send("n")
|
||||
local nFrames = tonumber(ws.receive(), 10)
|
||||
ws.send("f")
|
||||
local fps = tonumber(ws.receive(), 10)
|
||||
local speaker = peripheral.find "speaker"
|
||||
term.clear()
|
||||
local lock = false
|
||||
parallel.waitForAll(function()
|
||||
local start = os.epoch "utc"
|
||||
for f = 0, nFrames - 1 do
|
||||
while lock do os.pullEvent() end
|
||||
lock = true
|
||||
ws.send("v" .. f)
|
||||
local frame, ok = ws.receive()
|
||||
while #frame % 65535 == 0 do frame = frame .. ws.receive() end
|
||||
lock = false
|
||||
--if not ok then break end
|
||||
if load(frame) then
|
||||
local image, palette = assert(load(frame, "=frame", "t", {}))()
|
||||
for i = 0, #palette do term.setPaletteColor(2^i, table.unpack(palette[i])) end
|
||||
for y, r in ipairs(image) do
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(table.unpack(r))
|
||||
end
|
||||
end
|
||||
while os.epoch "utc" < start + (f + 1) / fps * 1000 do sleep(1 / fps) end
|
||||
end
|
||||
end, function()
|
||||
if not speaker or not speaker.playAudio then return end
|
||||
local pos = 0
|
||||
while pos < nFrames / fps * 48000 do
|
||||
while lock do os.pullEvent() end
|
||||
lock = true
|
||||
ws.send("a" .. pos)
|
||||
local audio, ok = ws.receive(1)
|
||||
lock = false
|
||||
if not ok then break end
|
||||
audio = {audio:byte(1, -1)}
|
||||
for i = 1, #audio do audio[i] = audio[i] - 128 end
|
||||
pos = pos + #audio
|
||||
while not speaker.playAudio(audio) do repeat local ev, sp = os.pullEvent("speaker_audio_empty") until sp == peripheral.getName(speaker) end
|
||||
end
|
||||
end)
|
||||
ws.close()
|
||||
for i = 0, 15 do term.setPaletteColor(2^i, term.nativePaletteColor(2^i)) end
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.setCursorPos(1, 1)
|
||||
term.clear()
|
Binary file not shown.
42
main.py
42
main.py
@ -5,7 +5,6 @@ import threading
|
||||
import queue
|
||||
import time
|
||||
from ffmpeg_utils import process_ffmpeg
|
||||
from sanjuuni_utils import process_sanjuuni
|
||||
from file_cleanup import start_cleanup_thread
|
||||
|
||||
app = Flask(__name__)
|
||||
@ -20,25 +19,23 @@ enter_parameter_table = {
|
||||
"input_url": str,
|
||||
"output_format": str,
|
||||
"args": list
|
||||
},
|
||||
"sanjuuni": {
|
||||
"input_url": str,
|
||||
"output_format": str,
|
||||
"args": list
|
||||
}
|
||||
}
|
||||
|
||||
# 检测输入参数是否符合要求
|
||||
def validate_request(data,api_name):
|
||||
for key in enter_parameter_table[api_name]:
|
||||
if key not in data and key != "subtitle":
|
||||
if key not in data:
|
||||
logging.warning(f"请求中没有提供{key}参数")
|
||||
return jsonify({'error': f"未提供{key}参数"}), 400
|
||||
if key in data and isinstance(data[key], enter_parameter_table[api_name][key]) == False:
|
||||
if isinstance(data[key], enter_parameter_table[api_name][key]) == False:
|
||||
logging.warning(f"请求中{key}参数类型错误,应为{enter_parameter_table[api_name][key]}")
|
||||
return jsonify({'error': f"{key}参数类型错误,您输入为{type(data[key])},应为{enter_parameter_table[api_name][key]}" }), 400
|
||||
return None, None
|
||||
|
||||
|
||||
|
||||
|
||||
@app.route('/api/ffmpeg', methods=['POST'])
|
||||
def ffmpeg_api():
|
||||
logging.info("收到FFmpeg API请求")
|
||||
@ -71,37 +68,15 @@ def ffmpeg_api():
|
||||
'file_id': result['file_id']
|
||||
}), 200
|
||||
|
||||
|
||||
@app.route('/api/sanjuuni', methods=['POST'])
|
||||
def sanjuuni_api():
|
||||
logging.info("收到Sanjuuni API请求")
|
||||
data = request.get_json()
|
||||
|
||||
# 检测参数类型
|
||||
error_response, status_code = validate_request(data, "sanjuuni")
|
||||
if error_response: return error_response, status_code
|
||||
return jsonify({'status': 'error','error': '暂未开放'}), 403
|
||||
|
||||
|
||||
# 创建处理进程
|
||||
result_queue = queue.Queue()
|
||||
def run_process(data, file_registry, file_lock, result_queue):
|
||||
with app.app_context(): # 设置应用上下文
|
||||
result = process_sanjuuni(data, file_registry, file_lock)
|
||||
result_queue.put(result)
|
||||
|
||||
thread = threading.Thread(target=run_process, args=(data, file_registry, file_lock, result_queue))
|
||||
thread.start()
|
||||
|
||||
# 等待处理结果
|
||||
result = result_queue.get()
|
||||
if 'error' in result:
|
||||
logging.error(f"处理过程中出错: {result['error']}")
|
||||
return jsonify({'status': 'error', 'error': result['error']}), 500
|
||||
else:
|
||||
logging.info(f"处理成功,返回下载URL: {result['download_url']}")
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'download_url': result['download_url'],
|
||||
'file_id': result['file_id']
|
||||
}), 200
|
||||
|
||||
@app.route('/download/<file_id>/<filename>', methods=['GET'])
|
||||
def download_file_endpoint(file_id, filename):
|
||||
@ -122,6 +97,7 @@ def download_file_endpoint(file_id, filename):
|
||||
logging.error(f"下载文件时出错: {e}")
|
||||
return jsonify({'status': 'error','error': str(e)}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
start_cleanup_thread()
|
||||
|
@ -1,146 +0,0 @@
|
||||
import os
|
||||
import uuid
|
||||
import subprocess
|
||||
import requests
|
||||
import tempfile
|
||||
import shutil
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
import time # 导入 time 模块
|
||||
|
||||
@contextmanager
|
||||
def temp_directory(dir=None):
|
||||
temp_dir = tempfile.mkdtemp(dir=dir)
|
||||
try:
|
||||
yield temp_dir
|
||||
finally:
|
||||
pass
|
||||
|
||||
def download_file(url, temp_dir):
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0',
|
||||
'Accept': '*/*'
|
||||
}
|
||||
|
||||
logging.info(f"开始从URL下载文件: {url}")
|
||||
response = requests.get(url, headers=headers, stream=True, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
file_path = os.path.join(temp_dir, 'input_audio')
|
||||
|
||||
with open(file_path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
file_size = os.path.getsize(file_path)
|
||||
if file_size < 1024:
|
||||
error_msg = f"下载的文件太小(仅{file_size}字节),可能无效"
|
||||
logging.error(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
logging.info(f"文件下载成功,保存到: {file_path} (大小: {file_size}字节)")
|
||||
return file_path
|
||||
except Exception as e:
|
||||
logging.error(f"从 {url} 下载文件时出错: {e}")
|
||||
raise
|
||||
|
||||
def execute_sanjuuni(input_path, output_path, sanjuuni_args):
|
||||
try:
|
||||
cmd = ['lib/sanjuuni/sanjuuni.exe', '-i', input_path] + sanjuuni_args + ['-o', output_path]
|
||||
logging.info(f"执行Sanjuuni命令: {' '.join(cmd)}")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
encoding='utf-8',
|
||||
errors='replace'
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
error_msg = f"Sanjuuni处理失败,返回码: {result.returncode}, 错误: {result.stderr}"
|
||||
logging.error(error_msg)
|
||||
raise Exception(error_msg)
|
||||
|
||||
logging.info("Sanjuuni处理成功完成")
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"执行Sanjuuni时出错: {e}")
|
||||
raise
|
||||
|
||||
def process_sanjuuni(data, file_registry, file_lock):
|
||||
try:
|
||||
temp_dir = tempfile.mkdtemp(dir='temp_files')
|
||||
logging.info(f"创建临时目录: {temp_dir}")
|
||||
|
||||
input_url = data.get('input_url')
|
||||
sanjuuni_args = data.get('args', []) # 从请求数据中获取 args 参数
|
||||
|
||||
# 定义不允许的参数
|
||||
disallowed_params = [
|
||||
'-s', '--http=', '-w', '--websocket=', '-u', '--websocket-client=',
|
||||
'-T', '--streamed', '--disable-opencl', '-i', '--input=', '-o', '--output=',
|
||||
'-S', '--subtitles='
|
||||
]
|
||||
|
||||
# 检查是否有不允许的参数
|
||||
for arg in sanjuuni_args:
|
||||
for param in disallowed_params:
|
||||
if arg.startswith(param):
|
||||
error_msg = f"不允许使用参数: {arg}"
|
||||
logging.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
subtitle = data.get('subtitle', '')
|
||||
output_format = data.get('output_format', 'lua')
|
||||
|
||||
# 构建 sanjuuni 参数
|
||||
if 'format' in data:
|
||||
sanjuuni_args.extend(['-f', data['format']])
|
||||
|
||||
if output_format == 'lua':
|
||||
sanjuuni_args.append('-l')
|
||||
elif output_format == 'nfp':
|
||||
sanjuuni_args.append('-n')
|
||||
elif output_format == 'raw':
|
||||
sanjuuni_args.append('-r')
|
||||
elif output_format == 'bimg':
|
||||
sanjuuni_args.append('-b')
|
||||
elif output_format == '32vid':
|
||||
sanjuuni_args.append('-3')
|
||||
else:
|
||||
raise ValueError(f"Unsupported output format: {output_format}")
|
||||
|
||||
input_path = download_file(input_url, temp_dir)
|
||||
|
||||
output_id = str(uuid.uuid4())[:8]
|
||||
output_filename = f"{output_id}.{output_format}"
|
||||
output_path = os.path.join(temp_dir, output_filename)
|
||||
|
||||
execute_sanjuuni(input_path, output_path, sanjuuni_args)
|
||||
|
||||
with file_lock:
|
||||
file_registry[output_id] = {
|
||||
'path': os.path.abspath(output_path),
|
||||
'filename': output_filename,
|
||||
'last_access': time.time(), # 使用 time.time() 记录最后访问时间
|
||||
'download_count': 0
|
||||
}
|
||||
logging.info(f"已注册新文件ID: {output_id}, 路径: {output_path}")
|
||||
|
||||
return {
|
||||
'status': 'success',
|
||||
'download_url': f"http://ffmpeg.liulikeji.cn/download/{output_id}/{output_filename}",
|
||||
'file_id': output_id,
|
||||
'temp_dir': temp_dir # 返回临时目录路径
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"处理过程中出错: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user