How to put video on the web without using YouTube

Why would I want to do this?

As much as I use and enjoy services like YouTube, Vimeo, and Flickr, I’ve never been a big fan of entrusting my content to large, centralized, corporate services. Yes they’re here today, yes they seem stable and secure, but will they always be around? Even while they are still in business, I’ve never liked the idea that the content I create effectively lives on their site, so they end up profiting from my page views. And frankly, centralization like that is counter to the distributed architecture at the core of the internet.

That said, this is as much to aid my memory for any future web video projects as it is to assist anyone else in a similar endeavor. It condenses about 18 hours of experimentation, research, and testing into a set of simple, easy to follow steps.

Step 1: Make some video

The easiest way to do this is with a point-and-shoot digital camera. I admit that I know absolutely nothing about video-editing, and even less about doing so on Ubuntu. That is fodder for a future post, I suppose. But honestly, we’re talking about short videos for the web. Who cares about editing?

For those who are interested in making a time-lapse video, I wrote a whole post on how to make a time-lapse video with FFmpeg.

Step 2: Add some sound

Since the audio quality from most point-and-shoot cameras is rather poor, most people tend to replace it with music if it’s not critical to the content of the video. I also know very little about audio editing, but I can coax Audacity into cutting an audio track down to the length of the video, and adding a fade-in and a fade-out. Once I’ve got a wav or mp3 that I want to use, here’s the command to merge the audio and the video:

ffmpeg -i video.mp4 -i audio.wav -ar 44100 -acodec libfaac -ab 128k -sameq final.mp4

Step 3: Encode for the web

Until all browsers support the new HTML5 <video> element, Flash Video (.flv) is the de facto standard for streaming video on the web. At this point it helps to know what dimensions to encode the video to. I discovered that both the width and height need to be multiples of 2, as this helps with the data compression. On my blog, video with a 4:3 aspect ratio (like TV) translates to pixel dimensions of 380×284. Here is the FFmpeg command to convert an mp4 video to a reasonably compressed flv video:

ffmpeg -i final.mp4 -ar 22050 -s 380x284 -qscale 4 web.flv

For better video and audio compression, you can also specify the H.264 and AAC codecs, which are supported in Flash 9 and above.

ffmpeg -i final.mp4 -vcodec libx264 -acodec libfaac web.flv

Step 4: Choose a media player

Several sources recommended the JW FLV Media Player, which looks a little homely at first, but turns out to be infinitely configurable, as well as skinnable. I wanted my video player to resemble the Vimeo-style controls as much as possible (overlayed, display on hover, etc) and sure enough they had a skin, Bekle, that looked just like it.

Screenshot of the JW Player with the Bekle skin

Minimally, the player requires that I host a single file, player.swf, and optionally a second swf file for the skin. The look and feel of the player can then be modified by many configuration options set via flashvars. One of the flashvars worth mentioning, config, makes it possible to bundle up a series of common flashvars settings into a simple XML format, so I don’t have to repeat settings that I plan to implement site-wide (like colors, the skin URL, etc).

<config>
   <skin>http://justinsomnia.org/videos/player-skin.swf</skin>
   <frontcolor>7D9CA2</frontcolor>
   <lightcolor>A3C0C6</lightcolor>
   <controlbar>over</controlbar>
   <icons>false</icons>
</config>

Step 5: Post online

The documentation recommends using v1.5 of the swfobject.js library to embed the Flash Video on the page, but I found that to be overkill for my purposes. I write most of my posts in straight HTML, so I’d prefer to limit the content of my posts to HTML, rather than mess with inline JavaScript. That and WordPress tends to choke on inline JavaScript in posts.

The real benefit of swfobject is its battle-tested cross-browser compatibility. By foregoing that route, I had to figure out what combination of <object>, <embed>, and <param> tags was the most compatible across the latest versions of the big 3: Firefox 3, Safari 4, and Internet Explorer 8. As it turns out Firefox and Safari were the most forgiving, and managed to load and play every combination of tags I threw at them. IE would only load the video if it had a param tag of type “movie”. Ohhhhk.

<object data="/videos/player.swf?file=/videos/pine-street-sunset-time-lapse.flv&image=/images/pine-street-sunset-time-lapse.jpg&config=/videos/player.xml" type="application/x-shockwave-flash" width="380" height="284">
<param name="movie" value="/videos/player.swf?file=/videos/pine-street-sunset-time-lapse.flv&image=/images/pine-street-sunset-time-lapse.jpg&config=/videos/player.xml"/>
<param name="allowfullscreen" value="true"/>
<param name="allowscriptaccess" value="always"/>
</object>

As an aside: after letting IE6 lie fallow for 5 years, and then reawakening after of furious advances made by Mozilla, Apple, Opera, Adobe, and now Google, Microsoft has yet to support the object tag standard as defined in the HTML 4.01 spec from 1999. Embarrassing.

Anyway, I’m not about to stoop to support IE’s backwardness in every post that I embed video, so I wrote a quick WordPress plugin that would inject the necessary movie param tag into the object tag for any IE user-agents.

<?php
/*
Plugin Name: Add movie param to object tags for IE
Description: This plugin gets the data attribute from an <code>&lt;object&gt;</code> tag and adds it in as a <code>&lt;param name="movie" value="[data]"/&gt;</code> for Internet Explorers
Author: Justin Watt
Version: 1
Author URI: http://justinsomnia.org
*/

add_filter("the_content", "add_movie_param_to_object_tags_for_ie");

function add_movie_param_to_object_tags_for_ie($content)
{
  if (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false)) {
    return preg_replace("#(<object\s+[^>]*?data\s*=\s*('|\"))([^'\"]+)(.*?)(</object>)#si", "$1$3$4<param name=\"movie\" value=\"$3\"/>$5", $content);
  } else {
    return $content;
  }
}