PageRenderTime 61ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/chamilo/chamilo/
PHP | 1244 lines | 778 code | 187 blank | 279 comment | 332 complexity | 5004b1679cc237b6852a767df7e1d8e9 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT

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

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