313 lines
8.9 KiB
Lua
313 lines
8.9 KiB
Lua
-- bimg_player.lua
|
|
-- BIMG Player (Shell Version)
|
|
-- Usage: bimg_player <file.bimg> [options]
|
|
|
|
-- Initialize settings
|
|
local running = false
|
|
local currentAnimation = nil
|
|
local httpEnabled = http and http.get
|
|
|
|
-- Show help message
|
|
local function showHelp()
|
|
print("BIMG Player - Usage:")
|
|
print("bimg_player <file.bimg> [options]")
|
|
print()
|
|
print("Options:")
|
|
print(" --loop Loop animation")
|
|
print(" --url Load from URL")
|
|
print(" --monitor <side> Play on specific monitor (top/bottom/left/right/front/back)")
|
|
print(" --scale <num> Set display scale (0.5-3)")
|
|
print(" --fps <num> Set frame rate (1-60)")
|
|
print(" --help Show this help")
|
|
print()
|
|
print("Examples:")
|
|
print("bimg_player animation.bimg --loop")
|
|
print("bimg_player http://example.com/image.bimg --url --monitor top")
|
|
end
|
|
|
|
-- Draw single frame
|
|
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
|
|
|
|
-- Load image file
|
|
local function loadImageFile(path)
|
|
local file, err = fs.open(shell.resolve(path), "rb")
|
|
if not file then error("Cannot open file: " .. err) end
|
|
local data = file.readAll()
|
|
file.close()
|
|
local success, img = pcall(textutils.unserialize, data)
|
|
if not success then error("Invalid BIMG file: " .. img) end
|
|
return img
|
|
end
|
|
|
|
-- Load image from URL
|
|
local function loadImageURL(url)
|
|
if not httpEnabled then
|
|
error("HTTP API not available, cannot load from URL")
|
|
end
|
|
|
|
print("Downloading from URL: "..url)
|
|
local response = http.get(url)
|
|
if not response then
|
|
error("Download failed: " .. url)
|
|
end
|
|
|
|
local data = response.readAll()
|
|
response.close()
|
|
local success, img = pcall(textutils.unserialize, data)
|
|
if not success then error("Invalid BIMG data: " .. img) end
|
|
return img
|
|
end
|
|
|
|
-- Monitor calibration
|
|
local function calibrateMonitors(width, height)
|
|
term.clear()
|
|
term.setCursorPos(1, 1)
|
|
print("Multi-Monitor Calibration Mode")
|
|
print("Please right-click each monitor in order")
|
|
print("From top-left to bottom-right, left to right first")
|
|
|
|
local monitors = {}
|
|
local names = {}
|
|
|
|
for y = 1, height do
|
|
monitors[y] = {}
|
|
for x = 1, width do
|
|
local _, oy = term.getCursorPos()
|
|
|
|
-- Draw calibration UI
|
|
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(string.format("Position (%d, %d)", x, y))
|
|
term.setCursorPos(1, oy)
|
|
|
|
-- Wait for monitor click
|
|
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('bimg_player.multimonitor', monitors)
|
|
settings.save()
|
|
print("Calibration complete. Settings saved.")
|
|
|
|
return monitors
|
|
end
|
|
|
|
-- Stop playback
|
|
local function stopPlayback()
|
|
if currentAnimation then
|
|
currentAnimation.running = false
|
|
os.queueEvent("bimg_stop")
|
|
end
|
|
running = false
|
|
end
|
|
|
|
-- Clean up terminal
|
|
local function cleanupTerminal(termObj)
|
|
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
|
|
|
|
-- Main playback function
|
|
local function playAnimation(img, options)
|
|
options = options or {}
|
|
local termObj = options.terminal or term
|
|
local loop = options.loop or false
|
|
local fps = options.fps or (img.secondsPerFrame and (1/img.secondsPerFrame)) or 20
|
|
local frameDelay = 1/fps
|
|
|
|
-- Set running state
|
|
running = true
|
|
currentAnimation = {
|
|
running = true,
|
|
loop = loop
|
|
}
|
|
|
|
-- Playback function
|
|
local function playFrames()
|
|
repeat
|
|
if img.multiMonitor then
|
|
local width, height = img.multiMonitor.width, img.multiMonitor.height
|
|
local monitors = settings.get('bimg_player.multimonitor')
|
|
|
|
-- Calibrate if needed
|
|
if not monitors or #monitors < height or #monitors[1] < width then
|
|
monitors = calibrateMonitors(width, height)
|
|
end
|
|
|
|
-- Multi-monitor playback
|
|
for i = 1, #img, width * height do
|
|
if 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
|
|
|
|
sleep(frameDelay)
|
|
end
|
|
else
|
|
-- Single monitor playback
|
|
termObj.clear()
|
|
for _, frame in ipairs(img) do
|
|
if not currentAnimation.running then break end
|
|
drawFrame(frame, termObj)
|
|
sleep(frameDelay)
|
|
end
|
|
end
|
|
until not loop or not currentAnimation.running
|
|
end
|
|
|
|
-- Start playback
|
|
parallel.waitForAny(
|
|
function()
|
|
local ok, err = pcall(playFrames)
|
|
if not ok then printError("Playback error: "..err) end
|
|
cleanupTerminal(termObj)
|
|
running = false
|
|
end,
|
|
function()
|
|
while currentAnimation.running do
|
|
os.pullEvent("bimg_stop")
|
|
currentAnimation.running = false
|
|
end
|
|
end
|
|
)
|
|
end
|
|
|
|
-- Parse command line arguments
|
|
local function parseArguments(args)
|
|
local options = {}
|
|
local path = nil
|
|
|
|
local i = 1
|
|
while i <= #args do
|
|
local arg = args[i]
|
|
if arg == "--loop" then
|
|
options.loop = true
|
|
elseif arg == "--url" then
|
|
options.isURL = true
|
|
elseif arg == "--monitor" then
|
|
i = i + 1
|
|
options.terminal = peripheral.wrap(args[i])
|
|
if not options.terminal then
|
|
error("Invalid monitor side: " .. (args[i] or "nil"))
|
|
end
|
|
elseif arg == "--scale" then
|
|
i = i + 1
|
|
local scale = tonumber(args[i])
|
|
if scale and scale >= 0.5 and scale <= 3 then
|
|
options.scale = scale
|
|
else
|
|
error("Invalid scale value (should be 0.5-3)")
|
|
end
|
|
elseif arg == "--fps" then
|
|
i = i + 1
|
|
local fps = tonumber(args[i])
|
|
if fps and fps >= 1 and fps <= 60 then
|
|
options.fps = fps
|
|
else
|
|
error("Invalid FPS value (should be 1-60)")
|
|
end
|
|
elseif arg == "--help" then
|
|
showHelp()
|
|
return nil
|
|
elseif not path and not arg:find("^-") then
|
|
path = arg
|
|
else
|
|
error("Unknown option: " .. arg)
|
|
end
|
|
i = i + 1
|
|
end
|
|
|
|
if not path then
|
|
showHelp()
|
|
return nil
|
|
end
|
|
|
|
return path, options
|
|
end
|
|
|
|
-- Main program entry
|
|
local function main(...)
|
|
local args = {...}
|
|
|
|
-- Show help if no arguments
|
|
if #args == 0 then
|
|
showHelp()
|
|
return
|
|
end
|
|
|
|
-- Parse arguments
|
|
local ok, path, options = pcall(parseArguments, args)
|
|
if not ok then
|
|
printError(path) -- Here path contains the error message
|
|
showHelp()
|
|
return
|
|
end
|
|
|
|
if not path then return end -- User requested help
|
|
|
|
-- Load image
|
|
local img
|
|
if options.isURL then
|
|
img = loadImageURL(path)
|
|
else
|
|
img = loadImageFile(path)
|
|
end
|
|
|
|
-- Apply display scale
|
|
if options.scale and img.multiMonitor then
|
|
img.multiMonitor.scale = options.scale
|
|
end
|
|
|
|
-- Start playback
|
|
playAnimation(img, options)
|
|
|
|
-- Wait for keypress if not looping
|
|
if not options.loop then
|
|
stopPlayback()
|
|
end
|
|
end
|
|
|
|
-- Start program
|
|
main(...) |