PageRenderTime 64ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/common/libraries/plugin/osflvplayer/flash/module.tag.id3v2.php

https://bitbucket.org/renaatdemuynck/chamilo
PHP | 3402 lines | 1525 code | 338 blank | 1539 comment | 454 complexity | b4029567fd0fbb6fd2a43f59d1efd273 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  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. // See readme.txt for more details //
  8. /////////////////////////////////////////////////////////////////
  9. /// //
  10. // module.tag.id3v2.php //
  11. // module for analyzing ID3v2 tags //
  12. // dependencies: module.tag.id3v1.php //
  13. // ///
  14. /////////////////////////////////////////////////////////////////
  15. getid3_lib :: IncludeDependency(GETID3_INCLUDEPATH . 'module.tag.id3v1.php', __FILE__, true);
  16. class getid3_id3v2
  17. {
  18. function getid3_id3v2(&$fd, &$ThisFileInfo, $StartingOffset = 0)
  19. {
  20. // Overall tag structure:
  21. // +-----------------------------+
  22. // | Header (10 bytes) |
  23. // +-----------------------------+
  24. // | Extended Header |
  25. // | (variable length, OPTIONAL) |
  26. // +-----------------------------+
  27. // | Frames (variable length) |
  28. // +-----------------------------+
  29. // | Padding |
  30. // | (variable length, OPTIONAL) |
  31. // +-----------------------------+
  32. // | Footer (10 bytes, OPTIONAL) |
  33. // +-----------------------------+
  34. // Header
  35. // ID3v2/file identifier "ID3"
  36. // ID3v2 version $04 00
  37. // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
  38. // ID3v2 size 4 * %0xxxxxxx
  39. // shortcuts
  40. $ThisFileInfo['id3v2']['header'] = true;
  41. $thisfile_id3v2 = &$ThisFileInfo['id3v2'];
  42. $thisfile_id3v2['flags'] = array();
  43. $thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
  44. fseek($fd, $StartingOffset, SEEK_SET);
  45. $header = fread($fd, 10);
  46. if (substr($header, 0, 3) == 'ID3')
  47. {
  48. $thisfile_id3v2['majorversion'] = ord($header{3});
  49. $thisfile_id3v2['minorversion'] = ord($header{4});
  50. // shortcut
  51. $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
  52. }
  53. else
  54. {
  55. unset($ThisFileInfo['id3v2']);
  56. return false;
  57. }
  58. if ($id3v2_majorversion > 4)
  59. { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
  60. $ThisFileInfo['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.' . $id3v2_majorversion . '.' . $thisfile_id3v2['minorversion'];
  61. return false;
  62. }
  63. $id3_flags = ord($header{5});
  64. switch ($id3v2_majorversion)
  65. {
  66. case 2 :
  67. // %ab000000 in v2.2
  68. $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
  69. $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
  70. break;
  71. case 3 :
  72. // %abc00000 in v2.3
  73. $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
  74. $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
  75. $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
  76. break;
  77. case 4 :
  78. // %abcd0000 in v2.4
  79. $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
  80. $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
  81. $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
  82. $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
  83. break;
  84. }
  85. $thisfile_id3v2['headerlength'] = getid3_lib :: BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
  86. $thisfile_id3v2['tag_offset_start'] = $StartingOffset;
  87. $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
  88. // Extended Header
  89. if (isset($thisfile_id3v2_flags['exthead']) && $thisfile_id3v2_flags['exthead'])
  90. {
  91. // Extended header size 4 * %0xxxxxxx
  92. // Number of flag bytes $01
  93. // Extended Flags $xx
  94. // Where the 'Extended header size' is the size of the whole extended header, stored as a 32 bit synchsafe integer.
  95. $thisfile_id3v2['exthead_length'] = getid3_lib :: BigEndian2Int(fread($fd, 4), 1);
  96. $thisfile_id3v2['exthead_flag_bytes'] = ord(fread($fd, 1));
  97. if ($thisfile_id3v2['exthead_flag_bytes'] == 1)
  98. {
  99. // The extended flags field, with its size described by 'number of flag bytes', is defined as:
  100. // %0bcd0000
  101. // b - Tag is an update
  102. // Flag data length $00
  103. // c - CRC data present
  104. // Flag data length $05
  105. // Total frame CRC 5 * %0xxxxxxx
  106. // d - Tag restrictions
  107. // Flag data length $01
  108. $extheaderflags = fread($fd, $thisfile_id3v2['exthead_flag_bytes']);
  109. $id3_exthead_flags = getid3_lib :: BigEndian2Bin(substr($header, 5, 1));
  110. $thisfile_id3v2['exthead_flags']['update'] = substr($id3_exthead_flags, 1, 1);
  111. $thisfile_id3v2['exthead_flags']['CRC'] = substr($id3_exthead_flags, 2, 1);
  112. if ($thisfile_id3v2['exthead_flags']['CRC'])
  113. {
  114. $extheaderrawCRC = fread($fd, 5);
  115. $thisfile_id3v2['exthead_flags']['CRC'] = getid3_lib :: BigEndian2Int($extheaderrawCRC, 1);
  116. }
  117. $thisfile_id3v2['exthead_flags']['restrictions'] = substr($id3_exthead_flags, 3, 1);
  118. if ($thisfile_id3v2['exthead_flags']['restrictions'])
  119. {
  120. // Restrictions %ppqrrstt
  121. $extheaderrawrestrictions = fread($fd, 1);
  122. $thisfile_id3v2['exthead_flags']['restrictions_tagsize'] = (bindec('11000000') & ord($extheaderrawrestrictions)) >> 6; // p - Tag size restrictions
  123. $thisfile_id3v2['exthead_flags']['restrictions_textenc'] = (bindec('00100000') & ord($extheaderrawrestrictions)) >> 5; // q - Text encoding restrictions
  124. $thisfile_id3v2['exthead_flags']['restrictions_textsize'] = (bindec('00011000') & ord($extheaderrawrestrictions)) >> 3; // r - Text fields size restrictions
  125. $thisfile_id3v2['exthead_flags']['restrictions_imgenc'] = (bindec('00000100') & ord($extheaderrawrestrictions)) >> 2; // s - Image encoding restrictions
  126. $thisfile_id3v2['exthead_flags']['restrictions_imgsize'] = (bindec('00000011') & ord($extheaderrawrestrictions)) >> 0; // t - Image size restrictions
  127. }
  128. }
  129. else
  130. {
  131. $ThisFileInfo['warning'][] = '$thisfile_id3v2[exthead_flag_bytes] = "' . $thisfile_id3v2['exthead_flag_bytes'] . '" (expecting "1")';
  132. fseek($fd, $thisfile_id3v2['exthead_length'] - 1, SEEK_CUR);
  133. //return false;
  134. }
  135. } // end extended header
  136. // create 'encoding' key - used by getid3::HandleAllTags()
  137. // in ID3v2 every field can have it's own encoding type
  138. // so force everything to UTF-8 so it can be handled consistantly
  139. $thisfile_id3v2['encoding'] = 'UTF-8';
  140. // Frames
  141. // All ID3v2 frames consists of one frame header followed by one or more
  142. // fields containing the actual information. The header is always 10
  143. // bytes and laid out as follows:
  144. //
  145. // Frame ID $xx xx xx xx (four characters)
  146. // Size 4 * %0xxxxxxx
  147. // Flags $xx xx
  148. $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
  149. if (@$thisfile_id3v2['exthead_length'])
  150. {
  151. $sizeofframes -= ($thisfile_id3v2['exthead_length'] + 4);
  152. }
  153. if (@$thisfile_id3v2_flags['isfooter'])
  154. {
  155. $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
  156. }
  157. if ($sizeofframes > 0)
  158. {
  159. $framedata = fread($fd, $sizeofframes); // read all frames from file into $framedata variable
  160. // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
  161. if (@$thisfile_id3v2_flags['unsynch'] && ($id3v2_majorversion <= 3))
  162. {
  163. $framedata = $this->DeUnsynchronise($framedata);
  164. }
  165. // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
  166. // of on tag level, making it easier to skip frames, increasing the streamability
  167. // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
  168. // there exists an unsynchronised frame, while the new unsynchronisation flag in
  169. // the frame header [S:4.1.2] indicates unsynchronisation.
  170. $framedataoffset = 10 + (@$thisfile_id3v2['exthead_length'] ? $thisfile_id3v2['exthead_length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
  171. while (isset($framedata) && (strlen($framedata) > 0))
  172. { // cycle through until no more frame data is left to parse
  173. if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion))
  174. {
  175. // insufficient room left in ID3v2 header for actual data - must be padding
  176. $thisfile_id3v2['padding']['start'] = $framedataoffset;
  177. $thisfile_id3v2['padding']['length'] = strlen($framedata);
  178. $thisfile_id3v2['padding']['valid'] = true;
  179. for($i = 0; $i < $thisfile_id3v2['padding']['length']; $i ++)
  180. {
  181. if ($framedata{$i} != "\x00")
  182. {
  183. $thisfile_id3v2['padding']['valid'] = false;
  184. $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
  185. $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset ' . $thisfile_id3v2['padding']['errorpos'] . ' (the remaining ' . ($thisfile_id3v2['padding']['length'] - $i) . ' bytes are considered invalid)';
  186. break;
  187. }
  188. }
  189. break; // skip rest of ID3v2 header
  190. }
  191. if ($id3v2_majorversion == 2)
  192. {
  193. // Frame ID $xx xx xx (three characters)
  194. // Size $xx xx xx (24-bit integer)
  195. // Flags $xx xx
  196. $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
  197. $framedata = substr($framedata, 6); // and leave the rest in $framedata
  198. $frame_name = substr($frame_header, 0, 3);
  199. $frame_size = getid3_lib :: BigEndian2Int(substr($frame_header, 3, 3), 0);
  200. $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
  201. }
  202. elseif ($id3v2_majorversion > 2)
  203. {
  204. // Frame ID $xx xx xx xx (four characters)
  205. // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
  206. // Flags $xx xx
  207. $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
  208. $framedata = substr($framedata, 10); // and leave the rest in $framedata
  209. $frame_name = substr($frame_header, 0, 4);
  210. if ($id3v2_majorversion == 3)
  211. {
  212. $frame_size = getid3_lib :: BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
  213. }
  214. else
  215. { // ID3v2.4+
  216. $frame_size = getid3_lib :: BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
  217. }
  218. if ($frame_size < (strlen($framedata) + 4))
  219. {
  220. $nextFrameID = substr($framedata, $frame_size, 4);
  221. if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion))
  222. {
  223. // next frame is OK
  224. }
  225. elseif (($frame_name == "\x00" . 'MP3') || ($frame_name == "\x00\x00" . 'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e'))
  226. {
  227. // MP3ext known broken frames - "ok" for the purposes of this test
  228. }
  229. elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib :: BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3)))
  230. {
  231. $ThisFileInfo['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
  232. $id3v2_majorversion = 3;
  233. $frame_size = getid3_lib :: BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
  234. }
  235. }
  236. $frame_flags = getid3_lib :: BigEndian2Int(substr($frame_header, 8, 2));
  237. }
  238. if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00"))
  239. {
  240. // padding encountered
  241. $thisfile_id3v2['padding']['start'] = $framedataoffset;
  242. $thisfile_id3v2['padding']['length'] = strlen($framedata);
  243. $thisfile_id3v2['padding']['valid'] = true;
  244. for($i = 0; $i < $thisfile_id3v2['padding']['length']; $i ++)
  245. {
  246. if ($framedata{$i} != "\x00")
  247. {
  248. $thisfile_id3v2['padding']['valid'] = false;
  249. $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
  250. $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset ' . $thisfile_id3v2['padding']['errorpos'] . ' (the remaining ' . ($thisfile_id3v2['padding']['length'] - $i) . ' bytes are considered invalid)';
  251. break;
  252. }
  253. }
  254. break; // skip rest of ID3v2 header
  255. }
  256. if ($frame_name == 'COM ')
  257. {
  258. $ThisFileInfo['warning'][] = 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $id3v2_majorversion . ' tag). (ERROR: IsValidID3v2FrameName("' . str_replace("\x00", ' ', $frame_name) . '", ' . $id3v2_majorversion . '))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
  259. $frame_name = 'COMM';
  260. }
  261. if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)))
  262. {
  263. unset($parsedFrame);
  264. $parsedFrame['frame_name'] = $frame_name;
  265. $parsedFrame['frame_flags_raw'] = $frame_flags;
  266. $parsedFrame['data'] = substr($framedata, 0, $frame_size);
  267. $parsedFrame['datalength'] = getid3_lib :: CastAsInt($frame_size);
  268. $parsedFrame['dataoffset'] = $framedataoffset;
  269. $this->ParseID3v2Frame($parsedFrame, $ThisFileInfo);
  270. $thisfile_id3v2[$frame_name][] = $parsedFrame;
  271. $framedata = substr($framedata, $frame_size);
  272. }
  273. else
  274. { // invalid frame length or FrameID
  275. if ($frame_size <= strlen($framedata))
  276. {
  277. if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion))
  278. {
  279. // next frame is valid, just skip the current frame
  280. $framedata = substr($framedata, $frame_size);
  281. $ThisFileInfo['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
  282. }
  283. else
  284. {
  285. // next frame is invalid too, abort processing
  286. //unset($framedata);
  287. $framedata = null;
  288. $ThisFileInfo['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
  289. }
  290. }
  291. elseif ($frame_size == strlen($framedata))
  292. {
  293. // this is the last frame, just skip
  294. $ThisFileInfo['warning'][] = 'This was the last ID3v2 frame.';
  295. }
  296. else
  297. {
  298. // next frame is invalid too, abort processing
  299. //unset($framedata);
  300. $framedata = null;
  301. $ThisFileInfo['warning'][] = 'Invalid ID3v2 frame size, aborting.';
  302. }
  303. if (! $this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))
  304. {
  305. switch ($frame_name)
  306. {
  307. case "\x00\x00" . 'MP' :
  308. case "\x00" . 'MP3' :
  309. case ' MP3' :
  310. case 'MP3e' :
  311. case "\x00" . 'MP' :
  312. case ' MP' :
  313. case 'MP3' :
  314. $ThisFileInfo['warning'][] = 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $id3v2_majorversion . ' tag). (ERROR: !IsValidID3v2FrameName("' . str_replace("\x00", ' ', $frame_name) . '", ' . $id3v2_majorversion . '))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
  315. break;
  316. default :
  317. $ThisFileInfo['warning'][] = 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $id3v2_majorversion . ' tag). (ERROR: !IsValidID3v2FrameName("' . str_replace("\x00", ' ', $frame_name) . '", ' . $id3v2_majorversion . '))).';
  318. break;
  319. }
  320. }
  321. elseif ($frame_size > strlen($framedata))
  322. {
  323. $ThisFileInfo['error'][] = 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $id3v2_majorversion . ' tag). (ERROR: $frame_size (' . $frame_size . ') > strlen($framedata) (' . strlen($framedata) . ')).';
  324. }
  325. else
  326. {
  327. $ThisFileInfo['error'][] = 'error parsing "' . $frame_name . '" (' . $framedataoffset . ' bytes into the ID3v2.' . $id3v2_majorversion . ' tag).';
  328. }
  329. }
  330. $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
  331. }
  332. }
  333. // Footer
  334. // The footer is a copy of the header, but with a different identifier.
  335. // ID3v2 identifier "3DI"
  336. // ID3v2 version $04 00
  337. // ID3v2 flags %abcd0000
  338. // ID3v2 size 4 * %0xxxxxxx
  339. if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter'])
  340. {
  341. $footer = fread($fd, 10);
  342. if (substr($footer, 0, 3) == '3DI')
  343. {
  344. $thisfile_id3v2['footer'] = true;
  345. $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
  346. $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
  347. }
  348. if ($thisfile_id3v2['majorversion_footer'] <= 4)
  349. {
  350. $id3_flags = ord(substr($footer{5}));
  351. $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
  352. $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
  353. $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
  354. $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
  355. $thisfile_id3v2['footerlength'] = getid3_lib :: BigEndian2Int(substr($footer, 6, 4), 1);
  356. }
  357. } // end footer
  358. if (isset($thisfile_id3v2['comments']['genre']))
  359. {
  360. foreach ($thisfile_id3v2['comments']['genre'] as $key => $value)
  361. {
  362. unset($thisfile_id3v2['comments']['genre'][$key]);
  363. $thisfile_id3v2['comments'] = getid3_lib :: array_merge_noclobber($thisfile_id3v2['comments'], $this->ParseID3v2GenreString($value));
  364. }
  365. }
  366. if (isset($thisfile_id3v2['comments']['track']))
  367. {
  368. foreach ($thisfile_id3v2['comments']['track'] as $key => $value)
  369. {
  370. if (strstr($value, '/'))
  371. {
  372. list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
  373. }
  374. }
  375. }
  376. if (! isset($thisfile_id3v2['comments']['year']) && ereg('^([0-9]{4})', trim(@$thisfile_id3v2['comments']['recording_time'][0]), $matches))
  377. {
  378. $thisfile_id3v2['comments']['year'] = array($matches[1]);
  379. }
  380. // Set avdataoffset
  381. $ThisFileInfo['avdataoffset'] = $thisfile_id3v2['headerlength'];
  382. if (isset($thisfile_id3v2['footer']))
  383. {
  384. $ThisFileInfo['avdataoffset'] += 10;
  385. }
  386. return true;
  387. }
  388. function ParseID3v2GenreString($genrestring)
  389. {
  390. // Parse genres into arrays of genreName and genreID
  391. // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
  392. // ID3v2.4.x: '21' $00 'Eurodisco' $00
  393. $genrestring = trim($genrestring);
  394. $returnarray = array();
  395. if (strpos($genrestring, "\x00") !== false)
  396. {
  397. $unprocessed = trim($genrestring); // trailing nulls will cause an infinite loop.
  398. $genrestring = '';
  399. while (strpos($unprocessed, "\x00") !== false)
  400. {
  401. // convert null-seperated v2.4-format into v2.3 ()-seperated format
  402. $endpos = strpos($unprocessed, "\x00");
  403. $genrestring .= '(' . substr($unprocessed, 0, $endpos) . ')';
  404. $unprocessed = substr($unprocessed, $endpos + 1);
  405. }
  406. unset($unprocessed);
  407. }
  408. if (getid3_id3v1 :: LookupGenreID($genrestring))
  409. {
  410. $returnarray['genre'][] = $genrestring;
  411. }
  412. else
  413. {
  414. while (strpos($genrestring, '(') !== false)
  415. {
  416. $startpos = strpos($genrestring, '(');
  417. $endpos = strpos($genrestring, ')');
  418. if (substr($genrestring, $startpos + 1, 1) == '(')
  419. {
  420. $genrestring = substr($genrestring, 0, $startpos) . substr($genrestring, $startpos + 1);
  421. $endpos --;
  422. }
  423. $element = substr($genrestring, $startpos + 1, $endpos - ($startpos + 1));
  424. $genrestring = substr($genrestring, 0, $startpos) . substr($genrestring, $endpos + 1);
  425. if (getid3_id3v1 :: LookupGenreName($element))
  426. { // $element is a valid genre id/abbreviation
  427. if (empty($returnarray['genre']) || ! in_array(getid3_id3v1 :: LookupGenreName($element), $returnarray['genre']))
  428. { // avoid duplicate entires
  429. $returnarray['genre'][] = getid3_id3v1 :: LookupGenreName($element);
  430. }
  431. }
  432. else
  433. {
  434. if (empty($returnarray['genre']) || ! in_array($element, $returnarray['genre']))
  435. { // avoid duplicate entires
  436. $returnarray['genre'][] = $element;
  437. }
  438. }
  439. }
  440. }
  441. if ($genrestring)
  442. {
  443. if (empty($returnarray['genre']) || ! in_array($genrestring, $returnarray['genre']))
  444. { // avoid duplicate entires
  445. $returnarray['genre'][] = $genrestring;
  446. }
  447. }
  448. return $returnarray;
  449. }
  450. function ParseID3v2Frame(&$parsedFrame, &$ThisFileInfo)
  451. {
  452. // shortcuts
  453. $id3v2_majorversion = $ThisFileInfo['id3v2']['majorversion'];
  454. $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
  455. if (empty($parsedFrame['framenamelong']))
  456. {
  457. unset($parsedFrame['framenamelong']);
  458. }
  459. $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
  460. if (empty($parsedFrame['framenameshort']))
  461. {
  462. unset($parsedFrame['framenameshort']);
  463. }
  464. if ($id3v2_majorversion >= 3)
  465. { // frame flags are not part of the ID3v2.2 standard
  466. if ($id3v2_majorversion == 3)
  467. {
  468. // Frame Header Flags
  469. // %abc00000 %ijk00000
  470. $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
  471. $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
  472. $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
  473. $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
  474. $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
  475. $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
  476. }
  477. elseif ($id3v2_majorversion == 4)
  478. {
  479. // Frame Header Flags
  480. // %0abc0000 %0h00kmnp
  481. $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
  482. $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
  483. $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
  484. $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
  485. $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
  486. $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
  487. $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
  488. $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
  489. // Frame-level de-unsynchronisation - ID3v2.4
  490. if ($parsedFrame['flags']['Unsynchronisation'])
  491. {
  492. $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
  493. }
  494. }
  495. // Frame-level de-compression
  496. if ($parsedFrame['flags']['compression'])
  497. {
  498. $parsedFrame['decompressed_size'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], 0, 4));
  499. if (! function_exists('gzuncompress'))
  500. {
  501. $ThisFileInfo['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "' . $parsedFrame['frame_name'] . '"';
  502. }
  503. elseif ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4)))
  504. {
  505. $parsedFrame['data'] = $decompresseddata;
  506. }
  507. else
  508. {
  509. $ThisFileInfo['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "' . $parsedFrame['frame_name'] . '"';
  510. }
  511. }
  512. }
  513. if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0))
  514. {
  515. $warning = 'Frame "' . $parsedFrame['frame_name'] . '" at offset ' . $parsedFrame['dataoffset'] . ' has no data portion';
  516. switch ($parsedFrame['frame_name'])
  517. {
  518. case 'WCOM' :
  519. $warning .= ' (this is known to happen with files tagged by RioPort)';
  520. break;
  521. default :
  522. break;
  523. }
  524. $ThisFileInfo['warning'][] = $warning;
  525. }
  526. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
  527. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI')))
  528. { // 4.1 UFI Unique file identifier
  529. // There may be more than one 'UFID' frame in a tag,
  530. // but only one with the same 'Owner identifier'.
  531. // <Header for 'Unique file identifier', ID: 'UFID'>
  532. // Owner identifier <text string> $00
  533. // Identifier <up to 64 bytes binary data>
  534. $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
  535. $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
  536. $parsedFrame['ownerid'] = $frame_idstring;
  537. $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
  538. unset($parsedFrame['data']);
  539. }
  540. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
  541. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX')))
  542. { // 4.2.2 TXX User defined text information frame
  543. // There may be more than one 'TXXX' frame in each tag,
  544. // but only one with the same description.
  545. // <Header for 'User defined text information frame', ID: 'TXXX'>
  546. // Text encoding $xx
  547. // Description <text string according to encoding> $00 (00)
  548. // Value <text string according to encoding>
  549. $frame_offset = 0;
  550. $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  551. if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3)))
  552. {
  553. $ThisFileInfo['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding';
  554. }
  555. $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  556. if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0)
  557. {
  558. $frame_terminatorpos ++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  559. }
  560. $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  561. if (ord($frame_description) === 0)
  562. {
  563. $frame_description = '';
  564. }
  565. $parsedFrame['encodingid'] = $frame_textencoding;
  566. $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
  567. $parsedFrame['description'] = $frame_description;
  568. $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
  569. if (! empty($parsedFrame['framenameshort']) && ! empty($parsedFrame['data']))
  570. {
  571. $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib :: iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']));
  572. }
  573. unset($parsedFrame['data']);
  574. }
  575. elseif ($parsedFrame['frame_name']{0} == 'T')
  576. { // 4.2. T??[?] Text information frame
  577. // There may only be one text information frame of its kind in an tag.
  578. // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
  579. // excluding 'TXXX' described in 4.2.6.>
  580. // Text encoding $xx
  581. // Information <text string(s) according to encoding>
  582. $frame_offset = 0;
  583. $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  584. if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3)))
  585. {
  586. $ThisFileInfo['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding';
  587. }
  588. $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
  589. $parsedFrame['encodingid'] = $frame_textencoding;
  590. $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
  591. if (! empty($parsedFrame['framenameshort']) && ! empty($parsedFrame['data']))
  592. {
  593. $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib :: iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']);
  594. }
  595. }
  596. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
  597. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX')))
  598. { // 4.3.2 WXX User defined URL link frame
  599. // There may be more than one 'WXXX' frame in each tag,
  600. // but only one with the same description
  601. // <Header for 'User defined URL link frame', ID: 'WXXX'>
  602. // Text encoding $xx
  603. // Description <text string according to encoding> $00 (00)
  604. // URL <text string>
  605. $frame_offset = 0;
  606. $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  607. if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3)))
  608. {
  609. $ThisFileInfo['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding';
  610. }
  611. $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
  612. if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0)
  613. {
  614. $frame_terminatorpos ++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  615. }
  616. $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
  617. if (ord($frame_description) === 0)
  618. {
  619. $frame_description = '';
  620. }
  621. $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
  622. $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
  623. if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0)
  624. {
  625. $frame_terminatorpos ++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  626. }
  627. if ($frame_terminatorpos)
  628. {
  629. // there are null bytes after the data - this is not according to spec
  630. // only use data up to first null byte
  631. $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
  632. }
  633. else
  634. {
  635. // no null bytes following data, just use all data
  636. $frame_urldata = (string) $parsedFrame['data'];
  637. }
  638. $parsedFrame['encodingid'] = $frame_textencoding;
  639. $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
  640. $parsedFrame['url'] = $frame_urldata;
  641. $parsedFrame['description'] = $frame_description;
  642. if (! empty($parsedFrame['framenameshort']) && $parsedFrame['url'])
  643. {
  644. $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib :: iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['url']);
  645. }
  646. unset($parsedFrame['data']);
  647. }
  648. elseif ($parsedFrame['frame_name']{0} == 'W')
  649. { // 4.3. W??? URL link frames
  650. // There may only be one URL link frame of its kind in a tag,
  651. // except when stated otherwise in the frame description
  652. // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
  653. // described in 4.3.2.>
  654. // URL <text string>
  655. $parsedFrame['url'] = trim($parsedFrame['data']);
  656. if (! empty($parsedFrame['framenameshort']) && $parsedFrame['url'])
  657. {
  658. $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
  659. }
  660. unset($parsedFrame['data']);
  661. }
  662. elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
  663. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL')))
  664. { // 4.4 IPL Involved people list (ID3v2.2 only)
  665. // There may only be one 'IPL' frame in each tag
  666. // <Header for 'User defined URL link frame', ID: 'IPL'>
  667. // Text encoding $xx
  668. // People list strings <textstrings>
  669. $frame_offset = 0;
  670. $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  671. if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3)))
  672. {
  673. $ThisFileInfo['warning'][] = 'Invalid text encoding byte (' . $frame_textencoding . ') in frame "' . $parsedFrame['frame_name'] . '" - defaulting to ISO-8859-1 encoding';
  674. }
  675. $parsedFrame['encodingid'] = $frame_textencoding;
  676. $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
  677. $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
  678. if (! empty($parsedFrame['framenameshort']) && ! empty($parsedFrame['data']))
  679. {
  680. $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib :: iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']);
  681. }
  682. }
  683. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
  684. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI')))
  685. { // 4.5 MCI Music CD identifier
  686. // There may only be one 'MCDI' frame in each tag
  687. // <Header for 'Music CD identifier', ID: 'MCDI'>
  688. // CD TOC <binary data>
  689. if (! empty($parsedFrame['framenameshort']) && ! empty($parsedFrame['data']))
  690. {
  691. $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
  692. }
  693. }
  694. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
  695. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC')))
  696. { // 4.6 ETC Event timing codes
  697. // There may only be one 'ETCO' frame in each tag
  698. // <Header for 'Event timing codes', ID: 'ETCO'>
  699. // Time stamp format $xx
  700. // Where time stamp format is:
  701. // $01 (32-bit value) MPEG frames from beginning of file
  702. // $02 (32-bit value) milliseconds from beginning of file
  703. // Followed by a list of key events in the following format:
  704. // Type of event $xx
  705. // Time stamp $xx (xx ...)
  706. // The 'Time stamp' is set to zero if directly at the beginning of the sound
  707. // or after the previous event. All events MUST be sorted in chronological order.
  708. $frame_offset = 0;
  709. $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  710. while ($frame_offset < strlen($parsedFrame['data']))
  711. {
  712. $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset ++, 1);
  713. $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
  714. $parsedFrame['timestamp'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
  715. $frame_offset += 4;
  716. }
  717. unset($parsedFrame['data']);
  718. }
  719. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
  720. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL')))
  721. { // 4.7 MLL MPEG location lookup table
  722. // There may only be one 'MLLT' frame in each tag
  723. // <Header for 'Location lookup table', ID: 'MLLT'>
  724. // MPEG frames between reference $xx xx
  725. // Bytes between reference $xx xx xx
  726. // Milliseconds between reference $xx xx xx
  727. // Bits for bytes deviation $xx
  728. // Bits for milliseconds dev. $xx
  729. // Then for every reference the following data is included;
  730. // Deviation in bytes %xxx....
  731. // Deviation in milliseconds %xxx....
  732. $frame_offset = 0;
  733. $parsedFrame['framesbetweenreferences'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], 0, 2));
  734. $parsedFrame['bytesbetweenreferences'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], 2, 3));
  735. $parsedFrame['msbetweenreferences'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], 5, 3));
  736. $parsedFrame['bitsforbytesdeviation'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], 8, 1));
  737. $parsedFrame['bitsformsdeviation'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], 9, 1));
  738. $parsedFrame['data'] = substr($parsedFrame['data'], 10);
  739. while ($frame_offset < strlen($parsedFrame['data']))
  740. {
  741. $deviationbitstream .= getid3_lib :: BigEndian2Bin(substr($parsedFrame['data'], $frame_offset ++, 1));
  742. }
  743. $reference_counter = 0;
  744. while (strlen($deviationbitstream) > 0)
  745. {
  746. $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
  747. $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
  748. $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
  749. $reference_counter ++;
  750. }
  751. unset($parsedFrame['data']);
  752. }
  753. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
  754. (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC')))
  755. { // 4.8 STC Synchronised tempo codes
  756. // There may only be one 'SYTC' frame in each tag
  757. // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
  758. // Time stamp format $xx
  759. // Tempo data <binary data>
  760. // Where time stamp format is:
  761. // $01 (32-bit value) MPEG frames from beginning of file
  762. // $02 (32-bit value) milliseconds from beginning of file
  763. $frame_offset = 0;
  764. $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  765. $timestamp_counter = 0;
  766. while ($frame_offset < strlen($parsedFrame['data']))
  767. {
  768. $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  769. if ($parsedFrame[$timestamp_counter]['tempo'] == 255)
  770. {
  771. $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset ++, 1));
  772. }
  773. $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib :: BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
  774. $frame_offset += 4;
  775. $timestamp_counter ++;
  776. }
  777. unset($parsedFrame['data']);
  778. }
  779. elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // …

Large files files are truncated, but you can click here to view the full file