local fs=require("filesystem") local byte,concat,floor=string.byte,table.concat,math.floor local L={} local HEADER_SIZE=64 local OP_BG,OP_FG,OP_BOTH,OP_TEXT=1,2,3,4 L.CODES={[0]=0x20,0x2584,0x2580,0x2588} 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 function L.isVideo(dir)return fs.exists(fs.concat(dir,"index.ocv"))end function L.readIndex(dir) local f,err=io.open(fs.concat(dir,"index.ocv"),"rb") if not f then return nil,"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 error("cannot open "..path..": "..tostring(err),0)end end local h4=file:read(4) if h4 and#h4==4 then local len=u32(h4,1) if len==0 then return""end local data=file:read(len) if not data or#datatail then s.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 s.buffered()return tail-head+1,bytes end function s.close() if file then file:close();file=nil end queue,head,tail,bytes={},1,0,0 end return s end function L.newGrid(w,h,black) local g={} local v=0x20|(black<<21)|(black<<29) for i=1,w*h do g[i]=v end return g end function L.decodeToGrid(data,grid,gw,idx,st) local codes=L.CODES local p,n,sets=1,#data,0 local fg,bg=st.fg or idx[0],st.bg or idx[0] while p<=n do local op=byte(data,p) if op==OP_TEXT then local x,y,len=byte(data,p+1,p+3) local q=p+4 local i=(y-1)*gw+x local colors=(fg<<21)|(bg<<29) local cnt=(len+3)//4 for k=0,cnt-1 do local b=byte(data,q+k) local m=math.min(4,len-k*4) for j=1,m do grid[i]=codes[(b>>(2*(4-j)))&3]|colors i=i+1 end end p=q+cnt sets=sets+1 elseif op==OP_BOTH then bg,fg=idx[byte(data,p+1)],idx[byte(data,p+2)];p=p+3 elseif op==OP_BG then bg=idx[byte(data,p+1)];p=p+2 elseif op==OP_FG then fg=idx[byte(data,p+1)];p=p+2 else error(string.format("corrupted frame: op %d at %d",op,p),0)end end st.fg,st.bg=fg,bg return sets end function L.glyphs() 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 function L.gpuRenderer(gpu,colors,glyphs) local setBg,setFg,set=gpu.setBackground,gpu.setForeground,gpu.set local g4=glyphs[4] local parts={} return function(data) local p,n=1,#data while p<=n do local op=byte(data,p) if op==OP_TEXT then local x,y,len=byte(data,p+1,p+3) local q=p+4 if len<=4 then set(x,y,glyphs[len][byte(data,q)]) p=q+1 else local full=floor(len/4) for i=1,full do parts[i]=g4[byte(data,q+i-1)]end local cnt,rest=full,len-full*4 if rest>0 then cnt=cnt+1;parts[cnt]=glyphs[rest][byte(data,q+full)]end set(x,y,concat(parts,"",1,cnt)) p=q+cnt 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 end end function L.paintGrid(gpu,grid,gw,gh,rgbOf) local curF,curB for y=1,gh do local x=1 local base=(y-1)*gw while x<=gw do local v=grid[base+x] local f,b=(v>>21)&255,(v>>29)&255 local chars,x2={},x while x2<=gw do local v2=grid[base+x2] if((v2>>21)&255)~=f or((v2>>29)&255)~=b then break end chars[#chars+1]=utf8.char(v2&0x1FFFFF) x2=x2+1 end if b~=curB then gpu.setBackground(rgbOf(b));curB=b end if f~=curF then gpu.setForeground(rgbOf(f));curF=f end gpu.set(x,y,table.concat(chars)) x=x2 end end end function L.find(roots,depth) local found,seen={},{} local function walk(dir,d) dir=fs.canonical(dir) if seen[dir]or not fs.isDirectory(dir)then return end seen[dir]=true if L.isVideo(dir)then found[#found+1]=dir return end if d<=0 then return end for name in fs.list(dir)do if name:sub(-1)=="/"then walk(fs.concat(dir,name),d-1)end end end for _,r in ipairs(roots)do pcall(walk,r,depth or 2)end table.sort(found) return found end return L