As some people may know, there is an video on the Internet called Bad Apple!!. This video is interesting because it is entirely in black and white, so it can be compressed fairly efficiently. That made some people run it on some fairly underpowered systems, like the Sega Genesis, the NES and even the Atari 2600!
That said, I've created a simple video decoder for Love2D, that plays this video. The goal was to try and make the video as small as possible. I know almost nothing about video and audio compression, so I used a very very very simple compression scheme.
Encoding
The player runs the video at 64x64 resolution, monochrome at 30fps. I've created a (very primitive) video encoder that does the following:
- Resizes the video to 64x64 and dumps all frames to a folder (using the awesome FFMpeg)
- Converts all frames to monochrome (the video file is actually in grayscale; it gets converted to pure black and white)
- For each frame, do:
-- Find the x and y coordinates of all the pixels that are different (black to white or white to black) from this frame to the other
-- Dump those coordinates to a file
-- Put a frame delimiter
- Compress the file using love.math.compress (zlib)
I limited the resolution of the video in order to use only printable ASCII characters on the compression; I was having problems reading the data from the file .
This results in a file like this (before compression):
Code: Select all
###XYXYXYXYXY#XYXYXY#XYXY#XYXYXYXYXY#
The audio is simply a highly compressed MP3 file playing on the background. That makes the video to go off-sync with the audio.
Playing
The player code is extremely simple:
Code: Select all
-- index is theindex of the current byte being read by the file
function love.update(dt)
value = string.byte(framedata, index) -- get the value of current byte
if value == 32 then -- if it is 32 (frame delimiter), simply skip
index = index + 1 -- (no changes between frames)
end
while value ~= 32 do -- else, while value isn't the frame delimiter
local i = string.byte(framedata, index) - 62 -- get x position of changed pixel
index = index + 1
local j = string.byte(framedata, index) - 62 -- get y position of changed pixel
index = index + 1
if frame[i][j] == 1 then -- swap the pixel on the "framebuffer"
frame[i][j] = 0 -- (black to white or white to black)
else
frame[i][j] = 1
end
value = string.byte(framedata, index) -- get next byte
end
end
There are probably lots of better ways to compress this video. I'd love to see another implementations of this!
Thanks for reading!