TzookB

puppeteer-to-video

Tue Nov 22 2022

slide deck I wanted to save
slide deck I wanted to save

How I extracted HTML slide decks into videos with Puppeteer and a small python script.

Recently I took a course on how to improve product UX. The course was interesting as it actually showed real examples from the real world with products like Lyft, DoorDash, Airbnb, and more. The course content was mostly in some cool slide deck, the slide was moving HTML elements as you go on. I wanted to save this deck so I could refer to it when needed. Export wasn’t available. My next option was to record the screen and go through all the decks manually, workable but annoying. I decided I’ll create a script that will automate the extraction for me. I planned the script to do this:

  • open the page
  • screenshot the slide deck element
  • Click the slide deck to move to the next slide
  • repeat the two steps until the deck is done
  • generate a video from all the screenshots above.

The Slide Deck Extractor script

  • open a new puppteer browser and page
  • set screen size to be large enough to have sized screenshots
  • set the proper cookies as the page is protected
  • open the page and wait for all network calls to load
  • find and wait for the iframe where the slide deck exist
  • loop 200 times, wait, click picture
  • I was lazy on finding when the deck ends
import puppeteer from 'puppeteer';

function delay(time) {
  return new Promise(function(resolve) { 
      setTimeout(resolve, time)
  });
}
(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    executablePath: '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
  });

  const generate = async function(destPath, uri) {
    const page = await browser.newPage();
    
    await page.setViewport({
      width: 3000,
      height: 2000,
    });

    const cookies = ['session-cookies-here'];

    await page.setCookie(...cookies);

    await page.goto(uri, {
      waitUntil: 'networkidle2',
    });

    const selector = '.deck iframe';
    await page.waitForSelector(selector);
    const element = await page.$(selector);

    let count = 1;
    const frame = page.frames().find(frame => frame.url().startsWith('https://example.com/slides'));

    while (count <= 200) {
      await delay(2000);
      await element.screenshot({path: `${destPath}/p${count}.png`});
      await frame.click('.navigate-right');
      count++;
    }
  }

  await generate(folder, uri)
  await browser.close();                          
})();

The Video Generator

  • super simple script here
  • get a folder, and video dest name
  • read all the images in the folder
  • sort them by the name (p1, p2, p3,..., p10, p11,...)
  • concat all the images together with 2 second duration
  • save the video
import cv2
import functools
import numpy as np
import os

def genVideo(image_folder, video_file):
  image_size = (960, 540)
  each_image_duration = 2
  def cut(x):
      return x[1:][0:-4]
  def compare(x, y):
      return 1 if int(cut(x)) > int(cut(y)) else -1
      
  images = [img for img in os.listdir(image_folder) if img.endswith(".png")]

  images.sort(key=functools.cmp_to_key(compare))

  out = cv2.VideoWriter(video_file, cv2.VideoWriter_fourcc('m', 'p', '4', 'v'), 1.0, image_size)

  for filename in images:
      img = cv2.imread(os.path.join(image_folder, filename))
      for _ in range(each_image_duration):
          out.write(img)

  out.release()