<Website>

getID3

PHK Home

File: /lib/module.audio-video.flv.php

Size:16933
Storage flags:strip

<?php





















































define
('GETID3_FLV_TAG_AUDIO'8);
define('GETID3_FLV_TAG_VIDEO'9);
define('GETID3_FLV_TAG_META'18);

define('GETID3_FLV_VIDEO_H263'2);
define('GETID3_FLV_VIDEO_SCREEN'3);
define('GETID3_FLV_VIDEO_VP6FLV'4);
define('GETID3_FLV_VIDEO_VP6FLV_ALPHA'5);
define('GETID3_FLV_VIDEO_SCREENV2'6);
define('GETID3_FLV_VIDEO_H264'7);

define('H264_AVC_SEQUENCE_HEADER'0);
define('H264_PROFILE_BASELINE'66);
define('H264_PROFILE_MAIN'77);
define('H264_PROFILE_EXTENDED'88);
define('H264_PROFILE_HIGH'100);
define('H264_PROFILE_HIGH10'110);
define('H264_PROFILE_HIGH422'122);
define('H264_PROFILE_HIGH444'144);
define('H264_PROFILE_HIGH444_PREDICTIVE'244);

class 
getid3_flv extends getid3_handler {

const 
magic 'FLV';

public 
$max_frames 100000

public function 
Analyze() {
$info = &$this->getid3->info;

$this->fseek($info['avdataoffset']);

$FLVdataLength $info['avdataend'] - $info['avdataoffset'];
$FLVheader $this->fread(5);

$info['fileformat'] = 'flv';
$info['flv']['header']['signature'] = substr($FLVheader03);
$info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader31));
$TypeFlags getid3_lib::BigEndian2Int(substr($FLVheader41));

if (
$info['flv']['header']['signature'] != self::magic) {
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
unset(
$info['flv'], $info['fileformat']);
return 
false;
}

$info['flv']['header']['hasAudio'] = (bool) ($TypeFlags 0x04);
$info['flv']['header']['hasVideo'] = (bool) ($TypeFlags 0x01);

$FrameSizeDataLength getid3_lib::BigEndian2Int($this->fread(4));
$FLVheaderFrameLength 9;
if (
$FrameSizeDataLength $FLVheaderFrameLength) {
$this->fseek($FrameSizeDataLength $FLVheaderFrameLengthSEEK_CUR);
}
$Duration 0;
$found_video false;
$found_audio false;
$found_meta false;
$found_valid_meta_playtime false;
$tagParseCount 0;
$info['flv']['framecount'] = array('total'=>0'audio'=>0'video'=>0);
$flv_framecount = &$info['flv']['framecount'];
while (((
$this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) {
$ThisTagHeader $this->fread(16);

$PreviousTagLength getid3_lib::BigEndian2Int(substr($ThisTagHeader04));
$TagType getid3_lib::BigEndian2Int(substr($ThisTagHeader41));
$DataLength getid3_lib::BigEndian2Int(substr($ThisTagHeader53));
$Timestamp getid3_lib::BigEndian2Int(substr($ThisTagHeader83));
$LastHeaderByte getid3_lib::BigEndian2Int(substr($ThisTagHeader151));
$NextOffset $this->ftell() - $DataLength;
if (
$Timestamp $Duration) {
$Duration $Timestamp;
}

$flv_framecount['total']++;
switch (
$TagType) {
case 
GETID3_FLV_TAG_AUDIO:
$flv_framecount['audio']++;
if (!
$found_audio) {
$found_audio true;
$info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F;
$info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03;
$info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
$info['flv']['audio']['audioType'] = $LastHeaderByte 0x01;
}
break;

case 
GETID3_FLV_TAG_VIDEO:
$flv_framecount['video']++;
if (!
$found_video) {
$found_video true;
$info['flv']['video']['videoCodec'] = $LastHeaderByte 0x07;

$FLVvideoHeader $this->fread(11);

if (
$info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {


$AVCPacketType getid3_lib::BigEndian2Int(substr($FLVvideoHeader01));
if (
$AVCPacketType == H264_AVC_SEQUENCE_HEADER) {

 
$configurationVersion getid3_lib::BigEndian2Int(substr($FLVvideoHeader41));
$AVCProfileIndication getid3_lib::BigEndian2Int(substr($FLVvideoHeader51));
$profile_compatibility getid3_lib::BigEndian2Int(substr($FLVvideoHeader61));
$lengthSizeMinusOne getid3_lib::BigEndian2Int(substr($FLVvideoHeader71));
$numOfSequenceParameterSets getid3_lib::BigEndian2Int(substr($FLVvideoHeader81));

if ((
$numOfSequenceParameterSets 0x1F) != 0) {

 
 
 
$spsSize getid3_lib::LittleEndian2Int(substr($FLVvideoHeader92));

 
$sps $this->fread($spsSize);
if (
strlen($sps) == $spsSize) { 
 
$spsReader = new AVCSequenceParameterSetReader($sps);
$spsReader->readData();
$info['video']['resolution_x'] = $spsReader->getWidth();
$info['video']['resolution_y'] = $spsReader->getHeight();
}
}
}


} elseif (
$info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {

$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader32))) >> 7;
$PictureSizeType $PictureSizeType 0x0007;
$info['flv']['header']['videoSizeType'] = $PictureSizeType;
switch (
$PictureSizeType) {
case 
0:

 
 
 
 
 

$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader42)) >> 7;
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader52)) >> 7;
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
break;

case 
1:
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader43)) >> 7;
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader63)) >> 7;
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
break;

