PageRenderTime 56ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-includes/ID3/module.tag.id3v2.php

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

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