Wednesday, February 10, 2021

Bad Apple in CMake: How it Works?

There are already lots of Bad Apple videos played in various things. Start from Minecraft and Command Block, Minecraft Sheep color, and even in your terminal. I'm interested with the latter, but the one I linked uses C. I don't think someone ever do this and I'm probably become the first one to do so. So I present you Bad Apple video player written in CMake. This blog post will explain in-detail how it works, but before we dive deep into the CMake script, there are some things need to be noted.

  1. CMake is slow. In my laptop, it can display the frames at 2.8 FPS. The video above is speed up.
  2. I use braille unicode characters. This allows me to encode 2x4 pixels width in a single braille character. Also Wikipedia has extensive documentation of which bits to set to activate dots in braille characters.
  3. The videos are converted to PBM. Why PBM? Because Bad Apple PV is black-and-white and because it's very simple to parse compared to PGM or PPM. As PBM packed all pixels to bits, where the MSB is the first pixel, this means it's possible to display 8x4 pixels of the image using only 4 braille characters.

Now, open the CMakeLists.txt file as points below will be highlighted by line number.

  1. The first few lines is not that important. They already explained in the comments. The "PRINTER_MODE" condition will be explained later, so just skip to line 138.
  2. At line 138, I look for FFmpeg executable. FFmpeg is used to convert the Bad Apple video into PBM frames.
  3. Line 142 to 158, I look for youtube-dl. youtube-dl is used to download the Bad Apple PV from YouTube.
  4. Line 163, download only the video. I use add_custom_command so the downloaded video is marked as "generated" which allows it to have file-level dependency later on.
  5. For line 169 to 183, there are 6572 frames that's marked as "generated". That 6572 files depends on 1 file, which is the video. This is why I said "file-level dependency". Notice at line 181 I added "VERBATIM" because I need to pass that percent sign as-is. (Reason: Windows doesn't like it)
  6. Then at the line 186, I define a target "BadApple". This will be the target that should be run and it depends on that 6572 frame files. Thus, the order of execution is: download the video, convert to 6572 frames, then run the specified command. If you inspect the command, it literally just run again CMake at the current source directory, but it sets the "PRINTER_MODE" to truth value and sets the "FRAME_PATH" variable, thus let's get back to line 22. You can quickly search up the "CMAKE_*" variables I use in the internet. It should be in the first page.
  7. Line 26 is the braille character that the CMake will use for display.
  8. Line 51 is a function that takes 5 arguments: the result variable name, then followed by the value of a byte in the PBM. That function will shift the bits to fits with braille unicode encoding bits (see https://en.wikipedia.org/wiki/Braille_Patterns#Identifying,_naming_and_ordering for more information). I think an image will explain it better.

  9. More explanation on line 51, that means BYTE1 is used to determine whetever dots 1 or 4 will be turned on. BYTE2 is used for dots 2 and 5. BYTE3 for dots 3 and 6. Finally, BYTE4 for dots 7 and 8. The formula is bit confusing to read due to parentheses and brackets everywhere.
  10. Line 63, a helper function that allows me to index an image byte by its X byte position and Y position. Note that it multiplies the index by 2 later on, that's because the whole PBM data is read as hex.
  11. Line 83 is escape character, building block of the ANSI escape codes of setting cursor position later on.
  12. Line 85 is to loop all the 6572 frames and display it one by one.
  13. Line 87, read the PBM image data directly as hex (because binary data). It's actually possible to calculate the offset by getting the string length of WIDTH and HEIGHT, but I'm lazy.
  14. Line 90 to 95 is basically the "Playback" text which displays the current second and minute of the video. Bad Apple PV runs at 30 FPS so 30 FPS is assumed.
  15. Line 99 and 102 is to loop over every image pixels (except at x axis where we process 8 pixels at a time).
  16. Line 104, now the image at point 8 makes sense. BYTE1 through BYTE4 contains the value of the packed image pixels,
  17. Finally at line 119, it prints the PBM image data as braille characters using CMake's message function and sets the cursor up, ready to be overwritten by the next braille characters.
  18. And at last, at line 124, it sets the cursor to the end and finally the function finishes.

Fun fact: I cut and speed up the videos only with FFmpeg since Kdenlive encoding options result in poor video quality (and my laptop native resolution is only 720p). The total recording duration of the video is 41 minutes and 4 seconds

... or equivalent to Bad Apple PV played 11 times plus quarter.


No comments:

Post a Comment