case 
2:
$info['video']['resolution_x'] = 352;
$info['video']['resolution_y'] = 288;
break;

case 
3:
$info['video']['resolution_x'] = 176;
$info['video']['resolution_y'] = 144;
break;

case 
4:
$info['video']['resolution_x'] = 128;
$info['video']['resolution_y'] = 96;
break;

case 
5:
$info['video']['resolution_x'] = 320;
$info['video']['resolution_y'] = 240;
break;

case 
6:
$info['video']['resolution_x'] = 160;
$info['video']['resolution_y'] = 120;
break;

default:
$info['video']['resolution_x'] = 0;
$info['video']['resolution_y'] = 0;
break;

}

} elseif (
$info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_VP6FLV_ALPHA) {


if (!isset(
$info['video']['resolution_x'])) { 
 
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader62));
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader72));
$info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3;
$info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3;
}


}
if (!empty(
$info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) {
$info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
}
}
break;


 case 
GETID3_FLV_TAG_META:
if (!
$found_meta) {
$found_meta true;
$this->fseek(-1SEEK_CUR);
$datachunk $this->fread($DataLength);
$AMFstream = new AMFStream($datachunk);
$reader = new AMFReader($AMFstream);
$eventName $reader->readData();
$info['flv']['meta'][$eventName] = $reader->readData();
unset(
$reader);

$copykeys = array('framerate'=>'frame_rate''width'=>'resolution_x''height'=>'resolution_y''audiodatarate'=>'bitrate''videodatarate'=>'bitrate');
foreach (
$copykeys as $sourcekey => $destkey) {
if (isset(
$info['flv']['meta']['onMetaData'][$sourcekey])) {
switch (
$sourcekey) {
case 
'width':
case 
'height':
$info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
break;
case 
'audiodatarate':
$info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
break;
case 
'videodatarate':
case 
'frame_rate':
default:
$info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
break;
}
}
}
if (!empty(
$info['flv']['meta']['onMetaData']['duration'])) {
$found_valid_meta_playtime true;
}
}
break;

default:

 break;
}
$this->fseek($NextOffset);
}

$info['playtime_seconds'] = $Duration 1000;
if (
$info['playtime_seconds'] > 0) {
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}

if (
$info['flv']['header']['hasAudio']) {
$info['audio']['codec'] = self::audioFormatLookup($info['flv']['audio']['audioFormat']);
$info['audio']['sample_rate'] = self::audioRateLookup($info['flv']['audio']['audioRate']);
$info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']);

$info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1
 
$info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false true); 
 
$info['audio']['dataformat'] = 'flv';
}
if (!empty(
$info['flv']['header']['hasVideo'])) {
$info['video']['codec'] = self::videoCodecLookup($info['flv']['video']['videoCodec']);
$info['video']['dataformat'] = 'flv';
$info['video']['lossless'] = false;
}


 if (!empty(
$info['flv']['meta']['onMetaData']['duration'])) {
$info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
if (isset(
$info['flv']['meta']['onMetaData']['audiocodecid'])) {
$info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']);
}
if (isset(
$info['flv']['meta']['onMetaData']['videocodecid'])) {
$info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']);
}
return 
true;
}


