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

/public/library/xinha/plugins/MootoolsFileManager/mootools-filemanager/Backend/Assets/getid3/module.tag.id3v2.php

https://bitbucket.org/mayorbrain/precurio-v2
PHP | 3280 lines | 2413 code | 448 blank | 419 comment | 514 complexity | b99db99587ced953cf875a1cba49d90e MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, MIT

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

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP version 5 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen |
  6. // +----------------------------------------------------------------------+
  7. // | This source file is subject to version 2 of the GPL license, |
  8. // | that is bundled with this package in the file license.txt and is |
  9. // | available through the world-wide-web at the following url: |
  10. // | http://www.gnu.org/copyleft/gpl.html |
  11. // +----------------------------------------------------------------------+
  12. // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org |
  13. // +----------------------------------------------------------------------+
  14. // | Authors: James Heinrich <infoŘgetid3*org> |
  15. // | Allan Hansen <ahŘartemis*dk> |
  16. // +----------------------------------------------------------------------+
  17. // | module.tag.id3v2.php |
  18. // | module for analyzing ID3v2 tags |
  19. // | dependencies: module.tag.id3v1.php |
  20. // | module.lib.image_size.php (optional) |
  21. // | zlib support in PHP (optional) |
  22. // +----------------------------------------------------------------------+
  23. //
  24. // $Id: module.tag.id3v2.php,v 1.15 2006/12/03 23:47:29 ah Exp $
  25. class getid3_id3v2 extends getid3_handler
  26. {
  27. public $option_starting_offset = 0;
  28. public function Analyze() {
  29. $getid3 = $this->getid3;
  30. // dependency
  31. $getid3->include_module('tag.id3v1');
  32. if ($getid3->option_tags_images) {
  33. $getid3->include_module('lib.image_size');
  34. }
  35. // Overall tag structure:
  36. // +-----------------------------+
  37. // | Header (10 bytes) |
  38. // +-----------------------------+
  39. // | Extended Header |
  40. // | (variable length, OPTIONAL) |
  41. // +-----------------------------+
  42. // | Frames (variable length) |
  43. // +-----------------------------+
  44. // | Padding |
  45. // | (variable length, OPTIONAL) |
  46. // +-----------------------------+
  47. // | Footer (10 bytes, OPTIONAL) |
  48. // +-----------------------------+
  49. //
  50. // Header
  51. // ID3v2/file identifier "ID3"
  52. // ID3v2 version $04 00
  53. // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
  54. // ID3v2 size 4 * %0xxxxxxx
  55. // shortcuts
  56. $getid3->info['id3v2']['header'] = true;
  57. $info_id3v2 = &$getid3->info['id3v2'];
  58. $info_id3v2['flags'] = array ();
  59. $info_id3v2_flags = &$info_id3v2['flags'];
  60. $this->fseek($this->option_starting_offset, SEEK_SET);
  61. $header = $this->fread(10);
  62. if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
  63. $info_id3v2['majorversion'] = ord($header{3});
  64. $info_id3v2['minorversion'] = ord($header{4});
  65. // shortcut
  66. $id3v2_major_version = &$info_id3v2['majorversion'];
  67. } else {
  68. unset($getid3->info['id3v2']);
  69. return false;
  70. }
  71. if ($id3v2_major_version > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
  72. throw new getid3_exception('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_major_version.'.'.$info_id3v2['minorversion']);
  73. }
  74. $id3_flags = ord($header{5});
  75. switch ($id3v2_major_version) {
  76. case 2:
  77. // %ab000000 in v2.2
  78. $info_id3v2_flags['unsynch'] = (bool)($id3_flags & 0x80); // a - Unsynchronisation
  79. $info_id3v2_flags['compression'] = (bool)($id3_flags & 0x40); // b - Compression
  80. break;
  81. case 3:
  82. // %abc00000 in v2.3
  83. $info_id3v2_flags['unsynch'] = (bool)($id3_flags & 0x80); // a - Unsynchronisation
  84. $info_id3v2_flags['exthead'] = (bool)($id3_flags & 0x40); // b - Extended header
  85. $info_id3v2_flags['experim'] = (bool)($id3_flags & 0x20); // c - Experimental indicator
  86. break;
  87. case 4:
  88. // %abcd0000 in v2.4
  89. $info_id3v2_flags['unsynch'] = (bool)($id3_flags & 0x80); // a - Unsynchronisation
  90. $info_id3v2_flags['exthead'] = (bool)($id3_flags & 0x40); // b - Extended header
  91. $info_id3v2_flags['experim'] = (bool)($id3_flags & 0x20); // c - Experimental indicator
  92. $info_id3v2_flags['isfooter'] = (bool)($id3_flags & 0x10); // d - Footer present
  93. break;
  94. }
  95. $info_id3v2['headerlength'] = getid3_lib::BigEndianSyncSafe2Int(substr($header, 6, 4)) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
  96. $info_id3v2['tag_offset_start'] = $this->option_starting_offset;
  97. $info_id3v2['tag_offset_end'] = $info_id3v2['tag_offset_start'] + $info_id3v2['headerlength'];
  98. // Frames
  99. // All ID3v2 frames consists of one frame header followed by one or more
  100. // fields containing the actual information. The header is always 10
  101. // bytes and laid out as follows:
  102. //
  103. // Frame ID $xx xx xx xx (four characters)
  104. // Size 4 * %0xxxxxxx
  105. // Flags $xx xx
  106. $size_of_frames = $info_id3v2['headerlength'] - 10; // not including 10-byte initial header
  107. if (@$info_id3v2['exthead']['length']) {
  108. $size_of_frames -= ($info_id3v2['exthead']['length'] + 4);
  109. }
  110. if (@$info_id3v2_flags['isfooter']) {
  111. $size_of_frames -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
  112. }
  113. if ($size_of_frames > 0) {
  114. $frame_data = $this->fread($size_of_frames); // read all frames from file into $frame_data variable
  115. // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
  116. if (@$info_id3v2_flags['unsynch'] && ($id3v2_major_version <= 3)) {
  117. $frame_data = str_replace("\xFF\x00", "\xFF", $frame_data);
  118. }
  119. // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
  120. // of on tag level, making it easier to skip frames, increasing the streamability
  121. // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
  122. // there exists an unsynchronised frame, while the new unsynchronisation flag in
  123. // the frame header [S:4.1.2] indicates unsynchronisation.
  124. //$frame_data_offset = 10 + (@$info_id3v2['exthead']['length'] ? $info_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)
  125. $frame_data_offset = 10; // how many bytes into the stream - start from after the 10-byte header
  126. // Extended Header
  127. if (@$info_id3v2_flags['exthead']) {
  128. $extended_header_offset = 0;
  129. if ($id3v2_major_version == 3) {
  130. // v2.3 definition:
  131. //Extended header size $xx xx xx xx // 32-bit integer
  132. //Extended Flags $xx xx
  133. // %x0000000 %00000000 // v2.3
  134. // x - CRC data present
  135. //Size of padding $xx xx xx xx
  136. $info_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4), 0);
  137. $extended_header_offset += 4;
  138. $info_id3v2['exthead']['flag_bytes'] = 2;
  139. $info_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, $info_id3v2['exthead']['flag_bytes']));
  140. $extended_header_offset += $info_id3v2['exthead']['flag_bytes'];
  141. $info_id3v2['exthead']['flags']['crc'] = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x8000);
  142. $info_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4));
  143. $extended_header_offset += 4;
  144. if ($info_id3v2['exthead']['flags']['crc']) {
  145. $info_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4));
  146. $extended_header_offset += 4;
  147. }
  148. $extended_header_offset += $info_id3v2['exthead']['padding_size'];
  149. }
  150. elseif ($id3v2_major_version == 4) {
  151. // v2.4 definition:
  152. //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
  153. //Number of flag bytes $01
  154. //Extended Flags $xx
  155. // %0bcd0000 // v2.4
  156. // b - Tag is an update
  157. // Flag data length $00
  158. // c - CRC data present
  159. // Flag data length $05
  160. // Total frame CRC 5 * %0xxxxxxx
  161. // d - Tag restrictions
  162. // Flag data length $01
  163. $info_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 4), 1);
  164. $extended_header_offset += 4;
  165. $info_id3v2['exthead']['flag_bytes'] = 1;
  166. $info_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, $info_id3v2['exthead']['flag_bytes']));
  167. $extended_header_offset += $info_id3v2['exthead']['flag_bytes'];
  168. $info_id3v2['exthead']['flags']['update'] = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x4000);
  169. $info_id3v2['exthead']['flags']['crc'] = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x2000);
  170. $info_id3v2['exthead']['flags']['restrictions'] = (bool) ($info_id3v2['exthead']['flag_raw'] & 0x1000);
  171. if ($info_id3v2['exthead']['flags']['crc']) {
  172. $info_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 5), 1);
  173. $extended_header_offset += 5;
  174. }
  175. if ($info_id3v2['exthead']['flags']['restrictions']) {
  176. // %ppqrrstt
  177. $restrictions_raw = getid3_lib::BigEndian2Int(substr($frame_data, $extended_header_offset, 1));
  178. $extended_header_offset += 1;
  179. $info_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw && 0xC0) >> 6; // p - Tag size restrictions
  180. $info_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw && 0x20) >> 5; // q - Text encoding restrictions
  181. $info_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw && 0x18) >> 3; // r - Text fields size restrictions
  182. $info_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw && 0x04) >> 2; // s - Image encoding restrictions
  183. $info_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw && 0x03) >> 0; // t - Image size restrictions
  184. }
  185. }
  186. $frame_data_offset += $extended_header_offset;
  187. $frame_data = substr($frame_data, $extended_header_offset);
  188. } // end extended header
  189. while (isset($frame_data) && (strlen($frame_data) > 0)) { // cycle through until no more frame data is left to parse
  190. if (strlen($frame_data) <= ($id3v2_major_version == 2 ? 6 : 10)) {
  191. // insufficient room left in ID3v2 header for actual data - must be padding
  192. $info_id3v2['padding']['start'] = $frame_data_offset;
  193. $info_id3v2['padding']['length'] = strlen($frame_data);
  194. $info_id3v2['padding']['valid'] = true;
  195. for ($i = 0; $i < $info_id3v2['padding']['length']; $i++) {
  196. if ($frame_data{$i} != "\x00") {
  197. $info_id3v2['padding']['valid'] = false;
  198. $info_id3v2['padding']['errorpos'] = $info_id3v2['padding']['start'] + $i;
  199. $getid3->warning('Invalid ID3v2 padding found at offset '.$info_id3v2['padding']['errorpos'].' (the remaining '.($info_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
  200. break;
  201. }
  202. }
  203. break; // skip rest of ID3v2 header
  204. }
  205. if ($id3v2_major_version == 2) {
  206. // Frame ID $xx xx xx (three characters)
  207. // Size $xx xx xx (24-bit integer)
  208. // Flags $xx xx
  209. $frame_header = substr($frame_data, 0, 6); // take next 6 bytes for header
  210. $frame_data = substr($frame_data, 6); // and leave the rest in $frame_data
  211. $frame_name = substr($frame_header, 0, 3);
  212. $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3));
  213. $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
  214. } elseif ($id3v2_major_version > 2) {
  215. // Frame ID $xx xx xx xx (four characters)
  216. // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
  217. // Flags $xx xx
  218. $frame_header = substr($frame_data, 0, 10); // take next 10 bytes for header
  219. $frame_data = substr($frame_data, 10); // and leave the rest in $frame_data
  220. $frame_name = substr($frame_header, 0, 4);
  221. if ($id3v2_major_version == 3) {
  222. $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4)); // 32-bit integer
  223. } else { // ID3v2.4+
  224. $frame_size = getid3_lib::BigEndianSyncSafe2Int(substr($frame_header, 4, 4)); // 32-bit synchsafe integer (28-bit value)
  225. }
  226. if ($frame_size < (strlen($frame_data) + 4)) {
  227. $nextFrameID = substr($frame_data, $frame_size, 4);
  228. if (getid3_id3v2::IsValidID3v2FrameName($nextFrameID, $id3v2_major_version)) {
  229. // next frame is OK
  230. } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
  231. // MP3ext known broken frames - "ok" for the purposes of this test
  232. } elseif (($id3v2_major_version == 4) && (getid3_id3v2::IsValidID3v2FrameName(substr($frame_data, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4)), 4), 3))) {
  233. $getid3->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');
  234. $id3v2_major_version = 3;
  235. $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4)); // 32-bit integer
  236. }
  237. }
  238. $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
  239. }
  240. if ((($id3v2_major_version == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
  241. // padding encountered
  242. $info_id3v2['padding']['start'] = $frame_data_offset;
  243. $info_id3v2['padding']['length'] = strlen($frame_header) + strlen($frame_data);
  244. $info_id3v2['padding']['valid'] = true;
  245. $len = strlen($frame_data);
  246. for ($i = 0; $i < $len; $i++) {
  247. if ($frame_data{$i} != "\x00") {
  248. $info_id3v2['padding']['valid'] = false;
  249. $info_id3v2['padding']['errorpos'] = $info_id3v2['padding']['start'] + $i;
  250. $getid3->warning('Invalid ID3v2 padding found at offset '.$info_id3v2['padding']['errorpos'].' (the remaining '.($info_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
  251. break;
  252. }
  253. }
  254. break; // skip rest of ID3v2 header
  255. }
  256. if ($frame_name == 'COM ') {
  257. $getid3->warning('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_major_version.'))). [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)]');
  258. $frame_name = 'COMM';
  259. }
  260. if (($frame_size <= strlen($frame_data)) && (getid3_id3v2::IsValidID3v2FrameName($frame_name, $id3v2_major_version))) {
  261. unset($parsed_frame);
  262. $parsed_frame['frame_name'] = $frame_name;
  263. $parsed_frame['frame_flags_raw'] = $frame_flags;
  264. $parsed_frame['data'] = substr($frame_data, 0, $frame_size);
  265. $parsed_frame['datalength'] = (int)($frame_size);
  266. $parsed_frame['dataoffset'] = $frame_data_offset;
  267. $this->ParseID3v2Frame($parsed_frame);
  268. $info_id3v2[$frame_name][] = $parsed_frame;
  269. $frame_data = substr($frame_data, $frame_size);
  270. } else { // invalid frame length or FrameID
  271. if ($frame_size <= strlen($frame_data)) {
  272. if (getid3_id3v2::IsValidID3v2FrameName(substr($frame_data, $frame_size, 4), $id3v2_major_version)) {
  273. // next frame is valid, just skip the current frame
  274. $frame_data = substr($frame_data, $frame_size);
  275. $getid3->warning('Next ID3v2 frame is valid, skipping current frame.');
  276. } else {
  277. // next frame is invalid too, abort processing
  278. throw new getid3_exception('Next ID3v2 frame is also invalid, aborting processing.');
  279. }
  280. } elseif ($frame_size == strlen($frame_data)) {
  281. // this is the last frame, just skip
  282. $getid3->warning('This was the last ID3v2 frame.');
  283. } else {
  284. // next frame is invalid too, abort processing
  285. $frame_data = null;
  286. $getid3->warning('Invalid ID3v2 frame size, aborting.');
  287. }
  288. if (!getid3_id3v2::IsValidID3v2FrameName($frame_name, $id3v2_major_version)) {
  289. switch ($frame_name) {
  290. case "\x00\x00".'MP':
  291. case "\x00".'MP3':
  292. case ' MP3':
  293. case 'MP3e':
  294. case "\x00".'MP':
  295. case ' MP':
  296. case 'MP3':
  297. $getid3->warning('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_major_version.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
  298. break;
  299. default:
  300. $getid3->warning('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_major_version.'))).');
  301. break;
  302. }
  303. } elseif ($frame_size > strlen(@$frame_data)){
  304. throw new getid3_exception('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($frame_data) ('.strlen($frame_data).')).');
  305. } else {
  306. throw new getid3_exception('error parsing "'.$frame_name.'" ('.$frame_data_offset.' bytes into the ID3v2.'.$id3v2_major_version.' tag).');
  307. }
  308. }
  309. $frame_data_offset += ($frame_size + ($id3v2_major_version == 2 ? 6 : 10));
  310. }
  311. }
  312. // Footer
  313. // The footer is a copy of the header, but with a different identifier.
  314. // ID3v2 identifier "3DI"
  315. // ID3v2 version $04 00
  316. // ID3v2 flags %abcd0000
  317. // ID3v2 size 4 * %0xxxxxxx
  318. if (isset($info_id3v2_flags['isfooter']) && $info_id3v2_flags['isfooter']) {
  319. $footer = fread ($getid3->fp, 10);
  320. if (substr($footer, 0, 3) == '3DI') {
  321. $info_id3v2['footer'] = true;
  322. $info_id3v2['majorversion_footer'] = ord($footer{3});
  323. $info_id3v2['minorversion_footer'] = ord($footer{4});
  324. }
  325. if ($info_id3v2['majorversion_footer'] <= 4) {
  326. $id3_flags = ord($footer{5});
  327. $info_id3v2_flags['unsynch_footer'] = (bool)($id3_flags & 0x80);
  328. $info_id3v2_flags['extfoot_footer'] = (bool)($id3_flags & 0x40);
  329. $info_id3v2_flags['experim_footer'] = (bool)($id3_flags & 0x20);
  330. $info_id3v2_flags['isfooter_footer'] = (bool)($id3_flags & 0x10);
  331. $info_id3v2['footerlength'] = getid3_lib::BigEndianSyncSafe2Int(substr($footer, 6, 4));
  332. }
  333. } // end footer
  334. if (isset($info_id3v2['comments']['genre'])) {
  335. foreach ($info_id3v2['comments']['genre'] as $key => $value) {
  336. unset($info_id3v2['comments']['genre'][$key]);
  337. $info_id3v2['comments'] = getid3_id3v2::array_merge_noclobber($info_id3v2['comments'], getid3_id3v2::ParseID3v2GenreString($value));
  338. }
  339. }
  340. if (isset($info_id3v2['comments']['track'])) {
  341. foreach ($info_id3v2['comments']['track'] as $key => $value) {
  342. if (strstr($value, '/')) {
  343. list($info_id3v2['comments']['track'][$key], $info_id3v2['comments']['totaltracks'][$key]) = explode('/', $info_id3v2['comments']['track'][$key]);
  344. }
  345. }
  346. }
  347. // Use year from recording time if year not set
  348. if (!isset($info_id3v2['comments']['year']) && ereg('^([0-9]{4})', @$info_id3v2['comments']['recording_time'][0], $matches)) {
  349. $info_id3v2['comments']['year'] = array ($matches[1]);
  350. }
  351. // Set avdataoffset
  352. $getid3->info['avdataoffset'] = $info_id3v2['headerlength'];
  353. if (isset($info_id3v2['footer'])) {
  354. $getid3->info['avdataoffset'] += 10;
  355. }
  356. return true;
  357. }
  358. private function ParseID3v2Frame(&$parsed_frame) {
  359. $getid3 = $this->getid3;
  360. $id3v2_major_version = $getid3->info['id3v2']['majorversion'];
  361. $frame_name_long = getid3_id3v2::FrameNameLongLookup($parsed_frame['frame_name']);
  362. if ($frame_name_long) {
  363. $parsed_frame['framenamelong'] = $frame_name_long;
  364. }
  365. $frame_name_short = getid3_id3v2::FrameNameShortLookup($parsed_frame['frame_name']);
  366. if ($frame_name_short) {
  367. $parsed_frame['framenameshort'] = $frame_name_short;
  368. }
  369. if ($id3v2_major_version >= 3) { // frame flags are not part of the ID3v2.2 standard
  370. if ($id3v2_major_version == 3) {
  371. // Frame Header Flags
  372. // %abc00000 %ijk00000
  373. $parsed_frame['flags']['TagAlterPreservation'] = (bool)($parsed_frame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
  374. $parsed_frame['flags']['FileAlterPreservation'] = (bool)($parsed_frame['frame_flags_raw'] & 0x4000); // b - File alter preservation
  375. $parsed_frame['flags']['ReadOnly'] = (bool)($parsed_frame['frame_flags_raw'] & 0x2000); // c - Read only
  376. $parsed_frame['flags']['compression'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0080); // i - Compression
  377. $parsed_frame['flags']['Encryption'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0040); // j - Encryption
  378. $parsed_frame['flags']['GroupingIdentity'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0020); // k - Grouping identity
  379. } elseif ($id3v2_major_version == 4) {
  380. // Frame Header Flags
  381. // %0abc0000 %0h00kmnp
  382. $parsed_frame['flags']['TagAlterPreservation'] = (bool)($parsed_frame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
  383. $parsed_frame['flags']['FileAlterPreservation'] = (bool)($parsed_frame['frame_flags_raw'] & 0x2000); // b - File alter preservation
  384. $parsed_frame['flags']['ReadOnly'] = (bool)($parsed_frame['frame_flags_raw'] & 0x1000); // c - Read only
  385. $parsed_frame['flags']['GroupingIdentity'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0040); // h - Grouping identity
  386. $parsed_frame['flags']['compression'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0008); // k - Compression
  387. $parsed_frame['flags']['Encryption'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0004); // m - Encryption
  388. $parsed_frame['flags']['Unsynchronisation'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
  389. $parsed_frame['flags']['DataLengthIndicator'] = (bool)($parsed_frame['frame_flags_raw'] & 0x0001); // p - Data length indicator
  390. // Frame-level de-unsynchronisation - ID3v2.4
  391. if ($parsed_frame['flags']['Unsynchronisation']) {
  392. $parsed_frame['data'] = str_replace("\xFF\x00", "\xFF", $parsed_frame['data']);
  393. }
  394. }
  395. // Frame-level de-compression
  396. if ($parsed_frame['flags']['compression']) {
  397. $parsed_frame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 0, 4));
  398. if (!function_exists('gzuncompress')) {
  399. $getid3->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsed_frame['frame_name'].'"');
  400. } elseif ($decompressed_data = @gzuncompress(substr($parsed_frame['data'], 4))) {
  401. $parsed_frame['data'] = $decompressed_data;
  402. } else {
  403. $getid3->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsed_frame['frame_name'].'"');
  404. }
  405. }
  406. }
  407. if (isset($parsed_frame['datalength']) && ($parsed_frame['datalength'] == 0)) {
  408. $warning = 'Frame "'.$parsed_frame['frame_name'].'" at offset '.$parsed_frame['dataoffset'].' has no data portion';
  409. switch ($parsed_frame['frame_name']) {
  410. case 'WCOM':
  411. $warning .= ' (this is known to happen with files tagged by RioPort)';
  412. break;
  413. default:
  414. break;
  415. }
  416. $getid3->warning($warning);
  417. return true;
  418. }
  419. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
  420. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
  421. // There may be more than one 'UFID' frame in a tag,
  422. // but only one with the same 'Owner identifier'.
  423. // <Header for 'Unique file identifier', ID: 'UFID'>
  424. // Owner identifier <text string> $00
  425. // Identifier <up to 64 bytes binary data>
  426. $frame_terminator_pos = strpos($parsed_frame['data'], "\x00");
  427. $frame_id_string = substr($parsed_frame['data'], 0, $frame_terminator_pos);
  428. $parsed_frame['ownerid'] = $frame_id_string;
  429. $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen("\x00"));
  430. unset($parsed_frame['data']);
  431. return true;
  432. }
  433. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
  434. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
  435. // There may be more than one 'TXXX' frame in each tag,
  436. // but only one with the same description.
  437. // <Header for 'User defined text information frame', ID: 'TXXX'>
  438. // Text encoding $xx
  439. // Description <text string according to encoding> $00 (00)
  440. // Value <text string according to encoding>
  441. $frame_offset = 0;
  442. $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
  443. if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
  444. $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
  445. }
  446. $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
  447. if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
  448. $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
  449. }
  450. $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
  451. if (ord($frame_description) === 0) {
  452. $frame_description = '';
  453. }
  454. $parsed_frame['encodingid'] = $frame_text_encoding;
  455. $parsed_frame['encoding'] = $this->TextEncodingNameLookup($frame_text_encoding);
  456. $parsed_frame['description'] = $frame_description;
  457. $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
  458. if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
  459. $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = trim($getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']));
  460. }
  461. unset($parsed_frame['data']);
  462. return true;
  463. }
  464. if ($parsed_frame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
  465. // There may only be one text information frame of its kind in an tag.
  466. // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
  467. // excluding 'TXXX' described in 4.2.6.>
  468. // Text encoding $xx
  469. // Information <text string(s) according to encoding>
  470. $frame_offset = 0;
  471. $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
  472. if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
  473. $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
  474. }
  475. $parsed_frame['data'] = (string)substr($parsed_frame['data'], $frame_offset);
  476. $parsed_frame['encodingid'] = $frame_text_encoding;
  477. $parsed_frame['encoding'] = $this->TextEncodingNameLookup($frame_text_encoding);
  478. if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
  479. // remove possible terminating \x00 (put by encoding id or software bug)
  480. $string = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']);
  481. if ($string[strlen($string)-1] = "\x00") {
  482. $string = substr($string, 0, strlen($string)-1);
  483. }
  484. $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $string;
  485. unset($string);
  486. }
  487. return true;
  488. }
  489. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
  490. (($id3v2_major_version == 2) && ($parsed_frame['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_text_encoding = ord($parsed_frame['data']{$frame_offset++});
  499. if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
  500. $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
  501. }
  502. $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
  503. if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
  504. $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
  505. }
  506. $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
  507. if (ord($frame_description) === 0) {
  508. $frame_description = '';
  509. }
  510. $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
  511. $frame_terminator_pos = strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding));
  512. if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
  513. $frame_terminator_pos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
  514. }
  515. if ($frame_terminator_pos) {
  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($parsed_frame['data'], 0, $frame_terminator_pos);
  519. } else {
  520. // no null bytes following data, just use all data
  521. $frame_urldata = (string)$parsed_frame['data'];
  522. }
  523. $parsed_frame['encodingid'] = $frame_text_encoding;
  524. $parsed_frame['encoding'] = $this->TextEncodingNameLookup($frame_text_encoding);
  525. $parsed_frame['url'] = $frame_urldata;
  526. $parsed_frame['description'] = $frame_description;
  527. if (!empty($parsed_frame['framenameshort']) && $parsed_frame['url']) {
  528. $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['url']);
  529. }
  530. unset($parsed_frame['data']);
  531. return true;
  532. }
  533. if ($parsed_frame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
  534. // There may only be one URL link frame of its kind in a tag,
  535. // except when stated otherwise in the frame description
  536. // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
  537. // described in 4.3.2.>
  538. // URL <text string>
  539. $parsed_frame['url'] = trim($parsed_frame['data']);
  540. if (!empty($parsed_frame['framenameshort']) && $parsed_frame['url']) {
  541. $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $parsed_frame['url'];
  542. }
  543. unset($parsed_frame['data']);
  544. return true;
  545. }
  546. if ((($id3v2_major_version == 3) && ($parsed_frame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
  547. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
  548. // There may only be one 'IPL' frame in each tag
  549. // <Header for 'User defined URL link frame', ID: 'IPL'>
  550. // Text encoding $xx
  551. // People list strings <textstrings>
  552. $frame_offset = 0;
  553. $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
  554. if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
  555. $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
  556. }
  557. $parsed_frame['encodingid'] = $frame_text_encoding;
  558. $parsed_frame['encoding'] = $this->TextEncodingNameLookup($parsed_frame['encodingid']);
  559. $parsed_frame['data'] = (string)substr($parsed_frame['data'], $frame_offset);
  560. if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
  561. $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $getid3->iconv($parsed_frame['encoding'], 'UTF-8', $parsed_frame['data']);
  562. }
  563. return true;
  564. }
  565. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
  566. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
  567. // There may only be one 'MCDI' frame in each tag
  568. // <Header for 'Music CD identifier', ID: 'MCDI'>
  569. // CD TOC <binary data>
  570. if (!empty($parsed_frame['framenameshort']) && !empty($parsed_frame['data'])) {
  571. $getid3->info['id3v2']['comments'][$parsed_frame['framenameshort']][] = $parsed_frame['data'];
  572. }
  573. return true;
  574. }
  575. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
  576. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
  577. // There may only be one 'ETCO' frame in each tag
  578. // <Header for 'Event timing codes', ID: 'ETCO'>
  579. // Time stamp format $xx
  580. // Where time stamp format is:
  581. // $01 (32-bit value) MPEG frames from beginning of file
  582. // $02 (32-bit value) milliseconds from beginning of file
  583. // Followed by a list of key events in the following format:
  584. // Type of event $xx
  585. // Time stamp $xx (xx ...)
  586. // The 'Time stamp' is set to zero if directly at the beginning of the sound
  587. // or after the previous event. All events MUST be sorted in chronological order.
  588. $frame_offset = 0;
  589. $parsed_frame['timestampformat'] = ord($parsed_frame['data']{$frame_offset++});
  590. while ($frame_offset < strlen($parsed_frame['data'])) {
  591. $parsed_frame['typeid'] = $parsed_frame['data']{$frame_offset++};
  592. $parsed_frame['type'] = getid3_id3v2::ETCOEventLookup($parsed_frame['typeid']);
  593. $parsed_frame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
  594. $frame_offset += 4;
  595. }
  596. unset($parsed_frame['data']);
  597. return true;
  598. }
  599. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
  600. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
  601. // There may only be one 'MLLT' frame in each tag
  602. // <Header for 'Location lookup table', ID: 'MLLT'>
  603. // MPEG frames between reference $xx xx
  604. // Bytes between reference $xx xx xx
  605. // Milliseconds between reference $xx xx xx
  606. // Bits for bytes deviation $xx
  607. // Bits for milliseconds dev. $xx
  608. // Then for every reference the following data is included;
  609. // Deviation in bytes %xxx....
  610. // Deviation in milliseconds %xxx....
  611. $frame_offset = 0;
  612. $parsed_frame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 0, 2));
  613. $parsed_frame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 2, 3));
  614. $parsed_frame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], 5, 3));
  615. $parsed_frame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int($parsed_frame['data'][8]);
  616. $parsed_frame['bitsformsdeviation'] = getid3_lib::BigEndian2Int($parsed_frame['data'][9]);
  617. $parsed_frame['data'] = substr($parsed_frame['data'], 10);
  618. while ($frame_offset < strlen($parsed_frame['data'])) {
  619. $deviation_bitstream .= getid3_lib::BigEndian2Bin($parsed_frame['data']{$frame_offset++});
  620. }
  621. $reference_counter = 0;
  622. while (strlen($deviation_bitstream) > 0) {
  623. $parsed_frame[$reference_counter]['bytedeviation'] = bindec(substr($deviation_bitstream, 0, $parsed_frame['bitsforbytesdeviation']));
  624. $parsed_frame[$reference_counter]['msdeviation'] = bindec(substr($deviation_bitstream, $parsed_frame['bitsforbytesdeviation'], $parsed_frame['bitsformsdeviation']));
  625. $deviation_bitstream = substr($deviation_bitstream, $parsed_frame['bitsforbytesdeviation'] + $parsed_frame['bitsformsdeviation']);
  626. $reference_counter++;
  627. }
  628. unset($parsed_frame['data']);
  629. return true;
  630. }
  631. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
  632. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
  633. // There may only be one 'SYTC' frame in each tag
  634. // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
  635. // Time stamp format $xx
  636. // Tempo data <binary data>
  637. // Where time stamp format is:
  638. // $01 (32-bit value) MPEG frames from beginning of file
  639. // $02 (32-bit value) milliseconds from beginning of file
  640. $frame_offset = 0;
  641. $parsed_frame['timestampformat'] = ord($parsed_frame['data']{$frame_offset++});
  642. $timestamp_counter = 0;
  643. while ($frame_offset < strlen($parsed_frame['data'])) {
  644. $parsed_frame[$timestamp_counter]['tempo'] = ord($parsed_frame['data']{$frame_offset++});
  645. if ($parsed_frame[$timestamp_counter]['tempo'] == 255) {
  646. $parsed_frame[$timestamp_counter]['tempo'] += ord($parsed_frame['data']{$frame_offset++});
  647. }
  648. $parsed_frame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsed_frame['data'], $frame_offset, 4));
  649. $frame_offset += 4;
  650. $timestamp_counter++;
  651. }
  652. unset($parsed_frame['data']);
  653. return true;
  654. }
  655. if ((($id3v2_major_version >= 3) && ($parsed_frame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
  656. (($id3v2_major_version == 2) && ($parsed_frame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
  657. // There may be more than one 'Unsynchronised lyrics/text transcription' frame
  658. // in each tag, but only one with the same language and content descriptor.
  659. // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
  660. // Text encoding $xx
  661. // Language $xx xx xx
  662. // Content descriptor <text string according to encoding> $00 (00)
  663. // Lyrics/text <full text string according to encoding>
  664. $frame_offset = 0;
  665. $frame_text_encoding = ord($parsed_frame['data']{$frame_offset++});
  666. if ((($id3v2_major_version <= 3) && ($frame_text_encoding > 1)) || (($id3v2_major_version == 4) && ($frame_text_encoding > 3))) {
  667. $getid3->warning('Invalid text encoding byte ('.$frame_text_encoding.') in frame "'.$parsed_frame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
  668. }
  669. $frame_language = substr($parsed_frame['data'], $frame_offset, 3);
  670. $frame_offset += 3;
  671. if ($frame_offset > strlen($parsed_frame['data'])) {
  672. $frame_offset = strlen($parsed_frame['data']) - 1;
  673. }
  674. $frame_terminator_pos = @strpos($parsed_frame['data'], getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding), $frame_offset);
  675. if (ord(substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)), 1)) === 0) {
  676. $frame_terminator_pos++; // @strpos() fooled because 2nd byte of Unicode chars are often 0x00
  677. }
  678. $frame_description = substr($parsed_frame['data'], $frame_offset, $frame_terminator_pos - $frame_offset);
  679. if (ord($frame_description) === 0) {
  680. $frame_description = '';
  681. }
  682. $parsed_frame['data'] = substr($parsed_frame['data'], $frame_terminator_pos + strlen(getid3_id3v2::TextEncodingTerminatorLookup($frame_text_encoding)));
  683. $parsed_frame['encodingid'] = $frame_text_encoding;
  684. $parsed_frame['encoding'] = $this->TextEncodingNameLookup($frame_text_encoding);
  685. $parsed_frame['data'] = $parsed_frame['data'];
  686. $parsed_frame['language'] = $frame_language;

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