修改demo2
This commit is contained in:
parent
d6ac4f00e7
commit
c43c51b779
BIN
Demo/SanjuuniApi-Demo/demo.mp4
Normal file
BIN
Demo/SanjuuniApi-Demo/demo.mp4
Normal file
Binary file not shown.
@ -1,67 +1,187 @@
|
||||
-- API 地址 / API Endpoint
|
||||
-- sanjuuni_converter.lua
|
||||
-- 使用Sanjuuni API的图像转换工具
|
||||
-- Usage: sanjuuni_converter --url <input_url> --format <output_format> --out <output_file> [options]
|
||||
|
||||
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 showHelp()
|
||||
print("Sanjuuni Image Converter - Usage:")
|
||||
print("sanjuuni_converter --url <input_url> --format <output_format> --out <output_file> [options]")
|
||||
print()
|
||||
print("Required arguments:")
|
||||
print(" --url <url> Input image URL")
|
||||
print(" --format <fmt> Output format (bimg, gif, etc.)")
|
||||
print(" --out <file> Output file path")
|
||||
print()
|
||||
print("Options:")
|
||||
print(" --args <params> Conversion parameters (default: \"-8\")")
|
||||
print(" --monitor <side> Use specified monitor for size reference")
|
||||
print(" --scale <num> Set display scale before getting monitor size")
|
||||
print(" --help Show this help message")
|
||||
print()
|
||||
print("Example:")
|
||||
print("sanjuuni_converter --url https://example.com/image.jpg --format bimg --out output.bimg")
|
||||
print("sanjuuni_converter --url https://example.com/image.jpg --format gif --out anim.gif --monitor top --scale 0.5")
|
||||
end
|
||||
|
||||
|
||||
local function Get_image_url(INPUT_URL,API_URL,args,output_format)
|
||||
-- ===== 1. 发送HTTP请求 / Send HTTP request =====
|
||||
-- 获取图像下载URL
|
||||
local function getImageUrl(inputUrl, args, outputFormat)
|
||||
-- 准备请求数据
|
||||
local requestData = {
|
||||
input_url = INPUT_URL,
|
||||
input_url = inputUrl,
|
||||
args = args,
|
||||
output_format = output_format
|
||||
output_format = outputFormat
|
||||
}
|
||||
|
||||
print("Sending request to Sanjuuni API...")
|
||||
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()
|
||||
if not response then
|
||||
error("HTTP request failed: "..(err or "unknown error"))
|
||||
end
|
||||
|
||||
--返回下载链接 / Return download URL
|
||||
if responseData.status ~= "success" then error("Conversion failed:"..(responseData.error or "Unknown error"))
|
||||
-- 解析响应数据
|
||||
local responseData = textutils.unserializeJSON(response.readAll())
|
||||
response.close()
|
||||
|
||||
if responseData.status ~= "success" then
|
||||
error("Conversion failed: "..(responseData.error or "unknown error"))
|
||||
end
|
||||
|
||||
print("Conversion successful!")
|
||||
return responseData.download_url
|
||||
end
|
||||
|
||||
-- 下载文件到本地
|
||||
local function downloadFile(url, outputPath)
|
||||
print("Downloading converted file...")
|
||||
local response = http.get(url)
|
||||
if not response then
|
||||
error("Failed to download file from: "..url)
|
||||
end
|
||||
|
||||
local file = fs.open(outputPath, "wb")
|
||||
if not file then
|
||||
error("Cannot open output file: "..outputPath)
|
||||
end
|
||||
|
||||
file.write(response.readAll())
|
||||
file.close()
|
||||
response.close()
|
||||
|
||||
print("File saved to: "..outputPath)
|
||||
end
|
||||
|
||||
-- 解析命令行参数
|
||||
local function parseArguments(args)
|
||||
local options = {
|
||||
args = {} -- 默认参数
|
||||
}
|
||||
local required = {"url", "format", "out"}
|
||||
local seen = {}
|
||||
|
||||
local i = 1
|
||||
while i <= #args do
|
||||
local arg = args[i]
|
||||
if arg == "--url" then
|
||||
i = i + 1
|
||||
options.url = args[i]
|
||||
seen.url = true
|
||||
elseif arg == "--format" then
|
||||
i = i + 1
|
||||
options.format = args[i]
|
||||
seen.format = true
|
||||
elseif arg == "--out" then
|
||||
i = i + 1
|
||||
options.out = args[i]
|
||||
seen.out = true
|
||||
elseif arg == "--args" then
|
||||
i = i + 1
|
||||
options.args = {args[i]} -- 简单参数处理
|
||||
elseif arg == "--monitor" then
|
||||
i = i + 1
|
||||
options.monitor = args[i]
|
||||
elseif arg == "--scale" then
|
||||
i = i + 1
|
||||
options.scale = tonumber(args[i])
|
||||
elseif arg == "--help" then
|
||||
showHelp()
|
||||
return nil
|
||||
else
|
||||
return responseData.download_url
|
||||
error("Unknown option: "..arg)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- 检查必需参数
|
||||
for _, req in ipairs(required) do
|
||||
if not seen[req] then
|
||||
error("Missing required argument: --"..req)
|
||||
end
|
||||
end
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
-- 主函数
|
||||
local function main(...)
|
||||
local args = {...}
|
||||
|
||||
if #args == 0 then
|
||||
showHelp()
|
||||
return
|
||||
end
|
||||
|
||||
-- 图片宽度 / Image width
|
||||
local width, height = term.getSize() -- 获取终端尺寸(width=宽度,height=高度)
|
||||
local width = width * 2 -- 宽度×2
|
||||
local height = height * 3 -- 高度×3
|
||||
-- 解析参数
|
||||
local ok, options = pcall(parseArguments, args)
|
||||
if not ok then
|
||||
printError(options) -- options contains error message
|
||||
showHelp()
|
||||
return
|
||||
end
|
||||
|
||||
-- 转换参数 / Conversion parameters
|
||||
args = { "-8","--width="..width,"--height="..height }
|
||||
output_format = "bimg"
|
||||
if not options then return end -- 用户请求帮助
|
||||
|
||||
-- 调用API / Call API
|
||||
local download_url = Get_image_url(INPUT_URL,API_URL,args,output_format)
|
||||
-- 获取终端/显示器尺寸
|
||||
local width, height
|
||||
if options.monitor then
|
||||
local monitor = peripheral.wrap(options.monitor)
|
||||
if not monitor then
|
||||
error("Invalid monitor: "..options.monitor)
|
||||
end
|
||||
|
||||
if options.scale then
|
||||
monitor.setTextScale(options.scale)
|
||||
end
|
||||
|
||||
width, height = monitor.getSize()
|
||||
else
|
||||
width, height = term.getSize()
|
||||
end
|
||||
|
||||
-- 计算最终尺寸
|
||||
width = width * 2
|
||||
height = height * 3
|
||||
|
||||
-- 下载图像文件 / Download image file
|
||||
print("\nConversion successful! Download URL:")
|
||||
print(download_url)
|
||||
-- 准备转换参数
|
||||
local conversionArgs = options.args
|
||||
table.insert(conversionArgs, "--width="..width)
|
||||
table.insert(conversionArgs, "--height="..height)
|
||||
|
||||
luafile = http.get(download_url)
|
||||
if luafile then
|
||||
print("\nLua file downloaded successfully.")
|
||||
-- 调用API转换图像
|
||||
local downloadUrl = getImageUrl(options.url, conversionArgs, options.format)
|
||||
|
||||
-- 保存bimg文件 / Save bimg file
|
||||
f = fs.open("demo.bimg", "w")
|
||||
f.write(luafile.readAll())
|
||||
f.close()
|
||||
print("\nLua file saved as demo.bimg.")
|
||||
-- 下载转换后的文件
|
||||
downloadFile(downloadUrl, options.out)
|
||||
|
||||
else
|
||||
print("\nFailed to download Lua file.")
|
||||
print("Image conversion completed successfully!")
|
||||
end
|
||||
|
||||
-- 运行程序
|
||||
main(...)
|
@ -1,261 +0,0 @@
|
||||
-- 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 = ("<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,294 +0,0 @@
|
||||
-- bimg_advanced.lua
|
||||
-- 增强版 BIMG 图像/动画播放库
|
||||
-- 功能:
|
||||
-- 1. 支持文件路径、URL 和直接数据播放
|
||||
-- 2. 支持循环播放
|
||||
-- 3. 提供播放控制功能
|
||||
-- 4. 支持多显示器配置
|
||||
-- 5. 支持自定义终端对象
|
||||
|
||||
local bimg = {}
|
||||
|
||||
local currentAnimation = nil
|
||||
local running = false
|
||||
local httpEnabled = false
|
||||
|
||||
-- 检查并启用HTTP功能
|
||||
if http and http.get then
|
||||
httpEnabled = true
|
||||
end
|
||||
|
||||
-- 内部函数:绘制单帧
|
||||
local function drawFrame(frame, termObj)
|
||||
for y, row in ipairs(frame) do
|
||||
termObj.setCursorPos(1, y)
|
||||
termObj.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
|
||||
termObj.setPaletteColor(2^i, table.unpack(c))
|
||||
else
|
||||
termObj.setPaletteColor(2^i, c)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 内部函数:加载图像数据
|
||||
local function loadImageData(data)
|
||||
if type(data) == "string" then
|
||||
-- 如果是字符串,尝试反序列化
|
||||
local success, result = pcall(textutils.unserialize, data)
|
||||
if success then
|
||||
return result
|
||||
else
|
||||
error("Invalid image data: " .. result)
|
||||
end
|
||||
elseif type(data) == "table" then
|
||||
-- 如果是table,直接使用
|
||||
return data
|
||||
else
|
||||
error("Unsupported image data type")
|
||||
end
|
||||
end
|
||||
|
||||
-- 从文件加载图像
|
||||
function bimg.loadFromFile(path)
|
||||
local file, err = fs.open(shell.resolve(path), "rb")
|
||||
if not file then error("Could not open file: " .. err) end
|
||||
local data = file.readAll()
|
||||
file.close()
|
||||
return loadImageData(data)
|
||||
end
|
||||
|
||||
-- 从URL加载图像
|
||||
function bimg.loadFromURL(url)
|
||||
if not httpEnabled then
|
||||
error("HTTP API is not available")
|
||||
end
|
||||
|
||||
local response = http.get(url)
|
||||
if not response then
|
||||
error("Failed to fetch from URL: " .. url)
|
||||
end
|
||||
|
||||
local data = response.readAll()
|
||||
response.close()
|
||||
return loadImageData(data)
|
||||
end
|
||||
|
||||
-- 内部函数:校准多显示器
|
||||
local function calibrateMonitors(width, height)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print('This image needs monitors to be calibrated before being displayed.')
|
||||
print('Please right-click each monitor in order, from the top left corner')
|
||||
print('to the bottom right corner, going right first, then down.')
|
||||
|
||||
local 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.')
|
||||
|
||||
return monitors
|
||||
end
|
||||
|
||||
-- 播放图像/动画
|
||||
function bimg.play(source, options)
|
||||
options = options or {}
|
||||
local termObj = options.terminal or term
|
||||
local loop = options.loop or false
|
||||
local isURL = options.isURL or false
|
||||
|
||||
-- 停止当前播放
|
||||
bimg.stop()
|
||||
|
||||
-- 加载图像数据
|
||||
local img
|
||||
if type(source) == "string" then
|
||||
if isURL then
|
||||
img = bimg.loadFromURL(source)
|
||||
else
|
||||
img = bimg.loadFromFile(source)
|
||||
end
|
||||
else
|
||||
img = loadImageData(source)
|
||||
end
|
||||
|
||||
-- 设置运行标志
|
||||
running = true
|
||||
currentAnimation = {
|
||||
img = img,
|
||||
term = termObj,
|
||||
running = true,
|
||||
loop = loop
|
||||
}
|
||||
|
||||
local function playFrames()
|
||||
repeat
|
||||
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
|
||||
monitors = calibrateMonitors(width, height)
|
||||
end
|
||||
|
||||
-- 播放多显示器动画
|
||||
for i = 1, #img, width * height do
|
||||
if not currentAnimation or not currentAnimation.running then break end
|
||||
|
||||
-- 在所有显示器上绘制当前帧
|
||||
for y = 1, height do
|
||||
for x = 1, width do
|
||||
local frameIndex = i + (y-1) * width + x-1
|
||||
if frameIndex <= #img then
|
||||
local monitor = peripheral.wrap(monitors[y][x])
|
||||
if monitor then
|
||||
drawFrame(img[frameIndex], monitor)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 等待帧间隔
|
||||
if img.animation then
|
||||
sleep(img[i].duration or img.secondsPerFrame or 0.05)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
-- 单显示器播放
|
||||
termObj.clear()
|
||||
|
||||
-- 播放每一帧
|
||||
for _, frame in ipairs(img) do
|
||||
if not currentAnimation or not currentAnimation.running then break end
|
||||
|
||||
drawFrame(frame, termObj)
|
||||
|
||||
-- 等待帧间隔
|
||||
if img.animation then
|
||||
sleep(frame.duration or img.secondsPerFrame or 0.05)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 如果不是循环播放,则退出
|
||||
if not currentAnimation or not currentAnimation.loop then
|
||||
break
|
||||
end
|
||||
|
||||
until not currentAnimation or not currentAnimation.running
|
||||
end
|
||||
|
||||
-- 播放结束后的清理
|
||||
local function cleanup()
|
||||
if currentAnimation and currentAnimation.running then
|
||||
local termObj = currentAnimation.term
|
||||
|
||||
-- 重置终端状态
|
||||
termObj.setBackgroundColor(colors.black)
|
||||
termObj.setTextColor(colors.white)
|
||||
termObj.clear()
|
||||
termObj.setCursorPos(1, 1)
|
||||
|
||||
-- 重置调色板
|
||||
for i = 0, 15 do
|
||||
termObj.setPaletteColor(2^i, term.nativePaletteColor(2^i))
|
||||
end
|
||||
end
|
||||
|
||||
running = false
|
||||
currentAnimation = nil
|
||||
end
|
||||
|
||||
-- 在新线程中播放
|
||||
parallel.waitForAny(
|
||||
function()
|
||||
local success, err = pcall(playFrames)
|
||||
if not success then
|
||||
printError("Error playing animation: " .. err)
|
||||
end
|
||||
cleanup()
|
||||
end,
|
||||
function()
|
||||
while currentAnimation and currentAnimation.running do
|
||||
os.pullEvent("bimg_stop")
|
||||
currentAnimation.running = false
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
-- 停止播放
|
||||
function bimg.stop()
|
||||
if currentAnimation then
|
||||
currentAnimation.running = false
|
||||
os.queueEvent("bimg_stop")
|
||||
end
|
||||
running = false
|
||||
end
|
||||
|
||||
-- 检查是否正在播放
|
||||
function bimg.isPlaying()
|
||||
return running
|
||||
end
|
||||
|
||||
-- 获取当前播放状态
|
||||
function bimg.getStatus()
|
||||
if not currentAnimation then
|
||||
return {
|
||||
playing = false,
|
||||
looping = false,
|
||||
frameCount = 0,
|
||||
currentFrame = 0
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
playing = currentAnimation.running,
|
||||
looping = currentAnimation.loop,
|
||||
frameCount = #currentAnimation.img,
|
||||
currentFrame = currentAnimation.currentFrame or 0
|
||||
}
|
||||
end
|
||||
|
||||
return bimg
|
@ -1,83 +0,0 @@
|
||||
-- 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)
|
Loading…
x
Reference in New Issue
Block a user