public static function 
audioFormatLookup($id) {
static 
$lookup = array(
=> 'Linear PCM, platform endian',
=> 'ADPCM',
=> 'mp3',
=> 'Linear PCM, little endian',
=> 'Nellymoser 16kHz mono',
=> 'Nellymoser 8kHz mono',
=> 'Nellymoser',
=> 'G.711A-law logarithmic PCM',
=> 'G.711 mu-law logarithmic PCM',
=> 'reserved',
10 => 'AAC',
11 => 'Speex',
12 => false
 
13 => false
 
14 => 'mp3 8kHz',
15 => 'Device-specific sound',
);
return (isset(
$lookup[$id]) ? $lookup[$id] : false);
}

public static function 
audioRateLookup($id) {
static 
$lookup = array(
=> 5500,
=> 11025,
=> 22050,
=> 44100,
);
return (isset(
$lookup[$id]) ? $lookup[$id] : false);
}

public static function 
audioBitDepthLookup($id) {
static 
$lookup = array(
=> 8,
=> 16,
);
return (isset(
$lookup[$id]) ? $lookup[$id] : false);
}

public static function 
videoCodecLookup($id) {
static 
$lookup = array(
GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
GETID3_FLV_VIDEO_SCREEN => 'Screen video',
GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6',
GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2',
GETID3_FLV_VIDEO_H264 => 'Sorenson H.264',
);
return (isset(
$lookup[$id]) ? $lookup[$id] : false);
}
}

