local component=require("component") local computer=require("computer") local event=require("event") local fs=require("filesystem") local shell=require("shell") local term=require("term") local gpu=component.gpu local byte,concat,floor=string.byte,table.concat,math.floor local uptime=computer.uptime local HEADER_SIZE=64 local MAX_SKIP=4 local RAM_SHARE=0.4 local REFILL=3 local KEY_SPACE,KEY_Q=32,113 local OP_BG,OP_FG,OP_BOTH,OP_TEXT=1,2,3,4 local function u16(s,o)return byte(s,o)*0x100+byte(s,o+1)end local function u32(s,o)return u16(s,o)*0x10000+u16(s,o+2)end local function fail(msg) error(msg,0) end local function buildColorTable(header) local rgb={} for i=0,15 do local o=17+i*3 rgb[i]=byte(header,o)*0x10000+byte(header,o+1)*0x100+byte(header,o+2) end for i=0,239 do local r=floor(floor(i/40)*255/5+0.5) local g=floor(floor(i/5)%8*255/7+0.5) local b=floor(i%5*255/4+0.5) rgb[16+i]=r*0x10000+g*0x100+b end return rgb end local function buildGlyphTable() local chars={[0]=" ","▄","▀","█"} local t={} for n=1,4 do t[n]={} for b=0,255 do local s={} for i=1,n do s[i]=chars[floor(b/4^(4-i))%4] end t[n][b]=concat(s) end end return t end local function readIndex(dir) local f,err=io.open(fs.concat(dir,"index.ocv"),"rb") if not f then fail("cannot open index.ocv: "..tostring(err))end local h=f:read(HEADER_SIZE) f:close() if not h or#hvideo.parts then return nil end local path=fs.concat(video.dir,string.format("part%04d.ocv",part)) local err file,err=io.open(path,"rb") if not file then fail("cannot open "..path..": "..tostring(err))end end local head4=file:read(4) if head4 and#head4==4 then local len=u32(head4,1) if len==0 then return""end local data=file:read(len) if not data or#datatail then stream.fill(1)end if head>tail then return nil end local data=queue[head] queue[head]=nil head=head+1 bytes=bytes-#data return data end function stream.buffered()return tail-head+1,bytes end function stream.close() if file then file:close();file=nil end queue,head,tail,bytes={},1,0,0 end return stream end local Display={} Display.__index=Display function Display.open(w,h,palette) if gpu.maxDepth()<8 then fail("нужен GPU и экран Tier 3")end local mw,mh=gpu.maxResolution() if mw0 then cnt=cnt+1 parts[cnt]=glyphs[rest][byte(data,q+full)] end set(x,y,concat(parts,"",1,cnt)) p=q+cnt sets=sets+1 end elseif op==OP_BOTH then setBg(colors[byte(data,p+1)]) setFg(colors[byte(data,p+2)]) p=p+3 elseif op==OP_BG then setBg(colors[byte(data,p+1)]) p=p+2 elseif op==OP_FG then setFg(colors[byte(data,p+1)]) p=p+2 else error(string.format("corrupted frame: op %d at %d",op,p),0) end end return sets end end local QUIT,PAUSE="quit","pause" local function poll(timeout) local name,_,char=event.pull(timeout) if name=="interrupted"then return QUIT end if name=="key_down"then if char==KEY_Q then return QUIT end if char==KEY_SPACE then return PAUSE end end end local function waitUntil(deadline) repeat local cmd=poll(math.max(0,deadline-uptime())) if cmd then return cmd end until uptime()>=deadline end local function play(video,display,render,stats) local capacity=math.max(64*1024,floor(computer.freeMemory()*RAM_SHARE)) local stream=openStream(video,capacity) stream.fill(math.huge,8) local frames0,bytes0=stream.buffered() stats.preload=stats.preload or string.format("%d кадров / %d КБ",frames0,floor(bytes0/1024)) local interval=1/video.fps local t0=uptime() local skipped=0 local ok,result=pcall(function() for k=0,video.frames-1 do local data=stream.next() if not data then return end local c0=os.clock() stats.sets=stats.sets+render(data) stats.cpu=stats.cpu+(os.clock()-c0) stats.drawn=stats.drawn+1 local due=t0+k*interval local now=uptime() local cmd if display.buffer and now>due+interval and skipped10 and{fps,10}or{fps} local printed=false for _,f in ipairs(targets)do local budget=floor(HEADROOM/f/perSet/50)*50 if budget>=100 and budget [-l]") print(" -l зациклить | Space — пауза, Q / Ctrl+C — выход") return end dir=shell.resolve(dir) if not fs.isDirectory(dir)then fail("папка не найдена: "..dir)end local video=readIndex(dir) local colors=buildColorTable(video.header) local render=makeRenderer(colors,buildGlyphTable()) local display=Display.open(video.width,video.height,colors) local stats={drawn=0,late=0,cpu=0,sets=0} local started=uptime() local ok,err=pcall(function() repeat if play(video,display,render,stats)==QUIT then break end if opts.l then display:clear()end until not opts.l end) display:close() if not ok and err~="interrupted"then fail(err)end local elapsed=math.max(uptime()-started,1e-6) print(string.format("%d кадров за %.1f с (%.1f fps), пропущено выводов: %d, CPU на кадр: %.1f мс", stats.drawn,elapsed,(stats.drawn-stats.late)/elapsed,stats.late, stats.cpu/math.max(stats.drawn,1)*1000)) print("Буфер ОЗУ при старте: "..tostring(stats.preload)) if stats.late>stats.drawn/10 then recommend(stats,video.fps)end end local ok,err=pcall(main,...) if not ok then io.stderr:write("ocv: "..tostring(err).."\n") end