I spent an inordinate amount of time not doing useful things today. During this non-productivity bender, I got a little obsessed with making the HTML5 video encoding process easier. I’ve been working on a tutorial site for the Blogsmith Bundle, so this whole deal needed to be sped up. I came out with a script which, when combined with the right command line utilities, takes 90% of the manual labor out of creating the multiple formats needed.

HTML5 video, if you’re not familiar, provides a means to supply modern browsers with high-quality video without using Flash. It works across iOS devices beautifully, and the markup is highly semantic. The problem is that every browser has chosen a different format to promote, and using HTML5 video effectively means providing the same video in three formats (OGV, MP4, WEBM). Then, you need to code in a Flash fallback for older browsers. This would all be fine if my usual video conversion tools had a multiple-encode feature. They don’t. The only one that comes close is Evom, which has an HTML5 preset to encode OGV and MP4 at the same time, but the OGV files won’t play in Firefox and it can’t do WEBM at all right now.

So, taking matters into my own hands…

The workflow

Here’s the workflow I’m using now:

  1. Export a screencast as an MP4 file to a bucket folder
  2. Hazel detects a new MP4 file and runs a shell script on it which:
    1. Creates a new folder based on the file’s base name and moves the file into it
    2. Runs an ffmpeg sequence to create a webm version
    3. Runs an ffmpeg2theora command to create an ogv
    4. Uses ffmpeg to create a poster image from the first frame of the mp4
    5. Uses rsync to put the finished folder on my server
  3. I get a growl notification from Hazel that files are converted and uploaded
  4. I insert the ungodly-long “shortcode” for the VideoJS plugin using TextExpander into a new WordPress post.

Four steps, and I only have to interact with two of them. The script is pretty hardcoded for my setup, but I moved some of the config up to the top for convenience. You’ll need ffmpeg and ffmpeg2theora installed, both of which are really easy to set up with homebrew. You can probably find binaries, too, but I just used brew for these.

The script

Here’s the script, hope it helps someone else, too:

#!/bin/bash
# html5encode.sh
# Brett Terpstra, 2011 - http://brettterpstra.com/automating-html5-video-encodes
# Requires ffmpeg and ffmpeg2theora
##################
### CONFIG #######
MAXSIZE="960x540"
SSHURL="username@your.webserver.com"
SSHDIR="/www/video/media/"
### END CONFIG ###

INPUT=$1
DIRNAME=`dirname "$INPUT"`
FILENAME=`basename "$INPUT"`
BASENAME=${FILENAME%%.*}

cd "$DIRNAME"
if [ -d "$BASENAME" ]; then
  # /usr/local/bin/growlnotify -s -i "Terminal.app" -m "Found $FILENAME, but directory $BASENAME already exists. Aborting"
  exit 1
fi
mkdir "$BASENAME"
mv "$FILENAME" "$BASENAME/"
cd "$BASENAME"

/usr/local/bin/ffmpeg -pass 1 -passlogfile "$FILENAME" -threads 16  -keyint_min 0 \
-g 250 -skip_threshold 0 -qmin 1 -qmax 51 -i "$FILENAME" -vcodec libvpx -b 614400 \
-s $MAXSIZE -aspect 16:9 -an -y temp.webm

/usr/local/bin/ffmpeg -pass 2 -passlogfile "$FILENAME" -threads 16  -keyint_min 0 \
-g 250 -skip_threshold 0 -qmin 1 -qmax 51 -i "$FILENAME" -vcodec libvpx -b 614400 \
-s $MAXSIZE -aspect 16:9 -acodec libvorbis -y "$BASENAME".webm

rm temp.webm
rm *.log

/usr/local/bin/ffmpeg2theora --videoquality 5 --audioquality 1 --max_size $MAXSIZE "$FILENAME" -o "$BASENAME.ogv"
/usr/local/bin/ffmpeg -i "$FILENAME" -ss 0 -vframes 1 -vcodec mjpeg -f image2 "${BASENAME}Poster.jpg"
cd ..
rsync -v -r -e ssh "`pwd`/$BASENAME" $SSHURL:$SSHDIR

The Hazel rule I’m running simply looks for files in the bucket folder with the .mp4 extension and passes them to the shell script.

Let me know if you have a better way to automate this. Also, let me know if you’ve found a good way to encode h.264 from the command line on a Mac. I haven’t been able to get ffmpeg to compile with x/h.264 support yet, but it was irrelevant as I had to output some format to begin with. Might as well save to the one format I can’t encode properly from a shell script…