class 
AMFStream {
public 
$bytes;
public 
$pos;

public function 
__construct(&$bytes) {
$this->bytes =& $bytes;
$this->pos 0;
}

public function 
readByte() {
return 
getid3_lib::BigEndian2Int(substr($this->bytes$this->pos++, 1));
}

public function 
readInt() {
return (
$this->readByte() << 8) + $this->readByte();
}

public function 
readLong() {
return (
$this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
}

public function 
readDouble() {
return 
getid3_lib::BigEndian2Float($this->read(8));
}

public function 
readUTF() {
$length $this->readInt();
return 
$this->read($length);
}

public function 
readLongUTF() {
$length $this->readLong();
return 
$this->read($length);
}

public function 
read($length) {
$val substr($this->bytes$this->pos$length);
$this->pos += $length;
return 
$val;
}

public function 
peekByte() {
$pos $this->pos;
$val $this->readByte();
$this->pos $pos;
return 
$val;
}

public function 
peekInt() {
$pos $this->pos;
$val $this->readInt();
$this->pos $pos;
return 
$val;
}

public function 
peekLong() {
$pos $this->pos;
$val $this->readLong();
$this->pos $pos;
return 
$val;
}

public function 
peekDouble() {
$pos $this->pos;
$val $this->readDouble();
$this->pos $pos;
return 
$val;
}

public function 
peekUTF() {
$pos $this->pos;
$val $this->readUTF();
$this->pos $pos;
return 
$val;
}

public function 
peekLongUTF() {
$pos $this->pos;
$val $this->readLongUTF();
$this->pos $pos;
return 
$val;
}
}

class 
AMFReader {
public 
$stream;

public function 
__construct(&$stream) {
$this->stream =& $stream;
}

public function 
readData() {
$value null;

$type $this->stream->readByte();
switch (
$type) {


 case 
0:
$value $this->readDouble();
break;


 case 
1:
$value $this->readBoolean();
break;


 case 
2:
$value $this->readString();
break;


 case 
3:
$value $this->readObject();
break;


 case 
6:
return 
null;
break;


 case 
8:
$value $this->readMixedArray();
break;


 case 
10:
$value $this->readArray();
break;


 case 
11:
$value $this->readDate();
break;


 case 
13:
$value $this->readLongString();
break;


 case 
15:
$value $this->readXML();
break;


 case 
16:
$value $this->readTypedObject();
break;


 default:
$value '(unknown or unsupported data type)';
break;
}

return 
$value;
}

public function 
readDouble() {
return 
$this->stream->readDouble();
}

public function 
readBoolean() {
return 
$this->stream->readByte() == 1;
}

public function 
readString() {
return 
$this->stream->readUTF();
}

public function 
readObject() {



$data = array();

while (
$key $this->stream->readUTF()) {
$data[$key] = $this->readData();
}

 if ((
$key == '') && ($this->stream->peekByte() == 0x09)) {

 
$this->stream->readByte();
}
return 
$data;
}

public function 
readMixedArray() {

 
$highestIndex $this->stream->readLong();

$data = array();

while (
$key $this->stream->readUTF()) {
if (
is_numeric($key)) {
$key = (float) $key;
}
$data[$key] = $this->readData();
}

 if ((
$key == '') && ($this->stream->peekByte() == 0x09)) {

 
$this->stream->readByte();
}

return 
$data;
}

public function 
readArray() {
$length $this->stream->readLong();
$data = array();

for (
$i 0$i $length$i++) {
$data[] = $this->readData();
}
return 
$data;
}

public function 
readDate() {
$timestamp $this->stream->readDouble();
$timezone $this->stream->readInt();
return 
$timestamp;
}

public function 
readLongString() {
return 
$this->stream->readLongUTF();
}

public function 
readXML() {
return 
$this->stream->readLongUTF();
}

public function 
readTypedObject() {
$className $this->stream->readUTF();
return 
$this->readObject();
}
}

class 
AVCSequenceParameterSetReader {
public 
$sps;
public 
$start 0;
public 
$currentBytes 0;
public 
$currentBits 0;
public 
$width;
public 
$height;

public function 
__construct($sps) {
$this->sps $sps;
}

public function 
readData() {
$this->skipBits(8);
$this->skipBits(8);
$profile $this->getBits(8); 
 if (
$profile 0) {
$this->skipBits(8);
$level_idc $this->getBits(8); 
 
$this->expGolombUe(); 
 
$this->expGolombUe(); 
 
$picOrderType $this->expGolombUe(); 
 if (
$picOrderType == 0) {
$this->expGolombUe(); 
 } elseif (
$picOrderType == 1) {
$this->skipBits(1); 
 
$this->expGolombSe(); 
 
$this->expGolombSe(); 
 
$num_ref_frames_in_pic_order_cnt_cycle $this->expGolombUe(); 
 for (
$i 0$i $num_ref_frames_in_pic_order_cnt_cycle$i++) {
$this->expGolombSe(); 
 }
}
$this->expGolombUe(); 
 
$this->skipBits(1); 
 
$pic_width_in_mbs_minus1 $this->expGolombUe(); 
 
$pic_height_in_map_units_minus1 $this->expGolombUe(); 

$frame_mbs_only_flag $this->getBits(1); 
 if (
$frame_mbs_only_flag == 0) {
$this->skipBits(1); 
 }
$this->skipBits(1); 
 
$frame_cropping_flag $this->getBits(1); 

$frame_crop_left_offset 0;
$frame_crop_right_offset 0;
$frame_crop_top_offset 0;
$frame_crop_bottom_offset 0;

if (
$frame_cropping_flag) {
$frame_crop_left_offset $this->expGolombUe(); 
 
$frame_crop_right_offset $this->expGolombUe(); 
 
$frame_crop_top_offset $this->expGolombUe(); 
 
$frame_crop_bottom_offset $this->expGolombUe(); 
 }
$this->skipBits(1); 
 

$this->width = (($pic_width_in_mbs_minus1 1) * 16) - ($frame_crop_left_offset 2) - ($frame_crop_right_offset 2);
$this->height = (($frame_mbs_only_flag) * ($pic_height_in_map_units_minus1 1) * 16) - ($frame_crop_top_offset 2) - ($frame_crop_bottom_offset 2);
}
}

public function 
skipBits($bits) {
$newBits $this->currentBits $bits;
$this->currentBytes += (int)floor($newBits 8);
$this->currentBits $newBits 8;
}

public function 
getBit() {
$result = (getid3_lib::BigEndian2Int(substr($this->sps$this->currentBytes1)) >> ($this->currentBits)) & 0x01;
$this->skipBits(1);
return 
$result;
}

public function 
getBits($bits) {
$result 0;
for (
$i 0$i $bits$i++) {
$result = ($result << 1) + $this->getBit();
}
return 
$result;
}

public function 
expGolombUe() {
$significantBits 0;
$bit $this->getBit();
while (
$bit == 0) {
$significantBits++;
$bit $this->getBit();

if (
$significantBits 31) {

 return 
0;
}
}
return (
<< $significantBits) + $this->getBits($significantBits) - 1;
}

public function 
expGolombSe() {
$result $this->expGolombUe();
if ((
$result 0x01) == 0) {
return -(
$result >> 1);
} else {
return (
$result 1) >> 1;
}
}

public function 
getWidth() {
return 
$this->width;
}

public function 
getHeight() {
return 
$this->height;
}
}

For more information about the PHK package format: http://phk.tekwire.net