diff --git a/include/FLV/FLV/Exceptions.php b/include/FLV/FLV/Exceptions.php new file mode 100755 index 0000000..bdb42e2 --- /dev/null +++ b/include/FLV/FLV/Exceptions.php @@ -0,0 +1,48 @@ + \ No newline at end of file diff --git a/include/FLV/FLV/FLV.php b/include/FLV/FLV/FLV.php new file mode 100755 index 0000000..32706e3 --- /dev/null +++ b/include/FLV/FLV/FLV.php @@ -0,0 +1,172 @@ +fp = @fopen( $fname, 'r' ); + if (! $this->fp) + throw( new FLV_FileException('Unable to open the file') ); + + $hdr = fread( $this->fp, self::FLV_HEADER_SIZE ); + + //check file header signature + if ( substr($hdr, 0, 3) !== self::FLV_HEADER_SIGNATURE ) + throw( new FLV_NotValidFileException('The header signature does not match') ); + + + $this->version = ord($hdr[3]); + $this->hasVideo = (bool)(ord($hdr[4]) & 0x01); + $this->hasAudio = (bool)(ord($hdr[4]) & 0x04); + + $this->bodyOfs = (ord($hdr[5]) << 24) + + (ord($hdr[6]) << 16) + + (ord($hdr[7]) << 8) + + (ord($hdr[8])); + + fseek( $this->fp, $this->bodyOfs ); + + $this->eof = false; + + return true; + } + + /** + * Close a previously open FLV file + * + */ + function close() + { + fclose( $this->fp ); + } + + + /** + * Returns the next tag from the open file + * + * @throws FLV_CorruptedFileException + * + * @param array $skipTagTypes The tag types contained in this array won't be examined + * @return FLV_Tag_Generic or one of its descendants + */ + function getTag( $skipTagTypes = false ) + { + static $cnt = 0; + + if ($this->eof) return null; + + + $hdr = fread( $this->fp, self::TAG_HEADER_SIZE ); + if (strlen($hdr) < self::TAG_HEADER_SIZE) + { + $this->eof = true; + return null; + } + + /* + //DEV: Some files seem to don't store this value! + // check against corrupted files + $prevTagSize = unpack( 'Nprev', $hdr ); + if ($prevTagSize['prev'] != $this->lastTagSize) + { + throw( + new FLV_CorruptedFileException( + sprintf( "Previous tag size check failed. Actual size is %d but defined size is %d", + $this->lastTagSize, + $prevTagSize['prev'] + ) + ) + ); + } + */ + + // Get the tag object by skiping the first 4 bytes which tell the previous tag size + $tag = FLV_Tag::getTag( substr( $hdr, 4 ) ); + + + // Read at most MAX_TAG_BODY_SIZE bytes of the body + $bytesToRead = min( self::MAX_TAG_BODY_SIZE, $tag->size ); + $tag->setBody( fread( $this->fp, $bytesToRead ) ); + + // Check if the tag body has to be processed + if ( is_array($skipTagTypes) && !in_array( $tag->type, $skipTagTypes ) ) + { + $tag->analyze(); + } + + // If the tag was skipped or the body size was larger than MAX_TAG_BODY_SIZE + if ($tag->size > $bytesToRead) + { + fseek( $this->fp, $tag->size-$bytesToRead, SEEK_CUR ); + } + + $this->lastTagSize = $tag->size + self::TAG_HEADER_SIZE - 4; + + return $tag; + } + + + /** + * Returns the offset from the start of the file of the last processed tag + * + * @return the offset + */ + function getTagOffset() + { + return ftell($this->fp) - $this->lastTagSize; + } +} + +?> \ No newline at end of file diff --git a/include/FLV/FLV/Tag.php b/include/FLV/FLV/Tag.php new file mode 100755 index 0000000..d0855e1 --- /dev/null +++ b/include/FLV/FLV/Tag.php @@ -0,0 +1,57 @@ + \ No newline at end of file diff --git a/include/FLV/FLV/Tag/Audio.php b/include/FLV/FLV/Tag/Audio.php new file mode 100755 index 0000000..9751926 --- /dev/null +++ b/include/FLV/FLV/Tag/Audio.php @@ -0,0 +1,59 @@ +body ); + + $this->codec = $bits->getInt(4); + $this->frequency = $bits->getInt(2); + $this->depth = $bits->getInt(1); + $this->mode = $bits->getInt(1); + } +} +?> \ No newline at end of file diff --git a/include/FLV/FLV/Tag/Data.php b/include/FLV/FLV/Tag/Data.php new file mode 100755 index 0000000..9e80b59 --- /dev/null +++ b/include/FLV/FLV/Tag/Data.php @@ -0,0 +1,38 @@ +body ); + + $this->name = $amf->getItem(); + $this->value = $amf->getItem(); + } +} +?> \ No newline at end of file diff --git a/include/FLV/FLV/Tag/Generic.php b/include/FLV/FLV/Tag/Generic.php new file mode 100755 index 0000000..974549a --- /dev/null +++ b/include/FLV/FLV/Tag/Generic.php @@ -0,0 +1,59 @@ +type = ord($hdr[$p++]); + + $this->size = (ord($hdr[$p++]) << 16) + + (ord($hdr[$p++]) << 8) + + (ord($hdr[$p++])); + + $this->timestamp = (ord($hdr[$p++]) << 16) + + (ord($hdr[$p++]) << 8) + + (ord($hdr[$p++])) + + (ord($hdr[$p++]) << 24); + + $this->streamId = (ord($hdr[$p++]) << 16) + + (ord($hdr[$p++]) << 8) + + (ord($hdr[$p++])); + } + + function setBody( $body ) + { + $this->body = $body; + } + + function analyze() + { + // nothing to do for a generic tag + } +} + +?> \ No newline at end of file diff --git a/include/FLV/FLV/Tag/Video.php b/include/FLV/FLV/Tag/Video.php new file mode 100755 index 0000000..bf48de1 --- /dev/null +++ b/include/FLV/FLV/Tag/Video.php @@ -0,0 +1,116 @@ +body ); + + $this->frametype = $bits->getInt( 4 ); + + $this->codec = $bits->getInt(4); + switch ($this->codec) + { + case self::CODEC_SORENSON_H263 : + + //skip video packet header + $bits->seek( 17+5+8, SEEK_CUR ); + + switch ($bits->getInt(3)) + { + case 0x00: + $this->width = $bits->getInt(8); + $this->height = $bits->getInt(8); + break; + case 0x01: + $this->width = $bits->getInt(16); + $this->height = $bits->getInt(16); + break; + case 0x02: //CIF + $this->width = 352; + $this->height = 288; + break; + case 0x03: //QCIF + $this->width = 176; + $this->height = 155; + break; + case 0x04: //SQCIF + $this->width = 128; + $this->height = 96; + break; + case 0x05: + $this->width = 320; + $this->height = 240; + break; + case 0x06: + $this->width = 160; + $this->height = 120; + break; + } + break; + + /* TODO: not tested */ + case self::CODEC_SCREENVIDEO_2 : + + $this->width = $bits->getInt(12); + $this->height = $bits->getInt(12); + + break; + + // format layout taken from libavcodec project (http://ffmpeg.mplayerhq.hu/) + case self::CODEC_ON2_VP6 : + case self::CODEC_ON2_VP6ALPHA : + + $adjW = $bits->getInt(4); + $adjH = $bits->getInt(4); + $mode = $bits->getInt(1); + if ($mode === 0) + { + $bits->seek(15, SEEK_CUR); + $this->height = $bits->getInt(8) * 16 - $adjH; + $this->width = $bits->getInt(8) * 16 - $adjW; + } + + break; + } + } +} + +?> \ No newline at end of file diff --git a/include/FLV/FLV/Util/AMFSerialize.php b/include/FLV/FLV/Util/AMFSerialize.php new file mode 100755 index 0000000..7d0aac9 --- /dev/null +++ b/include/FLV/FLV/Util/AMFSerialize.php @@ -0,0 +1,143 @@ +isLittleEndian = ( pack('s', 1) == pack('v', 1) ); + } + + /** + * Serializes a PHP variable into an AMF stream + * + * @param mixed $var The variable to serialize + * @param bool $skipMark if true won't add the datatype mark + * @return The AMF stream + */ + function serialize( $var, $skipMark = false ) + { + // process objects as hashed arrays + if (is_object($var)) + $var = (array)$var; + + if (is_array($var)) + { + // find out if the array is numeric or associative + $numeric = true; + foreach ( $var as $k=>$v ) + { + if (!is_numeric($k)) + { + $numeric = false; + break; + } + } + + if ($numeric) + { + $data = ($skipMark?'':"\x0A") . pack('N', count($var)); + foreach ( $var as $v ) + { + $data .= $this->serialize( $v ); + } + } else { + + $data = ($skipMark?'':"\x08") . pack('N', count($var)); + foreach ( $var as $k=>$v ) + { + $data .= $this->serialize((string)$k, true); + $data .= $this->serialize($v); + } + // end of sequence mark : empty string and 0x09 byte + $data .= $this->serialize('', true); + $data .= "\x09"; + } + + return $data; + + } else if (is_null($var)) { + + return ($skipMark?'':"\x05"); + + } else if (is_bool($var)) { + + return ($skipMark?'':"\x01") . ( $var ? "\x01" : "\x00" ); + + } else if (is_numeric($var)) { + + $number .= pack('d', $var); + + //reverse bytes if we are in little-endian hardware + if ($this->isLittleEndian) + { + $number = strrev( $number ); + } + + return ($skipMark?'':"\x00") . $number; + + } else if (is_string($var)) { + + // check for a date + if (preg_match('/^([0-9]{4})-?([0-9]{2})-?([0-9]{2})T([0-9]{2}):?([0-9]{2}):?([0-9]{2})(?:\.([0-9]{1,3}))?([Z+-])([0-9:]*)$/', trim($var), $m)) + { + $seconds = mktime( $m[4], $m[5], $m[6], $m[2], $m[3], $m[1] ); + $ms = $seconds * 1000 + $m[7]; + + if ($m[9]) + { + $ls = explode(':', $m[9]); + $tz = ($m[9]=='-' ? '-' : '') + $ls[0] * 60 + $ls[1]; + $tz = pack('s', $tz); + if ($this->isLittleEndian) + $tz = strrev($tz); + } + + return ($skipMark?'':"\x0B") . $this->serialize((float)$ms, true) . pack('n', $tz); + } + + //we could push this upto 65536 I think but I feel safer like this + if (strlen($var) < 32768) + { + return ($skipMark?'':"\x02") . pack('n', strlen($var)) . $var; + } else { + return ($skipMark?'':"\x0C") . pack('N', strlen($var)) . $var; + } + } else { + + //if the datatype is not supported use a null value + return $this->serialize( NULL ); + + } + } +} +?> \ No newline at end of file diff --git a/include/FLV/FLV/Util/AMFUnserialize.php b/include/FLV/FLV/Util/AMFUnserialize.php new file mode 100755 index 0000000..ee915bb --- /dev/null +++ b/include/FLV/FLV/Util/AMFUnserialize.php @@ -0,0 +1,306 @@ +isLittleEndian = ( pack('s', 1) == pack('v', 1) ); + + $this->setPayload($payload); + } + + /** + * Set a new AMF byte stream to process + * + * @param string $payload The AMF byte stream + */ + function setPayload( $payload ) + { + $this->data = $payload; + $this->pos = 0; + } + + /** + * Seeks into an specified offset in the AMF byte stream. It supports + * the same seeking modes as PHP's native fseek() + * + * @param int $offset Offset in bytes + * @param int $whence Seeking mode: SEEK_SET, SEEK_CUR, SEEK_END + * @return position from the start of the stream or false on failure + */ + function seek( $offset, $whence = SEEK_SET) + { + switch ($whence) { + case SEEK_SET: + if ($offset < strlen($this->data) && $offset >= 0) + { + $this->pos = $offset; + return $this->pos; + } + break; + + case SEEK_CUR: + if ($offset >= 0 && $this->pos+$offset < strlen($this->data)) + { + $this->pos += $offset; + return $this->pos; + } + break; + + case SEEK_END: + if ($offset <= 0 && strlen($this->data) + $offset >= 0) { + $this->pos = strlen($this->data) + $offset; + return $this->pos; + } + break; + } + + return false; + } + + /** + * Unserializes a boolean value + * + * @return true or false + */ + function getBoolean() + { + return $this->data[$this->pos++] > 0; + } + + /** + * Unserializes an UTF string which size is already known + * + * @param int $size The string size in bytes + * @return the string + * @access private + */ + private function getSizedString( $size ) + { + if ($size > 0) + { + $val = substr( $this->data, $this->pos, $size ); + $this->pos += $size; + return $val; + } else { + return ''; + } + } + + /** + * Unserializes an UTF string + * + * @return the string + */ + function getString() + { + //get string length + $size = (ord($this->data[$this->pos++]) << 8) + + ord($this->data[$this->pos++]); + + return $this->getSizedString( $size ); + } + + /** + * Unserializes an UTF string which can exceed the 64Kb length + * + * @return the string + */ + function getLongString() + { + $size = (ord($this->data[$this->pos++]) << 24) + + (ord($this->data[$this->pos++]) << 16) + + (ord($this->data[$this->pos++]) << 8) + + ord($this->data[$this->pos++]); + + return $this->getSizedString($size); + } + + /** + * Unserializes a number (always a double) + * + * @return the number + */ + function getNumber() + { + //read the number + $number = substr( $this->data, $this->pos, 8 ); + $this->pos += 8; + + //reverse bytes if we are in little-endian harware + if ($this->isLittleEndian) + { + $number = strrev( $number ); + } + + $tmp = unpack('dnum', $number); + + return $tmp['num']; + } + + /** + * Unserializes a numeric array + * + * @return the numeric array + */ + function getArray() + { + // item count + $cnt = (ord($this->data[$this->pos++]) << 24) + + (ord($this->data[$this->pos++]) << 16) + + (ord($this->data[$this->pos++]) << 8) + + ord($this->data[$this->pos++]); + + $arr = array(); + for ($i=0; $i<$cnt; $i++) + { + $arr[] = $this->getItem(); + } + + return $arr; + } + + /** + * Unserializes an Ecma compatible array, which is actually hash table + * + * @return the hash array + */ + function getEcmaArray() + { + // skip the item count, we'll use the terminator + $this->pos += 4; + + return $this->getObject(); + } + + + /** + * Unserializes an object as a hashed array + * + * @return the hash array + */ + function getObject() + { + $arr = array(); + do { + //fetch the key and cast it to a number if it's numeric + $key = $this->getString(); + if (is_numeric($key)) + $key = (float)$key; + + //check for the end of sequence mark + if ( ord($this->data[$this->pos]) == 0x09 ) + { + $this->pos++; + break; + } + + $arr[$key] = $this->getItem(); + + } while ( $this->pos < strlen($this->data) ); + + return $arr; + } + + /** + * Unserializes a date + * + * @return a string with the date in ISO 8601 format + */ + function getDate() + { + //64bit unsigned int with ms since 1/Jan/1970 + $ms = $this->getNumber(); + + //16bit signed int with local time offset in minuttes from UTC + $ofs = (ord($this->data[$this->pos++]) << 8) + ord($this->data[$this->pos++]); + if ($ofs > 720) + $ofs = -(65536 - $ofs); + $ofs = -$ofs; + + $date = date( 'Y-m-d\TH:i:s', floor($ms/1000) ) . '.' . str_pad( $ms % 1000, 3, '0', STR_PAD_RIGHT); + if ($ofs > 0) + return $date . '+' . str_pad( floor($ofs/60), 2, '0', STR_PAD_LEFT ) . ':' . str_pad( $ofs % 60, 2, '0', STR_PAD_LEFT ); + else if ($ofs < 0) + return $date . '-' . str_pad( floor($ofs/60), 2, '0', STR_PAD_LEFT ) . ':' . str_pad( $ofs % 60, 2, '0', STR_PAD_LEFT ); + else + return $date . 'Z'; + } + + /** + * Utility method which finds out the type of data to unserialize + * + * @throws FLV_UnknownAMFTypeException + * + * @return the unserialized variable + */ + function getItem() + { + switch (ord($this->data[$this->pos++])) + { + case 0x00: + return $this->getNumber(); + break; + case 0x01: + return $this->getBoolean(); + break; + case 0x02: + return $this->getString(); + break; + case 0x03: + return $this->getObject(); + break; + case 0x05: + return NULL; + break; + case 0x08: + return $this->getEcmaArray(); + break; + case 0x0A: + return $this->getArray(); + break; + case 0x0B: //11 Date + return $this->getDate(); + break; + case 0x0C: //12 + return $this->getLongString(); + default: + throw( new FLV_UnknownAMFTypeException( 'Unknown AMF datatype ' . ord($this->data[$this->pos-1]) ) ); + } + } + +} +?> \ No newline at end of file diff --git a/include/FLV/FLV/Util/BitStreamReader.php b/include/FLV/FLV/Util/BitStreamReader.php new file mode 100755 index 0000000..64d84ae --- /dev/null +++ b/include/FLV/FLV/Util/BitStreamReader.php @@ -0,0 +1,118 @@ +setPayload( $data ); + } + + /** + * Sets the binary stream to use + * + * @param string $data The binary string + */ + function setPayload( $data ) + { + $this->data = $data; + $this->pos = 0; + $this->bits = ''; + $this->ofs = 0; + } + + /** + * Makes sure we have the requested number of bits in the working buffer + * + * @access private + * @param int $cnt The number of bits needed + */ + private function fetch( $cnt ) + { + // Either we already have the needed bits in the buffer or we rebuild it + if ($this->pos < $this->ofs*8 || + $this->pos + $cnt > $this->ofs*8 + strlen($this->bits) ) + { + $this->bits = ''; + $this->ofs = FLOOR($this->pos/8); + for ($i = $this->ofs; $i <= $this->ofs + CEIL($cnt/8); $i++ ) + { + $this->bits .= str_pad( decbin(ord($this->data[$i])), 8, '0', STR_PAD_LEFT ); + } + } + } + + /** + * Consume an integer from an arbitrary number of bits in the stream + * + * @param int $cnt Length in bits of the integer + */ + function getInt( $cnt ) + { + $this->fetch( $cnt ); + + $ret = bindec( substr($this->bits, $this->pos-($this->ofs << 3), $cnt) ); + $this->pos += $cnt; + return $ret; + } + + /** + * Seeks into the bit stream in a similar way to fseek() + * + * @param int $cnt Number of bits to seek + * @param int $whence Either SEEK_SET (default), SEEK_CUR or SEEK_END + */ + function seek( $ofs, $whence = SEEK_SET ) + { + switch ($whence) + { + case SEEK_SET: + $this->pos = $ofs; + break; + case SEEK_CUR: + $this->pos += $ofs; + break; + case SEEK_END: + $this->pos = strlen($this->data)*8 + $ofs; + break; + } + + if ($this->pos < 0) + $this->pos = 0; + elseif ($this->pos > strlen($this->data)*8) + $this->pos = strlen($this->data)*8; + } +} + +?> \ No newline at end of file diff --git a/include/FLV/play.php b/include/FLV/play.php new file mode 100755 index 0000000..07bbd74 --- /dev/null +++ b/include/FLV/play.php @@ -0,0 +1,213 @@ +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("
The following exception was detected while trying to open a FLV file:\n" . $e->getMessage() . ""); +} + +//Here we should cache the result and use ->setMetaData() instead +$start = microtime(true); +$flv->computeMetaData(); +$end = microtime(true); +//echo "
" . print_r($flv->getMetaData(), true) . ""; +header('Content-type: flv-application/octet-stream'); +header('Content-Disposition: attachment; filename="out.flv"'); +$flv->play(0); + + +$flv->close(); + + + +?> \ No newline at end of file diff --git a/include/FLV/test.flv b/include/FLV/test.flv new file mode 100755 index 0000000..fcfa71d --- /dev/null +++ b/include/FLV/test.flv Binary files differ diff --git a/include/FLV/test.flv.meta b/include/FLV/test.flv.meta new file mode 100755 index 0000000..510803a --- /dev/null +++ b/include/FLV/test.flv.meta Binary files differ diff --git a/include/FLV/test1.flv b/include/FLV/test1.flv new file mode 100755 index 0000000..fcfa71d --- /dev/null +++ b/include/FLV/test1.flv Binary files differ diff --git a/include/FLV/test2.php b/include/FLV/test2.php new file mode 100755 index 0000000..4fc671a --- /dev/null +++ b/include/FLV/test2.php @@ -0,0 +1,154 @@ +open( FILENAME ); +} catch (Exception $e) { + die("
The following exception was detected while trying to open a FLV file:\n" . $e->getMessage() . ""); +} + + +$meta = array(); +$meta['metadatacreator'] = 'FLV Tools for PHP v0.1 by DrSlump'; +$meta['metadatadate'] = gmdate('Y-m-d\TH:i:s') . '.000Z'; +$meta['keyframes'] = array(); +$meta['keyframes']['filepositions'] = array(); +$meta['keyframes']['times'] = array(); + +$skipTagTypes = array(); + +try { + while ($tag = $flv->getTag( $skipTagTypes )) + { + $ts = number_format($tag->timestamp/1000, 3); + + if ($tag->timestamp > 0) + $meta['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) + { + $meta['keyframes']['filepositions'][] = $flv->getTagOffset(); + $meta['keyframes']['times'][] = $ts; + } + + if ( !in_array(FLV_TAG::TYPE_VIDEO, $skipTagTypes) ) + { + $meta['width'] = $tag->width; + $meta['height'] = $tag->height; + $meta['videocodecid'] = $tag->codec; + array_push( $skipTagTypes, FLV_Tag::TYPE_VIDEO ); + } + + break; + + case FLV_Tag::TYPE_AUDIO : + + if ($ts - $oldTs > AUDIO_FRAME_INTERVAL) + { + $meta['audioframes']['filepositions'][] = $flv->getTagOffset(); + $meta['audioframes']['times'][] = $ts; + $oldTs = $ts; + } + + if ( !in_array( FLV_Tag::TYPE_AUDIO, $skipTagTypes) ) + { + $meta['audiocodecid'] = $tag->codec; + $meta['audiofreqid'] = $tag->frequency; + $meta['audiodepthid'] = $tag->depth; + $meta['audiomodeid'] = $tag->mode; + + array_push( $skipTagTypes, FLV_Tag::TYPE_AUDIO ); + } + break; + case FLV_Tag::TYPE_DATA : + if ($tag->name == 'onMetaData') + { + $fileMetaPos = $pos; + $fileMetaSize = $tag->size; + $fileMeta = $tag->value; + } + break; + } + + //Does it actually help with memory allocation? + unset($tag); + } +} +catch (Exception $e) +{ + echo "
The following error took place while analyzing the file:\n" . $e->getMessage() . ""; + $flv->close(); + die(1); +} + +$flv->close(); + + +$end = microtime(true); +echo "
"; print_r($meta); echo ""; + + +//if the metadata is pressent in the file merge it with the generated one +if (!empty($fileMeta)) +{ + $meta = array_merge( $fileMeta, $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); + +echo "LEN: " . strlen($serMeta) . "
"; print_r($data); echo ""; +*/ +?> \ No newline at end of file