2 /////////////////////////////////////////////////////////////////
3 /// getID3() by James Heinrich <info@getid3.org> //
4 // available at http://getid3.sourceforge.net //
5 // or http://www.getid3.org //
7 // FLV module by Seth Kaufman <sethØwhirl-i-gig*com> //
9 // * version 0.1 (26 June 2005) //
12 // * version 0.1.1 (15 July 2005) //
13 // minor modifications by James Heinrich <info@getid3.org> //
15 // * version 0.2 (22 February 2006) //
16 // Support for On2 VP6 codec and meta information //
17 // by Steve Webster <steve.websterØfeaturecreep*com> //
19 // * version 0.3 (15 June 2006) //
20 // Modified to not read entire file into memory //
21 // by James Heinrich <info@getid3.org> //
23 // * version 0.4 (07 December 2007) //
24 // Bugfixes for incorrectly parsed FLV dimensions //
25 // and incorrect parsing of onMetaTag //
26 // by Evgeny Moysevich <moysevichØgmail*com> //
28 // * version 0.5 (21 May 2009) //
29 // Fixed parsing of audio tags and added additional codec //
30 // details. The duration is now read from onMetaTag (if //
31 // exists), rather than parsing whole file //
32 // by Nigel Barnes <ngbarnesØhotmail*com> //
34 // * version 0.6 (24 May 2009) //
35 // Better parsing of files with h264 video //
36 // by Evgeny Moysevich <moysevichØgmail*com> //
38 // * version 0.6.1 (30 May 2011) //
39 // prevent infinite loops in expGolombUe() //
41 /////////////////////////////////////////////////////////////////
43 // module.audio-video.flv.php //
44 // module for analyzing Shockwave Flash Video files //
45 // dependencies: NONE //
47 /////////////////////////////////////////////////////////////////
49 define('GETID3_FLV_TAG_AUDIO', 8);
50 define('GETID3_FLV_TAG_VIDEO', 9);
51 define('GETID3_FLV_TAG_META', 18);
53 define('GETID3_FLV_VIDEO_H263', 2);
54 define('GETID3_FLV_VIDEO_SCREEN', 3);
55 define('GETID3_FLV_VIDEO_VP6FLV', 4);
56 define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
57 define('GETID3_FLV_VIDEO_SCREENV2', 6);
58 define('GETID3_FLV_VIDEO_H264', 7);
60 define('H264_AVC_SEQUENCE_HEADER', 0);
61 define('H264_PROFILE_BASELINE', 66);
62 define('H264_PROFILE_MAIN', 77);
63 define('H264_PROFILE_EXTENDED', 88);
64 define('H264_PROFILE_HIGH', 100);
65 define('H264_PROFILE_HIGH10', 110);
66 define('H264_PROFILE_HIGH422', 122);
67 define('H264_PROFILE_HIGH444', 144);
68 define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
70 class getid3_flv extends getid3_handler
72 public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration
74 public function Analyze() {
75 $info = &$this->getid3->info;
77 fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
79 $FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
80 $FLVheader = fread($this->getid3->fp, 5);
82 $info['fileformat'] = 'flv';
83 $info['flv']['header']['signature'] = substr($FLVheader, 0, 3);
84 $info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
85 $TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
88 if ($info['flv']['header']['signature'] != $magic) {
89 $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
91 unset($info['fileformat']);
95 $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
96 $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
98 $FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4));
99 $FLVheaderFrameLength = 9;
100 if ($FrameSizeDataLength > $FLVheaderFrameLength) {
101 fseek($this->getid3->fp, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
104 $found_video = false;
105 $found_audio = false;
107 $found_valid_meta_playtime = false;
109 $info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
110 $flv_framecount = &$info['flv']['framecount'];
111 while (((ftell($this->getid3->fp) + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) {
112 $ThisTagHeader = fread($this->getid3->fp, 16);
114 $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4));
115 $TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1));
116 $DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3));
117 $Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3));
118 $LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
119 $NextOffset = ftell($this->getid3->fp) - 1 + $DataLength;
120 if ($Timestamp > $Duration) {
121 $Duration = $Timestamp;
124 $flv_framecount['total']++;
126 case GETID3_FLV_TAG_AUDIO:
127 $flv_framecount['audio']++;
130 $info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F;
131 $info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03;
132 $info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
133 $info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01;
137 case GETID3_FLV_TAG_VIDEO:
138 $flv_framecount['video']++;
141 $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
143 $FLVvideoHeader = fread($this->getid3->fp, 11);
145 if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
146 // this code block contributed by: moysevichØgmail*com
148 $AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
149 if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
150 // read AVCDecoderConfigurationRecord
151 $configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1));
152 $AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1));
153 $profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1));
154 $lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1));
155 $numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1));
157 if (($numOfSequenceParameterSets & 0x1F) != 0) {
158 // there is at least one SequenceParameterSet
159 // read size of the first SequenceParameterSet
160 //$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
161 $spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
162 // read the first SequenceParameterSet
163 $sps = fread($this->getid3->fp, $spsSize);
164 if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red
165 $spsReader = new AVCSequenceParameterSetReader($sps);
166 $spsReader->readData();
167 $info['video']['resolution_x'] = $spsReader->getWidth();
168 $info['video']['resolution_y'] = $spsReader->getHeight();
172 // end: moysevichØgmail*com
174 } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
176 $PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
177 $PictureSizeType = $PictureSizeType & 0x0007;
178 $info['flv']['header']['videoSizeType'] = $PictureSizeType;
179 switch ($PictureSizeType) {
181 //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
182 //$PictureSizeEnc <<= 1;
183 //$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
184 //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
185 //$PictureSizeEnc <<= 1;
186 //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
188 $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2));
189 $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
190 $PictureSizeEnc['x'] >>= 7;
191 $PictureSizeEnc['y'] >>= 7;
192 $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
193 $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
197 $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3));
198 $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3));
199 $PictureSizeEnc['x'] >>= 7;
200 $PictureSizeEnc['y'] >>= 7;
201 $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
202 $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
206 $info['video']['resolution_x'] = 352;
207 $info['video']['resolution_y'] = 288;
211 $info['video']['resolution_x'] = 176;
212 $info['video']['resolution_y'] = 144;
216 $info['video']['resolution_x'] = 128;
217 $info['video']['resolution_y'] = 96;
221 $info['video']['resolution_x'] = 320;
222 $info['video']['resolution_y'] = 240;
226 $info['video']['resolution_x'] = 160;
227 $info['video']['resolution_y'] = 120;
231 $info['video']['resolution_x'] = 0;
232 $info['video']['resolution_y'] = 0;
237 $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
242 case GETID3_FLV_TAG_META:
245 fseek($this->getid3->fp, -1, SEEK_CUR);
246 $datachunk = fread($this->getid3->fp, $DataLength);
247 $AMFstream = new AMFStream($datachunk);
248 $reader = new AMFReader($AMFstream);
249 $eventName = $reader->readData();
250 $info['flv']['meta'][$eventName] = $reader->readData();
253 $copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
254 foreach ($copykeys as $sourcekey => $destkey) {
255 if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
256 switch ($sourcekey) {
259 $info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
261 case 'audiodatarate':
262 $info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
264 case 'videodatarate':
267 $info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
272 if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
273 $found_valid_meta_playtime = true;
282 fseek($this->getid3->fp, $NextOffset, SEEK_SET);
285 $info['playtime_seconds'] = $Duration / 1000;
286 if ($info['playtime_seconds'] > 0) {
287 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
290 if ($info['flv']['header']['hasAudio']) {
291 $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['audio']['audioFormat']);
292 $info['audio']['sample_rate'] = $this->FLVaudioRate($info['flv']['audio']['audioRate']);
293 $info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info['flv']['audio']['audioSampleSize']);
295 $info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
296 $info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
297 $info['audio']['dataformat'] = 'flv';
299 if (!empty($info['flv']['header']['hasVideo'])) {
300 $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['video']['videoCodec']);
301 $info['video']['dataformat'] = 'flv';
302 $info['video']['lossless'] = false;
305 // Set information from meta
306 if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
307 $info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
308 $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
310 if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
311 $info['audio']['codec'] = $this->FLVaudioFormat($info['flv']['meta']['onMetaData']['audiocodecid']);
313 if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
314 $info['video']['codec'] = $this->FLVvideoCodec($info['flv']['meta']['onMetaData']['videocodecid']);
320 public function FLVaudioFormat($id) {
321 $FLVaudioFormat = array(
322 0 => 'Linear PCM, platform endian',
325 3 => 'Linear PCM, little endian',
326 4 => 'Nellymoser 16kHz mono',
327 5 => 'Nellymoser 8kHz mono',
329 7 => 'G.711A-law logarithmic PCM',
330 8 => 'G.711 mu-law logarithmic PCM',
333 11 => false, // unknown?
334 12 => false, // unknown?
335 13 => false, // unknown?
337 15 => 'Device-specific sound',
339 return (isset($FLVaudioFormat[$id]) ? $FLVaudioFormat[$id] : false);
342 public function FLVaudioRate($id) {
343 $FLVaudioRate = array(
349 return (isset($FLVaudioRate[$id]) ? $FLVaudioRate[$id] : false);
352 public function FLVaudioBitDepth($id) {
353 $FLVaudioBitDepth = array(
357 return (isset($FLVaudioBitDepth[$id]) ? $FLVaudioBitDepth[$id] : false);
360 public function FLVvideoCodec($id) {
361 $FLVvideoCodec = array(
362 GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
363 GETID3_FLV_VIDEO_SCREEN => 'Screen video',
364 GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6',
365 GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
366 GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2',
367 GETID3_FLV_VIDEO_H264 => 'Sorenson H.264',
369 return (isset($FLVvideoCodec[$id]) ? $FLVvideoCodec[$id] : false);
377 public function AMFStream(&$bytes) {
378 $this->bytes =& $bytes;
382 public function readByte() {
383 return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
386 public function readInt() {
387 return ($this->readByte() << 8) + $this->readByte();
390 public function readLong() {
391 return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
394 public function readDouble() {
395 return getid3_lib::BigEndian2Float($this->read(8));
398 public function readUTF() {
399 $length = $this->readInt();
400 return $this->read($length);
403 public function readLongUTF() {
404 $length = $this->readLong();
405 return $this->read($length);
408 public function read($length) {
409 $val = substr($this->bytes, $this->pos, $length);
410 $this->pos += $length;
414 public function peekByte() {
416 $val = $this->readByte();
421 public function peekInt() {
423 $val = $this->readInt();
428 public function peekLong() {
430 $val = $this->readLong();
435 public function peekDouble() {
437 $val = $this->readDouble();
442 public function peekUTF() {
444 $val = $this->readUTF();
449 public function peekLongUTF() {
451 $val = $this->readLongUTF();
460 public function AMFReader(&$stream) {
461 $this->stream =& $stream;
464 public function readData() {
467 $type = $this->stream->readByte();
472 $value = $this->readDouble();
477 $value = $this->readBoolean();
482 $value = $this->readString();
487 $value = $this->readObject();
497 $value = $this->readMixedArray();
502 $value = $this->readArray();
507 $value = $this->readDate();
512 $value = $this->readLongString();
515 // XML (handled as string)
517 $value = $this->readXML();
520 // Typed object (handled as object)
522 $value = $this->readTypedObject();
527 $value = '(unknown or unsupported data type)';
534 public function readDouble() {
535 return $this->stream->readDouble();
538 public function readBoolean() {
539 return $this->stream->readByte() == 1;
542 public function readString() {
543 return $this->stream->readUTF();
546 public function readObject() {
547 // Get highest numerical index - ignored
548 // $highestIndex = $this->stream->readLong();
552 while ($key = $this->stream->readUTF()) {
553 $data[$key] = $this->readData();
555 // Mixed array record ends with empty string (0x00 0x00) and 0x09
556 if (($key == '') && ($this->stream->peekByte() == 0x09)) {
558 $this->stream->readByte();
563 public function readMixedArray() {
564 // Get highest numerical index - ignored
565 $highestIndex = $this->stream->readLong();
569 while ($key = $this->stream->readUTF()) {
570 if (is_numeric($key)) {
573 $data[$key] = $this->readData();
575 // Mixed array record ends with empty string (0x00 0x00) and 0x09
576 if (($key == '') && ($this->stream->peekByte() == 0x09)) {
578 $this->stream->readByte();
584 public function readArray() {
585 $length = $this->stream->readLong();
588 for ($i = 0; $i < $length; $i++) {
589 $data[] = $this->readData();
594 public function readDate() {
595 $timestamp = $this->stream->readDouble();
596 $timezone = $this->stream->readInt();
600 public function readLongString() {
601 return $this->stream->readLongUTF();
604 public function readXML() {
605 return $this->stream->readLongUTF();
608 public function readTypedObject() {
609 $className = $this->stream->readUTF();
610 return $this->readObject();
614 class AVCSequenceParameterSetReader {
617 public $currentBytes = 0;
618 public $currentBits = 0;
622 public function AVCSequenceParameterSetReader($sps) {
626 public function readData() {
629 $profile = $this->getBits(8); // read profile
631 $this->expGolombUe(); // read sps id
632 if (in_array($profile, array(H264_PROFILE_HIGH, H264_PROFILE_HIGH10, H264_PROFILE_HIGH422, H264_PROFILE_HIGH444, H264_PROFILE_HIGH444_PREDICTIVE))) {
633 if ($this->expGolombUe() == 3) {
636 $this->expGolombUe();
637 $this->expGolombUe();
639 if ($this->getBit()) {
640 for ($i = 0; $i < 8; $i++) {
641 if ($this->getBit()) {
642 $size = $i < 6 ? 16 : 64;
645 for ($j = 0; $j < $size; $j++) {
646 if ($nextScale != 0) {
647 $deltaScale = $this->expGolombUe();
648 $nextScale = ($lastScale + $deltaScale + 256) % 256;
650 if ($nextScale != 0) {
651 $lastScale = $nextScale;
658 $this->expGolombUe();
659 $pocType = $this->expGolombUe();
661 $this->expGolombUe();
662 } elseif ($pocType == 1) {
664 $this->expGolombSe();
665 $this->expGolombSe();
666 $pocCycleLength = $this->expGolombUe();
667 for ($i = 0; $i < $pocCycleLength; $i++) {
668 $this->expGolombSe();
671 $this->expGolombUe();
673 $this->width = ($this->expGolombUe() + 1) * 16;
674 $heightMap = $this->expGolombUe() + 1;
675 $this->height = (2 - $this->getBit()) * $heightMap * 16;
678 public function skipBits($bits) {
679 $newBits = $this->currentBits + $bits;
680 $this->currentBytes += (int)floor($newBits / 8);
681 $this->currentBits = $newBits % 8;
684 public function getBit() {
685 $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
690 public function getBits($bits) {
692 for ($i = 0; $i < $bits; $i++) {
693 $result = ($result << 1) + $this->getBit();
698 public function expGolombUe() {
699 $significantBits = 0;
700 $bit = $this->getBit();
703 $bit = $this->getBit();
705 if ($significantBits > 31) {
706 // something is broken, this is an emergency escape to prevent infinite loops
710 return (1 << $significantBits) + $this->getBits($significantBits) - 1;
713 public function expGolombSe() {
714 $result = $this->expGolombUe();
715 if (($result & 0x01) == 0) {
716 return -($result >> 1);
718 return ($result + 1) >> 1;
722 public function getWidth() {
726 public function getHeight() {
727 return $this->height;