Benjamin Lannon

Select a theme. Click on the overlay or the button again to exit

Advent of Code 2020 Timelapse Behind the Scenes

This past December, I created a project to capture the progress of people completing the Advent of Code event. With it, I leveraged GitHub Actions and AWS to manage the data collection and then rendered the video out with ffmpeg.

If you want to see the final result, I shared the video in a Tweet.

Screenshot Collection

I've previously made a GitHub Action which Takes screenshots of webpages using Puppeteer. With this, I set up a GitHub Actions workflow that would visit https://adventofcode.com/2020/stats every 20 minutes and save the screenshot off to an AWS S3 Bucket using the AWS CLI.

name: Advent of Code Stats Screenshot
on:
schedule:
- cron: '*/20 * * * *' # every 20 minutes
jobs:
run_puppeteer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Screenshot
uses: 'lannonbr/puppeteer-screenshot-action@1.3.1'
with:
url: https://adventofcode.com/2020/stats
width: 1440
height: 1024
- name: Push to S3
env:
AWS_ACCESS_KEY_ID: $
AWS_SECRET_ACCESS_KEY: $
BUCKET_URL: $
run: aws s3 cp $GITHUB_WORKSPACE/screenshots/ $BUCKET_URL --recursive

I set this up on December 2nd so I didn't get the full overview, but rather the majority of it. From here, I waited it out while the data was being collected through the month.

Data transform

When December 26th came around, I downloaded all of the images stored in the S3 bucket down to my local machine to do some processing on them. To be able to stitch the images into a video format, I had to make sure that the images were ordered numberically rather than by their timestamp (ex: screenshot-0001.png, screenshot-0002.png, etc). I had over 1300 images across the month so I needed to make sure to pad the numbers to have a max of 3 0's up front.

I wrote a short Node.js script to handle the file conversion.

import fs from 'fs'

const files = fs.readdirSync('./orig')

files.sort(
(a, b) =>
// Extract timestamp from string (screenshot-1618881742.png -> 1618881742)
parseInt(a.split('-')[1].split('.')[0]) -
parseInt(a.split('-')[1].split('.')[0])
)

files.forEach((file, i) => {
let formattedIdx = (i + 1).toString().padStart(4, '0')

fs.copyFileSync(`./orig/${file}`, `./new/screenshot-${formattedIdx}.png`)
})

Image Stitching

Finally, I searched a ffmpeg command to take a series of screenshots into a video. (Credit to Hammad Mazhar).

ffmpeg -r 60 -f image2 -s 1440x1024 -i screenshot-%04d.png -vcodec libx264 -crf 20 -pix_fmt yuv420p out-60fps.mp4

Deconstructing the various arguments:

Once this was done, I had an mp4 file with the outputted video.