PageRenderTime 84ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/getid3/module.tag.id3v2.php

https://bitbucket.org/holyfield/getid3
PHP | 3334 lines | 1430 code | 348 blank | 1556 comment | 469 complexity | e4949b8c03a5b61a94dd21f86cfe1774 MD5 | raw file
Possible License(s): GPL-2.0

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

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

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