Sony HDV "video walkman" | Main | Connect a CCU-TX50 to a Telex intercom

July 30, 2007

Dynamic Flash metadata injection with PHP

Please note, this blog has been archived and now lives at www.discretecosine.com

So, between the summer lull in industry news and my own globe hopping, the blog has been pretty sparse lately. I think things will now begin to normalize, and I figured I'd start it off with a bit of info about a project I've been working on.

First, some history. Streaming flash video normally requires an expensive Flash Media Server license. Otherwise, you're limited to progressive download from http. Some very clever folks have, in the past, figured out how to do streaming over http using php. They accomplish this by adding a bunch of metadata in the flv file which cross references keyframe timestamps with byte offsets. So, if you have a keyframe 50 seconds into your video, they'd include where in the physical file data to find that. This allows you to seek in the video file without having to decode the whole flash video stream - you just need to read the metadata and do an fseek to an appropriate offset.

It gets slightly more complex, in that you need to also rebuild a proper flash header to that the file still has the right format, but for the most part that's not too difficult either.

There are a number of projects out there to make this pretty easy. Start by taking a look at the phpstream project. These solutions all require you to do the metadata injection with a separate tool, either the closed-source and Windows-only flvmdi, or the open-source, ruby-based flvtool2. Neither of these is optimal if you're looking to either do injection dynamically, or integrate injection into an existing automation workflow.

Luckily, there is another project, someone neglected, called flv4php. It implements many of the necessary FLV-parsing routines in php, allowing you to build your metadata array directly within php and write it back to the FLV file, or store it separately. You can even do this at runtime, if you're so inclined. I'd recommend against that particular approach if you're dealing with long flash videos, as there is a significant amount of processing overhead involved.

If you browse around the source, you'll find a php4 and php5 version of flv4php. The php4 version has many more features, but the php5 version has sample code for implementing metadata injection. Take a look at the test2.php code to get started. However, for long files, replace the line that says

$ts = number_format($tag->timestamp/1000, 3);

with:

$ts = $tag->timestamp/1000;

To prevent php from adding commas to your timestamps and thus breaking flash.

The test2.php code writes out a .meta file, containing the metadata for your video. That leaves the issue of how to read that data back in and inject it appropriately. That code is below.

To actually view streaming FLVs, you can use JeroenWijering's free flv player. Take a look at the source if you're curious how it all works on the client side.

Anyways, here's how to playback a .meta file in conjuction with an FLV file. $streamPos is the offset within the video that we're seeking to (it comes from Flash as a byte offset already). A bunch of this was stolen from the php4 tree of flv4php.

$fp = fopen( $targetFile, 'r' );
header('Content-type: flv-application/octet-stream\n');
header('Content-Disposition: attachment; filename="' . $fakeName . '"');
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

fseek($fp, 0);

$FLV_HEADER_SIZE = 9;
$hdr = fread( $fp, $FLV_HEADER_SIZE );
fseek($fp, 0);
$bodyOfs = (ord($hdr[5]) << 24) + (ord($hdr[6]) << 16) + (ord($hdr[7]) << 8) + (ord($hdr[8]));

echo fread($fp, $bodyOfs + 4);

$metadataFile = $targetFile . ".meta";
$metad= file_get_contents($metadataFile);
echo $metad;
$chunkSize = 4096;
$skippedOrigMeta = empty($origMetaSize);
if($streamPos == 0) {
}
else {
fseek($fp, $streamPos);
}
while (! feof($fp))
{
// if the original metadata is present and not yet skipped...
if (! $skippedOrigMeta)
{
$pos = ftell($fp);
// check if we are going to output it in this loop step
if ( $pos <= $origMetaOfs && $pos + $chunkSize > $origMetaOfs )
{
// output the bytes just before the original metadata tag
if ($origMetaOfs - $pos > 0)
echo fread($fp, $origMetaOfs - $pos);
// position the file pointer just after the metadata tag
fseek($fp, $origMetaOfs + $origMetaSize);
$skippedOrigMeta = true;
continue;
}
}
echo fread($fp, $chunkSize);
}
fclose($fp);

Posted by at July 30, 2007 9:12 AM | Tutorials

Comments

test

Posted by: test at July 31, 2007 8:47 AM

How can i do the reverse step : extracting keyframes to php array ?

Posted by: Eric at December 11, 2007 7:36 AM