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?>