From 4263279348f3eb283504bd74fd1be1afb01bc65c Mon Sep 17 00:00:00 2001 From: HKXluo Date: Sun, 4 May 2025 21:39:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0bimg=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bimg/bimg-play.lua | 313 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 bimg/bimg-play.lua diff --git a/bimg/bimg-play.lua b/bimg/bimg-play.lua new file mode 100644 index 0000000..af1cc33 --- /dev/null +++ b/bimg/bimg-play.lua @@ -0,0 +1,313 @@ +-- bimg_player.lua +-- BIMG Player (Shell Version) +-- Usage: bimg_player [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 [options]") + print() + print("Options:") + print(" --loop Loop animation") + print(" --url Load from URL") + print(" --monitor Play on specific monitor (top/bottom/left/right/front/back)") + print(" --scale Set display scale (0.5-3)") + print(" --fps 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(...) \ No newline at end of file