diff --git a/Demo/SanjuuniApi-Demo/demo.gif b/Demo/SanjuuniApi-Demo/demo.gif new file mode 100644 index 0000000..b52d410 Binary files /dev/null and b/Demo/SanjuuniApi-Demo/demo.gif differ diff --git a/Demo/SanjuuniApi-Demo/demo1.lua b/Demo/SanjuuniApi-Demo/demo1.lua index d4d66b5..8832160 100644 --- a/Demo/SanjuuniApi-Demo/demo1.lua +++ b/Demo/SanjuuniApi-Demo/demo1.lua @@ -5,12 +5,12 @@ local API_URL = "http://newgmapi.liulikeji.cn/api/sanjuuni" local INPUT_URL = "https://git.liulikeji.cn/xingluo/CCTweaked-Demo/raw/branch/main/Demo/SanjuuniApi-Demo/demo.jpg" -local function Get_image_url(INPUT_URL,API_URL,width,height) +local function Get_image_url(INPUT_URL,API_URL,args,output_format) -- ===== 1. 发送HTTP请求 / Send HTTP request ===== local requestData = { input_url = INPUT_URL, - args = { "-8","--width="..width,"--height="..height }, -- sanjuuni转换参数 / sanjuuni conversion args - output_format = "lua" + args = args, + output_format = output_format } local response, err = http.post( @@ -34,17 +34,24 @@ local function Get_image_url(INPUT_URL,API_URL,width,height) end end + +-- 图片宽度 / Image width local width, height = term.getSize() -- 获取终端尺寸(width=宽度,height=高度) local width = width * 2 -- 宽度×2 local height = height * 3 -- 高度×3 +-- 转换参数 / Conversion parameters +args = { "-8","--width="..width,"--height="..height } +output_format = "lua" -local download_url = Get_image_url(INPUT_URL,API_URL,width,height) +-- 调用API / Call API +local download_url = Get_image_url(INPUT_URL,API_URL,args,output_format) --- ===== 显示图片 / Display image ===== +-- 下载图像文件 / Download image file print("\nConversion successful! Download URL:") print(download_url) + luafile = http.get(download_url) if luafile then print("\nLua file downloaded successfully.") diff --git a/Demo/SanjuuniApi-Demo/demo2.lua b/Demo/SanjuuniApi-Demo/demo2.lua new file mode 100644 index 0000000..47ad1be --- /dev/null +++ b/Demo/SanjuuniApi-Demo/demo2.lua @@ -0,0 +1,67 @@ +-- API 地址 / API Endpoint +local API_URL = "http://newgmapi.liulikeji.cn/api/sanjuuni" + +-- 输入图片URL / Input image URL +local INPUT_URL = "https://git.liulikeji.cn/xingluo/CCTweaked-Demo/raw/branch/main/Demo/SanjuuniApi-Demo/demo.gif" + + +local function Get_image_url(INPUT_URL,API_URL,args,output_format) + -- ===== 1. 发送HTTP请求 / Send HTTP request ===== + local requestData = { + input_url = INPUT_URL, + args = args, + output_format = output_format + } + + local response, err = http.post( + API_URL, + textutils.serializeJSON(requestData), + { ["Content-Type"] = "application/json" } + ) + + -- ===== 2. 读取数据 / Send HTTP request ===== + if not response then error("HTTP Request Failure: "..(err or "Unknown error")) + else + -- 读取响应 / Read response + local responseData = textutils.unserializeJSON(response.readAll()) + response.close() + + --返回下载链接 / Return download URL + if responseData.status ~= "success" then error("Conversion failed:"..(responseData.error or "Unknown error")) + else + return responseData.download_url + end + end +end + + +-- 图片宽度 / Image width +local width, height = term.getSize() -- 获取终端尺寸(width=宽度,height=高度) +local width = width * 2 -- 宽度×2 +local height = height * 3 -- 高度×3 + +-- 转换参数 / Conversion parameters +args = { "-8","--width="..width,"--height="..height } +output_format = "bimg" + +-- 调用API / Call API +local download_url = Get_image_url(INPUT_URL,API_URL,args,output_format) + + +-- 下载图像文件 / Download image file +print("\nConversion successful! Download URL:") +print(download_url) + +luafile = http.get(download_url) +if luafile then + print("\nLua file downloaded successfully.") + + -- 保存Lua文件 / Save Lua file + f = fs.open("demo.bimg", "w") + f.write(luafile.readAll()) + f.close() + print("\nLua file saved as demo.bimg.") + +else + print("\nFailed to download Lua file.") +end diff --git a/Demo/SanjuuniApi-Demo/lib/32vid-player-mini.lua b/Demo/SanjuuniApi-Demo/lib/32vid-player-mini.lua new file mode 100644 index 0000000..ef6e9c7 --- /dev/null +++ b/Demo/SanjuuniApi-Demo/lib/32vid-player-mini.lua @@ -0,0 +1,261 @@ +-- 32vid-player-mini from sanjuuni +-- Licensed under the MIT license + +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 = ("= 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 = (" 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 = ("= 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 = ("= 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 = ("= 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 = ("") 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 diff --git a/Demo/SanjuuniApi-Demo/lib/raw-player.lua b/Demo/SanjuuniApi-Demo/lib/raw-player.lua new file mode 100644 index 0000000..b866708 --- /dev/null +++ b/Demo/SanjuuniApi-Demo/lib/raw-player.lua @@ -0,0 +1,83 @@ +-- raw-player from sanjuuni +-- Licensed under the MIT license + +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) \ No newline at end of file