1<?php
2/////////////////////////////////////////////////////////////////
3/// getID3() by James Heinrich <info@getid3.org>               //
4//  available at http://getid3.sourceforge.net                 //
5//            or http://www.getid3.org                         //
6//                                                             //
7//  FLV module by Seth Kaufman <seth@whirl-i-gig.com>          //
8//                                                             //
9//  * version 0.1 (26 June 2005)                               //
10//                                                             //
11//  minor modifications by James Heinrich <info@getid3.org>    //
12//  * version 0.1.1 (15 July 2005)                             //
13//                                                             //
14//  Support for On2 VP6 codec and meta information by          //
15//  Steve Webster <steve.webster@featurecreep.com>             //
16//  * version 0.2 (22 February 2006)                           //
17//                                                             //
18//  Modified to not read entire file into memory               //
19//  by James Heinrich <info@getid3.org>                        //
20//  * version 0.3 (15 June 2006)                               //
21//                                                             //
22/////////////////////////////////////////////////////////////////
23//                                                             //
24// module.audio-video.flv.php                                  //
25// module for analyzing Shockwave Flash Video files            //
26// dependencies: NONE                                          //
27//                                                            ///
28/////////////////////////////////////////////////////////////////
29
30define('GETID3_FLV_TAG_AUDIO', 8);
31define('GETID3_FLV_TAG_VIDEO', 9);
32define('GETID3_FLV_TAG_META', 18);
33
34define('GETID3_FLV_VIDEO_H263',   2);
35define('GETID3_FLV_VIDEO_SCREEN', 3);
36define('GETID3_FLV_VIDEO_VP6',    4);
37
38class getid3_flv
39{
40
41	function getid3_flv(&$fd, &$ThisFileInfo, $ReturnAllTagData=false) {
42		fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET);
43
44		$FLVdataLength = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'];
45		$FLVheader = fread($fd, 5);
46
47		$ThisFileInfo['fileformat'] = 'flv';
48		$ThisFileInfo['flv']['header']['signature'] =                           substr($FLVheader, 0, 3);
49		$ThisFileInfo['flv']['header']['version']   = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
50		$TypeFlags                                  = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
51
52		if ($ThisFileInfo['flv']['header']['signature'] != 'FLV') {
53			$ThisFileInfo['error'][] = 'Expecting "FLV" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['flv']['header']['signature'].'"';
54			unset($ThisFileInfo['flv']);
55			unset($ThisFileInfo['fileformat']);
56			return false;
57		}
58
59		$ThisFileInfo['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
60		$ThisFileInfo['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
61
62		$FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($fd, 4));
63		$FLVheaderFrameLength = 9;
64		if ($FrameSizeDataLength > $FLVheaderFrameLength) {
65			fseek($fd, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
66		}
67
68		$Duration = 0;
69		while ((ftell($fd) + 1) < $ThisFileInfo['avdataend']) {
70			//if (!$ThisFileInfo['flv']['header']['hasAudio'] || isset($ThisFileInfo['flv']['audio']['audioFormat'])) {
71			//	if (!$ThisFileInfo['flv']['header']['hasVideo'] || isset($ThisFileInfo['flv']['video']['videoCodec'])) {
72			//		break;
73			//	}
74			//}
75
76			$ThisTagHeader = fread($fd, 16);
77
78			$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  0, 4));
79			$TagType           = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  4, 1));
80			$DataLength        = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  5, 3));
81			$Timestamp         = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  8, 3));
82			$LastHeaderByte    = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
83			$NextOffset = ftell($fd) - 1 + $DataLength;
84
85			switch ($TagType) {
86				case GETID3_FLV_TAG_AUDIO:
87					if (!isset($ThisFileInfo['flv']['audio']['audioFormat'])) {
88						$ThisFileInfo['flv']['audio']['audioFormat']     =  $LastHeaderByte & 0x07;
89						$ThisFileInfo['flv']['audio']['audioRate']       = ($LastHeaderByte & 0x30) / 0x10;
90						$ThisFileInfo['flv']['audio']['audioSampleSize'] = ($LastHeaderByte & 0x40) / 0x40;
91						$ThisFileInfo['flv']['audio']['audioType']       = ($LastHeaderByte & 0x80) / 0x80;
92					}
93					break;
94
95				case GETID3_FLV_TAG_VIDEO:
96					if (!isset($ThisFileInfo['flv']['video']['videoCodec'])) {
97						$ThisFileInfo['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
98
99						$FLVvideoHeader = fread($fd, 11);
100
101						if ($ThisFileInfo['flv']['video']['videoCodec'] != GETID3_FLV_VIDEO_VP6) {
102
103							$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
104							$PictureSizeType = $PictureSizeType & 0x0007;
105							$ThisFileInfo['flv']['header']['videoSizeType'] = $PictureSizeType;
106							switch ($PictureSizeType) {
107								case 0:
108									$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
109									$PictureSizeEnc <<= 1;
110									$ThisFileInfo['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
111									$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
112									$PictureSizeEnc <<= 1;
113									$ThisFileInfo['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
114									break;
115
116								case 1:
117									$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 4));
118									$PictureSizeEnc <<= 1;
119									$ThisFileInfo['video']['resolution_x'] = ($PictureSizeEnc & 0xFFFF0000) >> 16;
120
121									$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 4));
122									$PictureSizeEnc <<= 1;
123									$ThisFileInfo['video']['resolution_y'] = ($PictureSizeEnc & 0xFFFF0000) >> 16;
124									break;
125
126								case 2:
127									$ThisFileInfo['video']['resolution_x'] = 352;
128									$ThisFileInfo['video']['resolution_y'] = 288;
129									break;
130
131								case 3:
132									$ThisFileInfo['video']['resolution_x'] = 176;
133									$ThisFileInfo['video']['resolution_y'] = 144;
134									break;
135
136								case 4:
137									$ThisFileInfo['video']['resolution_x'] = 128;
138									$ThisFileInfo['video']['resolution_y'] = 96;
139									break;
140
141								case 5:
142									$ThisFileInfo['video']['resolution_x'] = 320;
143									$ThisFileInfo['video']['resolution_y'] = 240;
144									break;
145
146								case 6:
147									$ThisFileInfo['video']['resolution_x'] = 160;
148									$ThisFileInfo['video']['resolution_y'] = 120;
149									break;
150
151								default:
152									$ThisFileInfo['video']['resolution_x'] = 0;
153									$ThisFileInfo['video']['resolution_y'] = 0;
154									break;
155
156							}
157						}
158					}
159					break;
160
161				// Meta tag
162				case GETID3_FLV_TAG_META:
163
164					fseek($fd, -1, SEEK_CUR);
165					$reader = new AMFReader(new AMFStream(fread($fd, $DataLength)));
166					$eventName = $reader->readData();
167					$ThisFileInfo['meta'][$eventName] = $reader->readData();
168					unset($reader);
169
170					$ThisFileInfo['video']['frame_rate']   = @$ThisFileInfo['meta']['onMetaData']['framerate'];
171					$ThisFileInfo['video']['resolution_x'] = @$ThisFileInfo['meta']['onMetaData']['width'];
172					$ThisFileInfo['video']['resolution_y'] = @$ThisFileInfo['meta']['onMetaData']['height'];
173					break;
174
175				default:
176					// noop
177					break;
178			}
179
180			if ($Timestamp > $Duration) {
181				$Duration = $Timestamp;
182			}
183
184			fseek($fd, $NextOffset, SEEK_SET);
185		}
186
187		if ($ThisFileInfo['playtime_seconds'] = $Duration / 1000) {
188		    $ThisFileInfo['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['playtime_seconds'];
189		}
190
191		if ($ThisFileInfo['flv']['header']['hasAudio']) {
192			$ThisFileInfo['audio']['codec']           =   $this->FLVaudioFormat($ThisFileInfo['flv']['audio']['audioFormat']);
193			$ThisFileInfo['audio']['sample_rate']     =     $this->FLVaudioRate($ThisFileInfo['flv']['audio']['audioRate']);
194			$ThisFileInfo['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($ThisFileInfo['flv']['audio']['audioSampleSize']);
195
196			$ThisFileInfo['audio']['channels']   = $ThisFileInfo['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
197			$ThisFileInfo['audio']['lossless']   = ($ThisFileInfo['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
198			$ThisFileInfo['audio']['dataformat'] = 'flv';
199		}
200		if (@$ThisFileInfo['flv']['header']['hasVideo']) {
201			$ThisFileInfo['video']['codec']      = $this->FLVvideoCodec($ThisFileInfo['flv']['video']['videoCodec']);
202			$ThisFileInfo['video']['dataformat'] = 'flv';
203			$ThisFileInfo['video']['lossless']   = false;
204		}
205
206		return true;
207	}
208
209
210	function FLVaudioFormat($id) {
211		$FLVaudioFormat = array(
212			0 => 'uncompressed',
213			1 => 'ADPCM',
214			2 => 'mp3',
215			5 => 'Nellymoser 8kHz mono',
216			6 => 'Nellymoser',
217		);
218		return (@$FLVaudioFormat[$id] ? @$FLVaudioFormat[$id] : false);
219	}
220
221	function FLVaudioRate($id) {
222		$FLVaudioRate = array(
223			0 =>  5500,
224			1 => 11025,
225			2 => 22050,
226			3 => 44100,
227		);
228		return (@$FLVaudioRate[$id] ? @$FLVaudioRate[$id] : false);
229	}
230
231	function FLVaudioBitDepth($id) {
232		$FLVaudioBitDepth = array(
233			0 =>  8,
234			1 => 16,
235		);
236		return (@$FLVaudioBitDepth[$id] ? @$FLVaudioBitDepth[$id] : false);
237	}
238
239	function FLVvideoCodec($id) {
240		$FLVvideoCodec = array(
241			GETID3_FLV_VIDEO_H263   => 'Sorenson H.263',
242			GETID3_FLV_VIDEO_SCREEN => 'Screen video',
243			GETID3_FLV_VIDEO_VP6    => 'On2 VP6',
244		);
245		return (@$FLVvideoCodec[$id] ? @$FLVvideoCodec[$id] : false);
246	}
247}
248
249class AMFStream {
250	var $bytes;
251	var $pos;
252
253	function AMFStream(&$bytes) {
254		$this->bytes =& $bytes;
255		$this->pos = 0;
256	}
257
258	function readByte() {
259		return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
260	}
261
262	function readInt() {
263		return ($this->readByte() << 8) + $this->readByte();
264	}
265
266	function readLong() {
267		return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
268	}
269
270	function readDouble() {
271		return getid3_lib::BigEndian2Float($this->read(8));
272	}
273
274	function readUTF() {
275		$length = $this->readInt();
276		return $this->read($length);
277	}
278
279	function readLongUTF() {
280		$length = $this->readLong();
281		return $this->read($length);
282	}
283
284	function read($length) {
285		$val = substr($this->bytes, $this->pos, $length);
286		$this->pos += $length;
287		return $val;
288	}
289
290	function peekByte() {
291		$pos = $this->pos;
292		$val = $this->readByte();
293		$this->pos = $pos;
294		return $val;
295	}
296
297	function peekInt() {
298		$pos = $this->pos;
299		$val = $this->readInt();
300		$this->pos = $pos;
301		return $val;
302	}
303
304	function peekLong() {
305		$pos = $this->pos;
306		$val = $this->readLong();
307		$this->pos = $pos;
308		return $val;
309	}
310
311	function peekDouble() {
312		$pos = $this->pos;
313		$val = $this->readDouble();
314		$this->pos = $pos;
315		return $val;
316	}
317
318	function peekUTF() {
319		$pos = $this->pos;
320		$val = $this->readUTF();
321		$this->pos = $pos;
322		return $val;
323	}
324
325	function peekLongUTF() {
326		$pos = $this->pos;
327		$val = $this->readLongUTF();
328		$this->pos = $pos;
329		return $val;
330	}
331}
332
333class AMFReader {
334	var $stream;
335
336	function AMFReader(&$stream) {
337		$this->stream =& $stream;
338	}
339
340	function readData() {
341		$value = null;
342
343		$type = $this->stream->readByte();
344
345		switch($type) {
346			// Double
347			case 0:
348				$value = $this->readDouble();
349			break;
350
351			// Boolean
352			case 1:
353				$value = $this->readBoolean();
354				break;
355
356			// String
357			case 2:
358				$value = $this->readString();
359				break;
360
361			// Object
362			case 3:
363				$value = $this->readObject();
364				break;
365
366			// null
367			case 6:
368				return null;
369				break;
370
371			// Mixed array
372			case 8:
373				$value = $this->readMixedArray();
374				break;
375
376			// Array
377			case 10:
378				$value = $this->readArray();
379				break;
380
381			// Date
382			case 11:
383				$value = $this->readDate();
384				break;
385
386			// Long string
387			case 13:
388				$value = $this->readLongString();
389				break;
390
391			// XML (handled as string)
392			case 15:
393				$value = $this->readXML();
394				break;
395
396			// Typed object (handled as object)
397			case 16:
398				$value = $this->readTypedObject();
399				break;
400
401			// Long string
402			default:
403				$value = '(unknown or unsupported data type)';
404			break;
405		}
406
407		return $value;
408	}
409
410	function readDouble() {
411		return $this->stream->readDouble();
412	}
413
414	function readBoolean() {
415		return $this->stream->readByte() == 1;
416	}
417
418	function readString() {
419		return $this->stream->readUTF();
420	}
421
422	function readObject() {
423		// Get highest numerical index - ignored
424		$highestIndex = $this->stream->readLong();
425
426		$data = array();
427
428		while ($key = $this->stream->readUTF()) {
429			// Mixed array record ends with empty string (0x00 0x00) and 0x09
430			if (($key == '') && ($this->stream->peekByte() == 0x09)) {
431				// Consume byte
432				$this->stream->readByte();
433				break;
434			}
435
436			$data[$key] = $this->readData();
437		}
438
439		return $data;
440	}
441
442	function readMixedArray() {
443		// Get highest numerical index - ignored
444		$highestIndex = $this->stream->readLong();
445
446		$data = array();
447
448		while ($key = $this->stream->readUTF()) {
449			// Mixed array record ends with empty string (0x00 0x00) and 0x09
450			if (($key == '') && ($this->stream->peekByte() == 0x09)) {
451				// Consume byte
452				$this->stream->readByte();
453				break;
454			}
455
456			if (is_numeric($key)) {
457				$key = (float) $key;
458			}
459
460			$data[$key] = $this->readData();
461		}
462
463		return $data;
464	}
465
466	function readArray() {
467		$length = $this->stream->readLong();
468
469		$data = array();
470
471		for ($i = 0; $i < count($length); $i++) {
472			$data[] = $this->readData();
473		}
474
475		return $data;
476	}
477
478	function readDate() {
479		$timestamp = $this->stream->readDouble();
480		$timezone = $this->stream->readInt();
481		return $timestamp;
482	}
483
484	function readLongString() {
485		return $this->stream->readLongUTF();
486	}
487
488	function readXML() {
489		return $this->stream->readLongUTF();
490	}
491
492	function readTypedObject() {
493		$className = $this->stream->readUTF();
494		return $this->readObject();
495	}
496}
497
498?>