local component = require("component") local fs = require("filesystem") local term = require("term") local computer = require("computer") if not component.isAvailable("internet") then io.stderr:write("internet component is not available\n") return end local internet = component.internet local args = {...} local baseUrl = args[1] or "http://192.168.1.103/video/" local outDir = args[2] or "video" if baseUrl:sub(-1) ~= "/" then baseUrl = baseUrl .. "/" end local function u32be(s, off) return s:byte(off) * 0x1000000 + s:byte(off+1) * 0x10000 + s:byte(off+2) * 0x100 + s:byte(off+3) end local function formatBytes(n) if n >= 1024 * 1024 then return string.format("%.2f MB", n / 1024 / 1024) end if n >= 1024 then return string.format("%.1f KB", n / 1024) end return tostring(n) .. " B" end local function ensureDir(path) if not fs.exists(path) then fs.makeDirectory(path) end end local function download(url, path, label, index, total) print(string.format("[%d/%d] Requesting %s", index, total, url)) local handle, err = internet.request(url) if not handle then return false, tostring(err) end if type(handle) ~= "function" and handle.finishConnect then local connected = false local started = computer.uptime() while computer.uptime() - started < 10 do local ok, result = pcall(handle.finishConnect) if ok and result then connected = true break end computer.pullSignal(0.05) end if not connected then pcall(handle.close) return false, "connection timeout" end end local file, ferr = io.open(path, "wb") if not file then if type(handle) ~= "function" then pcall(handle.close) end return false, tostring(ferr) end local downloaded = 0 local t0 = computer.uptime() local lastDraw = 0 local lastData = computer.uptime() while true do local chunk if type(handle) == "function" then chunk = handle() elseif handle.read then chunk = handle.read() else file:close() return false, "unsupported internet response handle" end if not chunk then if type(handle) == "function" then break end if downloaded > 0 and computer.uptime() - lastData > 1 then break end if downloaded == 0 and computer.uptime() - t0 > 10 then file:close() pcall(handle.close) return false, "empty response timeout" end computer.pullSignal(0.05) else file:write(chunk) downloaded = downloaded + #chunk lastData = computer.uptime() end local now = computer.uptime() if now - lastDraw >= 0.2 then lastDraw = now local _, y = term.getCursor() term.setCursor(1, y) term.clearLine() io.write(string.format("[%d/%d] %s: %s", index, total, label, formatBytes(downloaded))) io.flush() computer.pullSignal(0) end end file:close() if type(handle) ~= "function" then pcall(handle.close) end if downloaded <= 0 then if fs.exists(path) then fs.remove(path) end return false, "empty response" end local dt = computer.uptime() - t0 local _, y = term.getCursor() term.setCursor(1, y) term.clearLine() print(string.format("[%d/%d] %s: %s in %.1fs", index, total, label, formatBytes(downloaded), dt)) return true, nil end ensureDir(outDir) term.clear() print("OCV video downloader") print("URL : " .. baseUrl) print("DIR : " .. outDir) print("") local indexPath = fs.concat(outDir, "index.ocv") local ok, err = download(baseUrl .. "index.ocv", indexPath, "index.ocv", 1, 1) if not ok then io.stderr:write("download failed: " .. tostring(err) .. "\n") return end local f = io.open(indexPath, "rb") if not f then io.stderr:write("cannot open downloaded index\n") return end local header = f:read(32) f:close() local magic = header and header:sub(1, 4) or "" if not header or #header < 32 or (magic ~= "OCV3" and magic ~= "OCV4") then io.stderr:write("bad index.ocv: expected OCV3/OCV4\n") return end local screenH = u32be(header, 5) local screenW = u32be(header, 9) local fps = u32be(header, 13) local frameSize = u32be(header, 17) local frameCount = u32be(header, 21) local chunkCount = u32be(header, 25) local framesPerChunk = u32be(header, 29) local codec = magic == "OCV4" and "delta" or "raw" print("") print(string.format("Format: %s (%s)", magic, codec)) print(string.format("Video : %dx%d chars, %d fps", screenW, screenH, fps)) print(string.format("Frames: %d, chunks: %d, frame size: %s, chunk frames: %d", frameCount, chunkCount, formatBytes(frameSize), framesPerChunk)) print("") for i = 1, chunkCount do local name = string.format("part%04d.ocv", i) local partOk, partErr = download(baseUrl .. name, fs.concat(outDir, name), name, i + 1, chunkCount + 1) if not partOk then io.stderr:write("download failed: " .. name .. ": " .. tostring(partErr) .. "\n") return end end print("") print("Done. Run: player " .. outDir)