We had a problem with our flash movie player: most videos played fine,
but for some files the whole video had to be transferred before it started
to play. What was going on here?
It turned out that the quicktime format’s header, the “moov” atom, can
actually be at the end of the file – presumably because it is
convenient for video editing software. There’s also a fix for this
problem: newer ffmpeg distributions come with a small helper called
qt-faststart that can fix this kind of problem.
Anyway, “qt-faststart” needs to copy the whole movie to do this, so
how do we find out if it is really necessary? Furthermore, we’ve
already got a decent number of video files on our platform we’d need to
check for streamability.
One solution is to use a command line tool to check for this. Before I
heard of qt-faststart and before I knew how to parse quicktime movies,
I tried to (ab)use mplayer for this:
mplayer -msglevel all=7 -frames 1 -vo null -ao null $FILE | \
sed -n 's/^MOV: Movie header: start: \(.*\) end:.*/\1/p'
This starts playing a the movie file with mplayer, but just one frame,
discarding the video and audio generated, with lots of debug output
going to stdout. Another solution is to use qt-faststart, sending its
output to /dev/null and parsing the messages it spits out.
On the other hand, “qt-faststart” is not such a complicated program:
it’s just 316 lines of C code. So I thought this functionality should
be easy to port to ruby. And it turned out that the quicktime format
is not that difficult to parse, at least for the very basic
information we need to get.
A quicktime movie is made up of blocks called “atoms”. Every atom
begins with an 8 byte header, the first 4 bytes determine the size of
the atom including the header, a 32 bit unsigned integer, while the
second 4 bytes make up a 4 letter ASCII string for the atom type. As
we only want to know where the “moov” atom is we don’t need to know
what the contents of an atom look like.
So without further ado, here’s the method we use now to determine if a
video file is streamable:
def streamable?(file)
atoms = %w(free junk mdat moov pnot skip wide PICT ftyp cmov stco co64)
File.open(file) do |f|
while !f.eof?
# the size is an unsigned 32bit integer, big-endian AKA network byte order
# the type is 4 ascii characters
size, type = f.read(8).unpack("Na4")
raise ArgumentError, "unknown atom type #{type} in #{file} at #{f.pos}" unless atoms.include?(type)
return f.pos < 0xff if type == "moov"
f.seek(size - 8, IO::SEEK_CUR)
end
raise ArgumentError, "no moov atom found"
end
rescue
false
end


