Advertisement
BombBloke

RecGif (ComputerCraft)

Oct 7th, 2015
897
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.93 KB | None | 0 0
  1. -- +---------------------+------------+---------------------+
  2. -- |                     |            |                     |
  3. -- |                     |   RecGif   |                     |
  4. -- |                     |            |                     |
  5. -- +---------------------+------------+---------------------+
  6.  
  7. local version = "Version 1.1.7"
  8.  
  9. -- Records your terminal and saves the result as an animating GIF.
  10. -- http://www.computercraft.info/forums2/index.php?/topic/24840-recgif/
  11.  
  12. -- ----------------------------------------------------------
  13.  
  14. local calls, recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput, callCount, callListCount = {{["delay"] = 0}}, {}, term.current(), {...}, false, false, 2, "", 1, 2
  15. local curBlink, oldBlink, curCalls, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, calls[1], {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, term.getSize()
  16. local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
  17. local charW, charH, chars, resp
  18.  
  19. for i = #arg, 1, -1 do
  20.     local curArg = arg[i]:lower()
  21.    
  22.     if curArg == "-i" then
  23.         showInput, ySize = true, ySize + 1
  24.         table.remove(arg, i)
  25.     elseif curArg == "-s" then
  26.         skipLast = true
  27.         table.remove(arg, i)
  28.     elseif curArg:sub(1, 4) == "-ld:" then
  29.         curArg = tonumber(curArg:sub(5))
  30.         if curArg then lastDelay = curArg end
  31.         table.remove(arg, i)
  32.     end
  33. end
  34.  
  35. local function snooze()
  36.     local myEvent = tostring({})
  37.     os.queueEvent(myEvent)
  38.     os.pullEvent(myEvent)
  39. end
  40.  
  41. local function safeString(text)
  42.     local newText = {}
  43.    
  44.     for i = 1, #text do
  45.         local val = text:byte(i)
  46.         newText[i] = (val > 31 and val < 127) and val or 63
  47.     end
  48.    
  49.     return string.char(unpack(newText))
  50. end
  51.  
  52. local function safeCol(text, subst)
  53.     local newText = {}
  54.    
  55.     for i = 1, #text do
  56.         local val = text:sub(i, i)
  57.         newText[i] = greys[val] and val or subst
  58.     end
  59.    
  60.     return table.concat(newText)
  61. end
  62.  
  63. -- Build a terminal that records stuff:
  64.  
  65. for key, func in pairs(oldTerm) do
  66.     recTerm[key] = function(...)
  67.         local result = {pcall(func, ...)}
  68.        
  69.         if result[1] then
  70.             curCalls[callCount] = {key, ...}
  71.             callCount = callCount + 1
  72.             return unpack(result, 2)
  73.         else error(result[2], 2) end
  74.     end
  75. end
  76.  
  77. -- Run the user's script through the special terminal:
  78.  
  79. term.setTextColour(colours.white)
  80. term.setBackgroundColour(colours.black)
  81. term.setCursorPos(1, 1)
  82. term.clear()
  83.  
  84. term.redirect(recTerm)
  85.  
  86. do
  87.     local thread, curTime = coroutine.create(shell.run), os.clock()
  88.     local coResume, coYield, coStatus = coroutine.resume, coroutine.yield, coroutine.status
  89.     local ok, filter = coResume(thread, #arg == 0 and "shell" or arg[1], unpack(arg, 2))
  90.  
  91.     while coStatus(thread) ~= "dead" do
  92.         local event = {coYield()}
  93.  
  94.         if event[1] == filter or not filter or event[1] == "terminate" or (showInput and (event[1] == "key" or event[1] == "mouse_click")) then
  95.             local newTime = os.clock()
  96.  
  97.             if newTime ~= curTime then
  98.                 local delay = curCalls.delay + (newTime - curTime)
  99.                 curTime = newTime
  100.  
  101.                 if callCount > 1 then
  102.                     curCalls.delay = curCalls.delay + delay
  103.                     curCalls, callCount = {["delay"] = 0}, 1
  104.                     calls[callListCount] = curCalls
  105.                     callListCount = callListCount + 1
  106.                 else calls[callListCount - 2].delay = calls[callListCount - 2].delay + delay end
  107.             end
  108.  
  109.             if showInput and (event[1] == "key" or event[1] == "mouse_click") then
  110.                 curCalls[callCount] = {unpack(event)}
  111.                 callCount = callCount + 1
  112.             end
  113.  
  114.             if event[1] == filter or not filter or event[1] == "terminate" then ok, filter = coResume(thread, unpack(event)) end
  115.         end
  116.     end
  117. end
  118.  
  119. term.redirect(oldTerm)
  120.  
  121. if #calls[#calls] == 0 then calls[#calls] = nil end
  122. if skipLast and #calls > 1 then calls[#calls] = nil end
  123. calls[#calls].delay = lastDelay
  124.  
  125. -- Recording done, bug user as to whether to encode it:
  126.  
  127. repeat
  128.     write("\nEncode GIF? (y/n): ")
  129.     resp = read():lower()
  130. until resp == "y" or resp == "n"
  131.  
  132. if resp == "n" then error() end
  133.  
  134. write("\nEnter filename: ")
  135.  
  136. resp = read()
  137. if resp:sub(#resp - 3):lower() ~= ".gif" then resp = resp .. ".gif" end
  138.  
  139. write("\nEncoding... ")
  140.  
  141. -- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
  142.  
  143. do
  144.     local callListCount, tempCalls, blink, oldBlink, curBlink, blinkDelay = 1, {}, false, false, true, 0
  145.    
  146.     for i = 1, #calls - 1 do
  147.         curCalls = calls[i]
  148.         tempCalls[callListCount] = curCalls
  149.         for j = 1, #curCalls do if curCalls[j][1] == "setCursorBlink" then blink = curCalls[j][2] end end
  150.        
  151.         if blink then
  152.             if blinkDelay == 0 then
  153.                 curCalls[#curCalls + 1] = {"toggleCur", curBlink}
  154.                 blinkDelay, curBlink = 0.4, not curBlink
  155.             end
  156.            
  157.             while tempCalls[callListCount].delay > blinkDelay do
  158.                 local remainder = tempCalls[callListCount].delay - blinkDelay
  159.                 tempCalls[callListCount].delay = blinkDelay
  160.                 callListCount = callListCount + 1
  161.                 tempCalls[callListCount] = {{"toggleCur", curBlink}, ["delay"] = remainder}
  162.                 blinkDelay, curBlink = 0.4, not curBlink
  163.             end
  164.            
  165.             blinkDelay = blinkDelay - tempCalls[callListCount].delay
  166.         else
  167.             if oldBlink then curCalls[#curCalls + 1] = {"toggleCur", false} end
  168.             blinkDelay = (curCalls.delay - blinkDelay) % 0.4
  169.         end
  170.        
  171.         callListCount, oldBlink = callListCount + 1, blink
  172.     end
  173.    
  174.     tempCalls[callListCount] = calls[#calls]
  175.     tempCalls[callListCount][#tempCalls[callListCount] + 1] = {"toggleCur", false}
  176.    
  177.     calls, curCalls = tempCalls, nil
  178. end
  179.  
  180. snooze()
  181.  
  182. -- Get files / load APIs:
  183.  
  184. if not bbpack then
  185.     if not (fs.exists("bbpack") or fs.exists(shell.resolve("bbpack"))) then
  186.         shell.run("pastebin get cUYTGbpb bbpack")
  187.         os.loadAPI(shell.resolve("bbpack"))
  188.     else os.loadAPI(fs.exists("bbpack") and "bbpack" or shell.resolve("bbpack")) end
  189. end
  190.  
  191. if not GIF then
  192.     if not (fs.exists("GIF") or fs.exists(shell.resolve("GIF"))) then
  193.         shell.run("pastebin get 5uk9uRjC GIF")
  194.         os.loadAPI(shell.resolve("GIF"))
  195.     else os.loadAPI(fs.exists("GIF") and "GIF" or shell.resolve("GIF")) end
  196. end
  197.  
  198. if not fs.exists(shell.resolve("ascii.gif")) then shell.run("bbpack get " .. (_HOST and "CnLzL5fg" or "Y0eLUPtr")) end
  199.  
  200. -- Load font data:
  201.  
  202. do
  203.     local ascii, counter = GIF.toPaintutils(GIF.flattenGIF(GIF.loadGIF(shell.resolve("ascii.gif")))), 0
  204.     local newFont, ybump, xbump = #ascii ~= #ascii[1], 0, 0
  205.     charW, charH, chars = newFont and #ascii[1] / 16 or #ascii[1] * 3 / 64, #ascii / 16, {}
  206.  
  207.     for yy = 0, newFont and 15 or 7 do
  208.         for xx = 0, 15 do
  209.             local newChar, length = {}, 0
  210.  
  211.             -- Place in 2d grid of bools:
  212.             for y = 1, charH do
  213.                 local newRow = {}
  214.  
  215.                 for x = 1, charW do
  216.                     local set = ascii[y + ybump][x + xbump] == 1
  217.                     if set and x > length then length = x end
  218.                     newRow[x] = set
  219.                 end
  220.  
  221.                 newChar[y] = newRow
  222.             end
  223.  
  224.             -- Center:
  225.             if not newFont then for y = 1, charH do for x = 1, math.floor((charW - length) / 2) do table.insert(newChar[y], 1, false) end end end
  226.  
  227.             chars[counter] = newChar
  228.             counter, xbump = counter + 1, xbump + (newFont and charW or charH)
  229.         end
  230.         xbump, ybump = 0, ybump + charH
  231.     end
  232. end
  233.  
  234. snooze()
  235.  
  236. -- Terminal data translation:
  237.  
  238. do
  239.     local hex, counter = "0123456789abcdef", 1
  240.  
  241.     for i = 1, 16 do
  242.         colourNum[counter] = hex:sub(i, i)
  243.         counter = counter * 2
  244.     end
  245. end
  246.  
  247. for y = 1, ySize do
  248.     buffer[y] = {}
  249.     for x = 1, xSize do buffer[y][x] = {" ", colourNum[tCol], colourNum[bCol]} end
  250. end
  251.  
  252. if showInput then for x = 1, xSize do buffer[ySize][x][3] = colourNum[colours.lightGrey] end end
  253.  
  254. tTerm.blit = function(text, fgCol, bgCol)
  255.     if xPos > xSize or xPos + #text - 1 < 1 or yPos < 1 or yPos > ySize then return end
  256.    
  257.     if not _HOST then text = safeString(text) end
  258.    
  259.     if not term.isColour() then
  260.         fgCol = safeCol(fgCol, "0")
  261.         bgCol = safeCol(bgCol, "f")
  262.     end
  263.    
  264.     if xPos < 1 then
  265.         text = text:sub(2 - xPos)
  266.         fgCol = fgCol:sub(2 - xPos)
  267.         bgCol = bgCol:sub(2 - xPos)
  268.         xPos = 1
  269.     end
  270.    
  271.     if xPos + #text - 1 > xSize then
  272.         text = text:sub(1, xSize - xPos + 1)
  273.         fgCol = fgCol:sub(1, xSize - xPos + 1)
  274.         bgCol = bgCol:sub(1, xSize - xPos + 1)
  275.     end
  276.    
  277.     for x = 1, #text do
  278.         buffer[yPos][xPos + x - 1][1] = text:sub(x, x)
  279.         buffer[yPos][xPos + x - 1][2] = fgCol:sub(x, x)
  280.         buffer[yPos][xPos + x - 1][3] = bgCol:sub(x, x)
  281.     end
  282.    
  283.     xPos = xPos + #text
  284. end
  285.  
  286. tTerm.write = function(text)
  287.     text = tostring(text)
  288.     tTerm.blit(text, string.rep(colourNum[tCol], #text), string.rep(colourNum[bCol], #text))
  289. end
  290.  
  291. tTerm.clearLine = function()
  292.     local oldXPos = xPos
  293.    
  294.     xPos = 1
  295.     tTerm.write(string.rep(" ", xSize))
  296.    
  297.     xPos = oldXPos
  298. end
  299.  
  300. tTerm.clear = function()
  301.     local oldXPos, oldYPos = xPos, yPos
  302.    
  303.     for y = 1, ySize do
  304.         xPos, yPos = 1, y
  305.         tTerm.write(string.rep(" ", xSize))
  306.     end
  307.    
  308.     xPos, yPos = oldXPos, oldYPos
  309. end
  310.  
  311. tTerm.setCursorPos = function(x, y)
  312.     xPos, yPos = math.floor(x), math.floor(y)
  313. end
  314.  
  315. tTerm.setTextColour = function(col)
  316.     tCol = col
  317. end
  318.  
  319. tTerm.setTextColor = function(col)
  320.     tCol = col
  321. end
  322.  
  323. tTerm.setBackgroundColour = function(col)
  324.     bCol = col
  325. end
  326.  
  327. tTerm.setBackgroundColor = function(col)
  328.     bCol = col
  329. end
  330.  
  331. tTerm.scroll = function(lines)
  332.     if math.abs(lines) < ySize then
  333.         local oldXPos, oldYPos = xPos, yPos
  334.        
  335.         for y = 1, ySize do
  336.             if y + lines > 0 and y + lines <= ySize then
  337.                 for x = 1, xSize do
  338.                     xPos, yPos = x, y
  339.                     tTerm.blit(buffer[y + lines][x][1], buffer[y + lines][x][2], buffer[y + lines][x][3])
  340.                 end
  341.             else
  342.                 yPos = y
  343.                 tTerm.clearLine()
  344.             end
  345.         end
  346.        
  347.         xPos, yPos = oldXPos, oldYPos
  348.     else tTerm.clear() end
  349. end
  350.  
  351. tTerm.toggleCur = function(newBlink)
  352.     curBlink = newBlink
  353. end
  354.  
  355. tTerm.newInput = function(input)
  356.     local oldTC, oldBC, oldX, oldY = tCol, bCol, xPos, yPos
  357.     tCol, bCol, xPos, yPos, ySize, input = colours.grey, colours.lightGrey, 1, ySize + 1, ySize + 1, input .. " "
  358.    
  359.     while #curInput + #input + 1 > xSize do curInput = curInput:sub(curInput:find(" ") + 1) end
  360.     curInput = curInput .. input .. " "
  361.     tTerm.clearLine()
  362.     tTerm.write(curInput)
  363.    
  364.     tCol, bCol, xPos, yPos, ySize = oldTC, oldBC, oldX, oldY, ySize - 1
  365. end
  366.  
  367. tTerm.key = function(key)
  368.     tTerm.newInput((not keys.getName(key)) and "unknownKey" or keys.getName(key))
  369. end
  370.  
  371. tTerm.mouse_click = function(button, x, y)
  372.     tTerm.newInput(buttons[button] .. "C@" .. tostring(x) .. "x" .. tostring(y))
  373. end
  374.  
  375. local image = {["width"] = xSize * charW, ["height"] = ySize * charH}
  376.  
  377. for i = 1, #calls do
  378.     local xMin, yMin, xMax, yMax, oldBuffer, curCalls, changed = xSize + 1, ySize + 1, 0, 0, {}, calls[i], false
  379.     calls[i] = nil
  380.    
  381.     for y = 1, ySize do
  382.         oldBuffer[y] = {}
  383.         for x = 1, xSize do oldBuffer[y][x] = {buffer[y][x][1], buffer[y][x][2], buffer[y][x][3], buffer[y][x][4]} end
  384.     end
  385.    
  386.     snooze()
  387.    
  388.     if showInput then ySize = ySize - 1 end
  389.     for j = 1, #curCalls do if tTerm[curCalls[j][1]] then tTerm[curCalls[j][1]](unpack(curCalls[j], 2)) end end
  390.     if showInput then ySize = ySize + 1 end
  391.    
  392.     if i > 1 then
  393.         for yy = 1, ySize do for xx = 1, xSize do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] then
  394.             changed = true
  395.             if xx < xMin then xMin = xx end
  396.             if xx > xMax then xMax = xx end
  397.             if yy < yMin then yMin = yy end
  398.             if yy > yMax then yMax = yy end
  399.         end end end
  400.     else xMin, yMin, xMax, yMax, changed = 1, 1, xSize, ySize, true end
  401.    
  402.     if oldBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not curBlink) and oldXPos > 0 and oldYPos > 0 and oldXPos <= xSize and oldYPos <= ySize then
  403.         changed = true
  404.         if oldXPos < xMin then xMin = oldXPos end
  405.         if oldXPos > xMax then xMax = oldXPos end
  406.         if oldYPos < yMin then yMin = oldYPos end
  407.         if oldYPos > yMax then yMax = oldYPos end
  408.         buffer[oldYPos][oldXPos][4] = false
  409.     end
  410.    
  411.     if curBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not oldBlink) and xPos > 0 and yPos > 0 and xPos <= xSize and yPos <= ySize then
  412.         changed = true
  413.         if xPos < xMin then xMin = xPos end
  414.         if xPos > xMax then xMax = xPos end
  415.         if yPos < yMin then yMin = yPos end
  416.         if yPos > yMax then yMax = yPos end
  417.         buffer[yPos][xPos][4] = true
  418.     end
  419.    
  420.     oldBlink, oldXPos, oldYPos = curBlink, xPos, yPos
  421.    
  422.     local thisFrame = {["xstart"] = (xMin - 1) * charW, ["ystart"] = (yMin - 1) * charH, ["xend"] = (xMax - xMin + 1) * charW, ["yend"] = (yMax - yMin + 1) * charH, ["delay"] = curCalls.delay, ["disposal"] = 1}
  423.    
  424.     for y = 1, (yMax - yMin + 1) * charH do
  425.         local row = {}
  426.         for x = 1, (xMax - xMin + 1) * charW do row[x] = " " end
  427.         thisFrame[y] = row
  428.     end
  429.    
  430.     snooze()
  431.    
  432.     for yy = yMin, yMax do
  433.         local yBump = (yy - yMin) * charH
  434.        
  435.         for xx = xMin, xMax do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] or buffer[yy][xx][4] ~= oldBuffer[yy][xx][4] or  i == 1 then
  436.             local thisChar, thisT, thisB, xBump = chars[buffer[yy][xx][1]:byte()], buffer[yy][xx][2], buffer[yy][xx][3], (xx - xMin) * charW
  437.            
  438.             for y = 1, charH do for x = 1, charW do thisFrame[y + yBump][x + xBump] = thisChar[y][x] and thisT or thisB end end
  439.            
  440.             if buffer[yy][xx][4] then
  441.                 thisT, thisChar = colourNum[tCol], chars[95]
  442.                 for y = 1, charH do for x = 1, charW do if thisChar[y][x] then thisFrame[y + yBump][x + xBump] = thisT end end end
  443.             end
  444.         end end
  445.        
  446.         for y = yBump + 1, yBump + charH do
  447.             local skip, chars, row = 0, {}, {}
  448.            
  449.             for x = 1, #thisFrame[y] do
  450.                 if thisFrame[y][x] == " " then
  451.                     if #chars > 0 then
  452.                         row[#row + 1] = table.concat(chars)
  453.                         chars = {}
  454.                     end
  455.                    
  456.                     skip = skip + 1
  457.                 else
  458.                     if skip > 0 then
  459.                         row[#row + 1] = skip
  460.                         skip = 0
  461.                     end
  462.                    
  463.                     chars[#chars + 1] = thisFrame[y][x]
  464.                 end
  465.             end
  466.            
  467.             if #chars > 0 then row[#row + 1] = table.concat(chars) end
  468.             thisFrame[y] = row
  469.         end
  470.        
  471.         snooze()
  472.     end
  473.    
  474.     if changed then image[#image + 1] = thisFrame else image[#image].delay = image[#image].delay + curCalls.delay end
  475. end
  476.  
  477. buffer = nil
  478.  
  479. GIF.saveGIF(image, resp)
  480.  
  481. print("Encode complete.\n")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement