module.audio-video.flv.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  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 //
  15. // by 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. // Bugfixes for incorrectly parsed FLV dimensions //
  23. // and incorrect parsing of onMetaTag //
  24. // by Evgeny Moysevich <moysevich@gmail.com> //
  25. // * version 0.4 (07 December 2007) //
  26. // //
  27. /////////////////////////////////////////////////////////////////
  28. // //
  29. // module.audio-video.flv.php //
  30. // module for analyzing Shockwave Flash Video files //
  31. // dependencies: NONE //
  32. // ///
  33. /////////////////////////////////////////////////////////////////
  34. define('GETID3_FLV_TAG_AUDIO', 8);
  35. define('GETID3_FLV_TAG_VIDEO', 9);
  36. define('GETID3_FLV_TAG_META', 18);
  37. define('GETID3_FLV_VIDEO_H263', 2);
  38. define('GETID3_FLV_VIDEO_SCREEN', 3);
  39. define('GETID3_FLV_VIDEO_VP6', 4);
  40. class getid3_flv
  41. {
  42. function getid3_flv(&$fd, &$ThisFileInfo, $ReturnAllTagData=false) {
  43. //$start_time = microtime(true);
  44. fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET);
  45. $FLVdataLength = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'];
  46. $FLVheader = fread($fd, 5);
  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. if ($ThisFileInfo['flv']['header']['signature'] != 'FLV') {
  52. $ThisFileInfo['error'][] = 'Expecting "FLV" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['flv']['header']['signature'].'"';
  53. unset($ThisFileInfo['flv']);
  54. unset($ThisFileInfo['fileformat']);
  55. return false;
  56. }
  57. $ThisFileInfo['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
  58. $ThisFileInfo['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
  59. $FrameSizeDataLength = getid3_lib::BigEndian2Int(fread($fd, 4));
  60. $FLVheaderFrameLength = 9;
  61. if ($FrameSizeDataLength > $FLVheaderFrameLength) {
  62. fseek($fd, $FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
  63. }
  64. //echo __LINE__.'='.number_format(microtime(true) - $start_time, 3).'<br>';
  65. $Duration = 0;
  66. $found_video = false;
  67. $found_audio = false;
  68. $found_meta = false;
  69. while ((ftell($fd) + 16) < $ThisFileInfo['avdataend']) {
  70. $ThisTagHeader = fread($fd, 16);
  71. $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4));
  72. $TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1));
  73. $DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3));
  74. $Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3));
  75. $LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
  76. $NextOffset = ftell($fd) - 1 + $DataLength;
  77. if ($Timestamp > $Duration) {
  78. $Duration = $Timestamp;
  79. }
  80. //echo __LINE__.'['.ftell($fd).']=('.$TagType.')='.number_format(microtime(true) - $start_time, 3).'<br>';
  81. switch ($TagType) {
  82. case GETID3_FLV_TAG_AUDIO:
  83. if (!$found_audio) {
  84. $found_audio = true;
  85. $ThisFileInfo['flv']['audio']['audioFormat'] = $LastHeaderByte & 0x07;
  86. $ThisFileInfo['flv']['audio']['audioRate'] = ($LastHeaderByte & 0x30) / 0x10;
  87. $ThisFileInfo['flv']['audio']['audioSampleSize'] = ($LastHeaderByte & 0x40) / 0x40;
  88. $ThisFileInfo['flv']['audio']['audioType'] = ($LastHeaderByte & 0x80) / 0x80;
  89. }
  90. break;
  91. case GETID3_FLV_TAG_VIDEO:
  92. if (!$found_video) {
  93. $found_video = true;
  94. $ThisFileInfo['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
  95. $FLVvideoHeader = fread($fd, 11);
  96. if ($ThisFileInfo['flv']['video']['videoCodec'] != GETID3_FLV_VIDEO_VP6) {
  97. $PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
  98. $PictureSizeType = $PictureSizeType & 0x0007;
  99. $ThisFileInfo['flv']['header']['videoSizeType'] = $PictureSizeType;
  100. switch ($PictureSizeType) {
  101. case 0:
  102. //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
  103. //$PictureSizeEnc <<= 1;
  104. //$ThisFileInfo['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
  105. //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
  106. //$PictureSizeEnc <<= 1;
  107. //$ThisFileInfo['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
  108. $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2));
  109. $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
  110. $PictureSizeEnc['x'] >>= 7;
  111. $PictureSizeEnc['y'] >>= 7;
  112. $ThisFileInfo['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
  113. $ThisFileInfo['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
  114. break;
  115. case 1:
  116. $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3));
  117. $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3));
  118. $PictureSizeEnc['x'] >>= 7;
  119. $PictureSizeEnc['y'] >>= 7;
  120. $ThisFileInfo['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
  121. $ThisFileInfo['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
  122. break;
  123. case 2:
  124. $ThisFileInfo['video']['resolution_x'] = 352;
  125. $ThisFileInfo['video']['resolution_y'] = 288;
  126. break;
  127. case 3:
  128. $ThisFileInfo['video']['resolution_x'] = 176;
  129. $ThisFileInfo['video']['resolution_y'] = 144;
  130. break;
  131. case 4:
  132. $ThisFileInfo['video']['resolution_x'] = 128;
  133. $ThisFileInfo['video']['resolution_y'] = 96;
  134. break;
  135. case 5:
  136. $ThisFileInfo['video']['resolution_x'] = 320;
  137. $ThisFileInfo['video']['resolution_y'] = 240;
  138. break;
  139. case 6:
  140. $ThisFileInfo['video']['resolution_x'] = 160;
  141. $ThisFileInfo['video']['resolution_y'] = 120;
  142. break;
  143. default:
  144. $ThisFileInfo['video']['resolution_x'] = 0;
  145. $ThisFileInfo['video']['resolution_y'] = 0;
  146. break;
  147. }
  148. }
  149. }
  150. break;
  151. // Meta tag
  152. case GETID3_FLV_TAG_META:
  153. if (!$found_meta) {
  154. $found_meta = true;
  155. fseek($fd, -1, SEEK_CUR);
  156. $reader = new AMFReader(new AMFStream(fread($fd, $DataLength)));
  157. $eventName = $reader->readData();
  158. $ThisFileInfo['meta'][$eventName] = $reader->readData();
  159. unset($reader);
  160. $ThisFileInfo['video']['frame_rate'] = @$ThisFileInfo['meta']['onMetaData']['framerate'];
  161. $ThisFileInfo['video']['resolution_x'] = @$ThisFileInfo['meta']['onMetaData']['width'];
  162. $ThisFileInfo['video']['resolution_y'] = @$ThisFileInfo['meta']['onMetaData']['height'];
  163. }
  164. break;
  165. default:
  166. // noop
  167. break;
  168. }
  169. fseek($fd, $NextOffset, SEEK_SET);
  170. }
  171. if ($ThisFileInfo['playtime_seconds'] = $Duration / 1000) {
  172. $ThisFileInfo['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['playtime_seconds'];
  173. }
  174. if ($ThisFileInfo['flv']['header']['hasAudio']) {
  175. $ThisFileInfo['audio']['codec'] = $this->FLVaudioFormat($ThisFileInfo['flv']['audio']['audioFormat']);
  176. $ThisFileInfo['audio']['sample_rate'] = $this->FLVaudioRate($ThisFileInfo['flv']['audio']['audioRate']);
  177. $ThisFileInfo['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($ThisFileInfo['flv']['audio']['audioSampleSize']);
  178. $ThisFileInfo['audio']['channels'] = $ThisFileInfo['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
  179. $ThisFileInfo['audio']['lossless'] = ($ThisFileInfo['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
  180. $ThisFileInfo['audio']['dataformat'] = 'flv';
  181. }
  182. if (@$ThisFileInfo['flv']['header']['hasVideo']) {
  183. $ThisFileInfo['video']['codec'] = $this->FLVvideoCodec($ThisFileInfo['flv']['video']['videoCodec']);
  184. $ThisFileInfo['video']['dataformat'] = 'flv';
  185. $ThisFileInfo['video']['lossless'] = false;
  186. }
  187. return true;
  188. }
  189. function FLVaudioFormat($id) {
  190. $FLVaudioFormat = array(
  191. 0 => 'uncompressed',
  192. 1 => 'ADPCM',
  193. 2 => 'mp3',
  194. 5 => 'Nellymoser 8kHz mono',
  195. 6 => 'Nellymoser',
  196. );
  197. return (@$FLVaudioFormat[$id] ? @$FLVaudioFormat[$id] : false);
  198. }
  199. function FLVaudioRate($id) {
  200. $FLVaudioRate = array(
  201. 0 => 5500,
  202. 1 => 11025,
  203. 2 => 22050,
  204. 3 => 44100,
  205. );
  206. return (@$FLVaudioRate[$id] ? @$FLVaudioRate[$id] : false);
  207. }
  208. function FLVaudioBitDepth($id) {
  209. $FLVaudioBitDepth = array(
  210. 0 => 8,
  211. 1 => 16,
  212. );
  213. return (@$FLVaudioBitDepth[$id] ? @$FLVaudioBitDepth[$id] : false);
  214. }
  215. function FLVvideoCodec($id) {
  216. $FLVvideoCodec = array(
  217. GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
  218. GETID3_FLV_VIDEO_SCREEN => 'Screen video',
  219. GETID3_FLV_VIDEO_VP6 => 'On2 VP6',
  220. );
  221. return (@$FLVvideoCodec[$id] ? @$FLVvideoCodec[$id] : false);
  222. }
  223. }
  224. class AMFStream {
  225. var $bytes;
  226. var $pos;
  227. function AMFStream(&$bytes) {
  228. $this->bytes =& $bytes;
  229. $this->pos = 0;
  230. }
  231. function readByte() {
  232. return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
  233. }
  234. function readInt() {
  235. return ($this->readByte() << 8) + $this->readByte();
  236. }
  237. function readLong() {
  238. return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
  239. }
  240. function readDouble() {
  241. return getid3_lib::BigEndian2Float($this->read(8));
  242. }
  243. function readUTF() {
  244. $length = $this->readInt();
  245. return $this->read($length);
  246. }
  247. function readLongUTF() {
  248. $length = $this->readLong();
  249. return $this->read($length);
  250. }
  251. function read($length) {
  252. $val = substr($this->bytes, $this->pos, $length);
  253. $this->pos += $length;
  254. return $val;
  255. }
  256. function peekByte() {
  257. $pos = $this->pos;
  258. $val = $this->readByte();
  259. $this->pos = $pos;
  260. return $val;
  261. }
  262. function peekInt() {
  263. $pos = $this->pos;
  264. $val = $this->readInt();
  265. $this->pos = $pos;
  266. return $val;
  267. }
  268. function peekLong() {
  269. $pos = $this->pos;
  270. $val = $this->readLong();
  271. $this->pos = $pos;
  272. return $val;
  273. }
  274. function peekDouble() {
  275. $pos = $this->pos;
  276. $val = $this->readDouble();
  277. $this->pos = $pos;
  278. return $val;
  279. }
  280. function peekUTF() {
  281. $pos = $this->pos;
  282. $val = $this->readUTF();
  283. $this->pos = $pos;
  284. return $val;
  285. }
  286. function peekLongUTF() {
  287. $pos = $this->pos;
  288. $val = $this->readLongUTF();
  289. $this->pos = $pos;
  290. return $val;
  291. }
  292. }
  293. class AMFReader {
  294. var $stream;
  295. function AMFReader(&$stream) {
  296. $this->stream =& $stream;
  297. }
  298. function readData() {
  299. $value = null;
  300. $type = $this->stream->readByte();
  301. switch ($type) {
  302. // Double
  303. case 0:
  304. $value = $this->readDouble();
  305. break;
  306. // Boolean
  307. case 1:
  308. $value = $this->readBoolean();
  309. break;
  310. // String
  311. case 2:
  312. $value = $this->readString();
  313. break;
  314. // Object
  315. case 3:
  316. $value = $this->readObject();
  317. break;
  318. // null
  319. case 6:
  320. return null;
  321. break;
  322. // Mixed array
  323. case 8:
  324. $value = $this->readMixedArray();
  325. break;
  326. // Array
  327. case 10:
  328. $value = $this->readArray();
  329. break;
  330. // Date
  331. case 11:
  332. $value = $this->readDate();
  333. break;
  334. // Long string
  335. case 13:
  336. $value = $this->readLongString();
  337. break;
  338. // XML (handled as string)
  339. case 15:
  340. $value = $this->readXML();
  341. break;
  342. // Typed object (handled as object)
  343. case 16:
  344. $value = $this->readTypedObject();
  345. break;
  346. // Long string
  347. default:
  348. $value = '(unknown or unsupported data type)';
  349. break;
  350. }
  351. return $value;
  352. }
  353. function readDouble() {
  354. return $this->stream->readDouble();
  355. }
  356. function readBoolean() {
  357. return $this->stream->readByte() == 1;
  358. }
  359. function readString() {
  360. return $this->stream->readUTF();
  361. }
  362. function readObject() {
  363. // Get highest numerical index - ignored
  364. // $highestIndex = $this->stream->readLong();
  365. $data = array();
  366. while ($key = $this->stream->readUTF()) {
  367. $data[$key] = $this->readData();
  368. }
  369. // Mixed array record ends with empty string (0x00 0x00) and 0x09
  370. if (($key == '') && ($this->stream->peekByte() == 0x09)) {
  371. // Consume byte
  372. $this->stream->readByte();
  373. }
  374. return $data;
  375. }
  376. function readMixedArray() {
  377. // Get highest numerical index - ignored
  378. $highestIndex = $this->stream->readLong();
  379. $data = array();
  380. while ($key = $this->stream->readUTF()) {
  381. if (is_numeric($key)) {
  382. $key = (float) $key;
  383. }
  384. $data[$key] = $this->readData();
  385. }
  386. // Mixed array record ends with empty string (0x00 0x00) and 0x09
  387. if (($key == '') && ($this->stream->peekByte() == 0x09)) {
  388. // Consume byte
  389. $this->stream->readByte();
  390. }
  391. return $data;
  392. }
  393. function readArray() {
  394. $length = $this->stream->readLong();
  395. $data = array();
  396. for ($i = 0; $i < $length; $i++) {
  397. $data[] = $this->readData();
  398. }
  399. return $data;
  400. }
  401. function readDate() {
  402. $timestamp = $this->stream->readDouble();
  403. $timezone = $this->stream->readInt();
  404. return $timestamp;
  405. }
  406. function readLongString() {
  407. return $this->stream->readLongUTF();
  408. }
  409. function readXML() {
  410. return $this->stream->readLongUTF();
  411. }
  412. function readTypedObject() {
  413. $className = $this->stream->readUTF();
  414. return $this->readObject();
  415. }
  416. }
  417. ?>