<?php
include_once 'FLV/FLV.php';
class MyFLV extends FLV {
/**
* On audio-only files the frame index will use this as minimum gap
*/
private $audioFrameGap = 3;
private $origMetaOfs = 0;
private $origMetaSize = 0;
private $origMetaData;
private $compMetaData;
function computeMetaData()
{
$this->compMetaData = array();
$this->compMetaData['metadatacreator'] = 'FLV Tools for PHP v0.1 by DrSlump';
$this->compMetaData['metadatadate'] = gmdate('Y-m-d\TH:i:s') . '.000Z';
$this->compMetaData['keyframes'] = array();
$this->compMetaData['keyframes']['filepositions'] = array();
$this->compMetaData['keyframes']['times'] = array();
$this->origMetaOfs = 0;
$this->origMetaSize = 0;
$this->origMetaData = null;
$skipTagTypes = array();
while ($tag = $this->getTag( $skipTagTypes ))
{
// pre-calculate the timestamp as seconds
$ts = number_format($tag->timestamp/1000, 3);
if ($tag->timestamp > 0)
$this->compMetaData['lasttimestamp'] = $ts;
switch ($tag->type)
{
case FLV_Tag::TYPE_VIDEO :
//Optimization, extract the frametype without analyzing the tag body
if ((ord($tag->body[0]) >> 4) == FLV_Tag_Video::FRAME_KEYFRAME)
{
$this->compMetaData['keyframes']['filepositions'][] = $this->getTagOffset();
$this->compMetaData['keyframes']['times'][] = $ts;
}
if ( !in_array(FLV_TAG::TYPE_VIDEO, $skipTagTypes) )
{
$this->compMetaData['width'] = $tag->width;
$this->compMetaData['height'] = $tag->height;
$this->compMetaData['videocodecid'] = $tag->codec;
//Processing one video tag is enough
array_push( $skipTagTypes, FLV_Tag::TYPE_VIDEO );
}
break;
case FLV_Tag::TYPE_AUDIO :
//Save audio frame positions when there is no video
if (!$flv->hasVideo && $ts - $oldTs > $this->audioFrameGap)
{
$this->compMetaData['keyframes']['filepositions'][] = $this->getTagOffset();
$this->compMetaData['keyframes']['times'][] = $ts;
$oldTs = $ts;
}
if ( !in_array( FLV_Tag::TYPE_AUDIO, $skipTagTypes) )
{
$this->compMetaData['audiocodecid'] = $tag->codec;
$this->compMetaData['audiofreqid'] = $tag->frequency;
$this->compMetaData['audiodepthid'] = $tag->depth;
$this->compMetaData['audiomodeid'] = $tag->mode;
//Processing one audio tag is enough
array_push( $skipTagTypes, FLV_Tag::TYPE_AUDIO );
}
break;
case FLV_Tag::TYPE_DATA :
if ($tag->name == 'onMetaData')
{
$this->origMetaOfs = $this->getTagOffset();
$this->origMetaSize = $tag->size + self::TAG_HEADER_SIZE;
$this->origMetaData = $tag->value;
}
break;
}
//Does this actually help with memory allocation?
unset($tag);
}
if (! empty($this->compMetaData['keyframes']['times']))
$this->compMetaData['lastkeyframetimestamp'] = $this->compMetaData['keyframes']['times'][ count($this->compMetaData['keyframes']['times'])-1 ];
$this->compMetaData['duration'] = $this->compMetaData['lasttimestamp'];
return $this->compMetaData;
}
function setMetaData( $metadata, $origMetaOfs = 0, $origMetaSize = 0 )
{
$this->compMetaData = $metadata;
$this->origMetaOfs = $origMetaOfs;
$this->origMetaSize = $origMetaSize;
}
function getMetaData()
{
if (! is_array($this->origMetaData))
return $this->compMetaData;
else
return array_merge( $this->origMetaData, $this->compMetaData );
}
function play( $from = 0 )
{
fseek($this->fp, 0);
// get original file header just in case it has any special flag
echo fread($this->fp, $this->bodyOfs + 4);
// output the metadata if available
$meta = $this->getMetaData();
if (! empty($meta))
{
//serialize the metadata as an AMF stream
include_once 'FLV/Util/AMFSerialize.php';
$amf = new FLV_Util_AMFSerialize();
$serMeta = $amf->serialize('onMetaData');
$serMeta.= $amf->serialize($meta);
//Data tag mark
$out = pack('C', FLV_Tag::TYPE_DATA);
//Size of the data tag (BUG: limited to 64Kb)
$out.= pack('Cn', 0, strlen($serMeta));
//Timestamp
$out.= pack('N', 0);
//StreamID
$out.= pack('Cn', 0, 0);
echo $out;
echo $serMeta;
// PrevTagSize for the metadata
echo pack('N', strlen($serMeta) + strlen($out) );
}
$chunkSize = 4096;
$skippedOrigMeta = empty($this->origMetaSize);
while (! feof($this->fp))
{
// if the original metadata is pressent and not yet skipped...
if (! $skippedOrigMeta)
{
$pos = ftell($this->fp);
// check if we are going to output it in this loop step
if ( $pos <= $this->origMetaOfs &&
$pos + $chunkSize > $this->origMetaOfs )
{
// output the bytes just before the original metadata tag
if ($this->origMetaOfs - $pos > 0)
echo fread($this->fp, $this->origMetaOfs - $pos);
// position the file pointer just after the metadata tag
fseek($this->fp, $this->origMetaOfs + $this->origMetaSize);
$skippedOrigMeta = true;
continue;
}
}
echo fread($this->fp, $chunkSize);
}
}
}
$flv = new MyFLV();
try {
$flv->open( 'test1.flv' );
} catch (Exception $e) {
die("<pre>The following exception was detected while trying to open a FLV file:\n" . $e->getMessage() . "</pre>");
}
//Here we should cache the result and use ->setMetaData() instead
$start = microtime(true);
$flv->computeMetaData();
$end = microtime(true);
//echo "<hr/>EXTRACT METADATA PROCESS TOOK " . number_format(($end-$start), 2) . " seconds<br/>";
//echo "<pre>" . print_r($flv->getMetaData(), true) . "</pre>";
header('Content-type: flv-application/octet-stream');
header('Content-Disposition: attachment; filename="out.flv"');
$flv->play(0);
$flv->close();
?>