DEV Community

RobL
RobL

Posted on

Making a YouTube Short

Every year myself and my wife go to Essen, Germany for the annual Spiel, this year it's 5th - 8th October. She runs her own board game YouTube channel as I Play Red. I help her film and stuff.

So, this year we decided to create a fun video before the Spiel, which coincidentally crosses over with Rails World which I am sad about not attending, maybe next year.

As a fun side-project I created MySpiel which gathers information about all of the new board game releases at Spiel including grabbing images of the board game box art. It's a Rails project with the images stored with ActiveStorage, there are about 1,000 images so what came we make with that.

We wanted to make a YouTube Short, the criteria for that is a video with a ratio of 16:9 that is 60 seconds long at the most. Ok, it actually got to be vertical, so the ratio is actually 9:16. So I think we're going to smash all of those images together into a video just to demonstrate how huge 1,000 new releases really is. So whatever images we've got they have to be resized to that ratio before we do anything else.

ImageMagick is a pretty standard tool for image manipulation and it's got a pretty powerful command line interface which honestly is often overwhelming but fortunately there are plenty of forums, stack overflow, etc to get good examples.

Firstly though I want to find all the images on my local disk I'm using local storage for ActiveStorage. We can grab the path to all of the files and whack that in an external file. You can extract the path name to the file using ActiveStorage::Blob.service#path_for

class Game < ApplicationRecord
  has_one_attached :cover

  def on_disk
    ActiveStorage::Blob.service.path_for(self.cover.key)
  end
end
Enter fullscreen mode Exit fullscreen mode

Then we dump all the paths to a file.

File.open(Rails.root.join("images.txt"), "w") do |f| 
  f.puts Game.all.map(&:on_disk).join("\n")
end
Enter fullscreen mode Exit fullscreen mode

Now that we have all the paths, I'm going to use ImageMagick to resize and crop all the images to 9:16 in bash. It will read in the files from images.txt and output each of them to a .png

#!/bin/bash
for i in `cat images.txt`;
do 
  file $i;
  o=`basename $i`
  magick $i -gravity center -extent 9:16 -resize 1440x2560 out/$o.png;
done;
Enter fullscreen mode Exit fullscreen mode

Celtae from Pythagoras Games is a "worker swapping" game powered by a rondel in which players choose actions to perform during their turn.

Celtae 1:1

Celtae 1:1

Celtae 9:16

Celtae 9:16

Ok, that took a while but now we have almost 1,100 images resized and cropped. We want to collect these altogether into a video. We can use FFMpeg for that, but pausing a bit. We want to fit 1,100 images into 60 seconds. That 18 images per second. I'm going to take a guess that smashing that many images together in close succession is going to be quite jarring. How can we make make it less jarring. Well for fun we could order the images by colour (or approximate colour).

This was a nice little snippet.

https://www.imagemagick.org/discourse-server/viewtopic.php?t=29789

Effectively it resizes the image in memory to 1px by 1px, then extracts the RGB value

convert celtae.png -scale 1x1\! txt:- | tail -n 1
0,0: (76,30,13)  #4C1E0D  srgb(29.6631%,11.7692%,4.93835%)
Enter fullscreen mode Exit fullscreen mode

The full script here effectively renames each file to extracted RGB value.

#!/bin/bash
for file in *.png
do
  filename=`convert $file -scale 1x1\! txt:- | tail -n 1 | awk -F\( '{print $2}'|cut -d\) -f1|awk -F\, '{print $1$2$3}'`

  extension=".png"
  while [ -f "$filename$extension" ]
  do
    random=`echo $RANDOM % 10 + 1 | bc`
    filename=$filename$random
  done

  mv $file $filename$extension
done
Enter fullscreen mode Exit fullscreen mode

So now I have a directory of images that are ordered by the filename (RGB value) by default. We can now use ffmpeg to convert our images to into a video, each image represents a frame and we can set our framerate to 18 in order to fit everything into 60 seconds.

cat *.png | ffmpeg -framerate 18 -f image2pipe -i - -c:v libx264 -r 30 -pix_fmt yuv420p output.mp4
Enter fullscreen mode Exit fullscreen mode

The result is our output.mp4.

The final version needed a few tweaks, so I imported the final mp4 into DaVinci Resolve added a few overlay images but here it is.

That was a fun, can you spot any games you are looking forward to? I can pick a few of them out and recognise but 18 frames per second is a bit unsettling.

If you like boardgames this is I Play Red, if you're in Germany and not at Rails World then you could do worse than to check out Spiel. It's a little bit enormous, and a spectacle and with 1,100 releases this year you'll find something that appeals.

I'd love to see how other people are programmatically creating video content for some further inspiration.

Top comments (0)