1<?php
2
3/////////////////////////////////////////////////////////////////
4/// getID3() by James Heinrich <info@getid3.org> //
5// available at https://github.com/JamesHeinrich/getID3 //
6// or https://www.getid3.org //
7// or http://getid3.sourceforge.net //
8// see readme.txt for more details //
9/////////////////////////////////////////////////////////////////
10/// //
11// module.tag.id3v2.php //
12// module for analyzing ID3v2 tags //
13// dependencies: module.tag.id3v1.php //
14// ///
15/////////////////////////////////////////////////////////////////
16
17if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
18 exit;
19}
20getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
21
22class getid3_id3v2 extends getid3_handler
23{
24 public $StartingOffset = 0;
25
26 /**
27 * @return bool
28 */
29 public function Analyze() {
30 $info = &$this->getid3->info;
31
32 // Overall tag structure:
33 // +-----------------------------+
34 // | Header (10 bytes) |
35 // +-----------------------------+
36 // | Extended Header |
37 // | (variable length, OPTIONAL) |
38 // +-----------------------------+
39 // | Frames (variable length) |
40 // +-----------------------------+
41 // | Padding |
42 // | (variable length, OPTIONAL) |
43 // +-----------------------------+
44 // | Footer (10 bytes, OPTIONAL) |
45 // +-----------------------------+
46
47 // Header
48 // ID3v2/file identifier "ID3"
49 // ID3v2 version $04 00
50 // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
51 // ID3v2 size 4 * %0xxxxxxx
52
53
54 // shortcuts
55 $info['id3v2']['header'] = true;
56 $thisfile_id3v2 = &$info['id3v2'];
57 $thisfile_id3v2['flags'] = array();
58 $thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
59
60
61 $this->fseek($this->StartingOffset);
62 $header = $this->fread(10);
63 if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
64
65 $thisfile_id3v2['majorversion'] = ord($header[3]);
66 $thisfile_id3v2['minorversion'] = ord($header[4]);
67
68 // shortcut
69 $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
70
71 } else {
72
73 unset($info['id3v2']);
74 return false;
75
76 }
77
78 if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
79
80 $this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
81 return false;
82
83 }
84
85 $id3_flags = ord($header[5]);
86 switch ($id3v2_majorversion) {
87 case 2:
88 // %ab000000 in v2.2
89 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
90 $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
91 break;
92
93 case 3:
94 // %abc00000 in v2.3
95 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
96 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
97 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
98 break;
99
100 case 4:
101 // %abcd0000 in v2.4
102 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
103 $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
104 $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
105 $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
106 break;
107 }
108
109 $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
110
111 $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
112 $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
113
114
115
116 // create 'encoding' key - used by getid3::HandleAllTags()
117 // in ID3v2 every field can have it's own encoding type
118 // so force everything to UTF-8 so it can be handled consistantly
119 $thisfile_id3v2['encoding'] = 'UTF-8';
120
121
122 // Frames
123
124 // All ID3v2 frames consists of one frame header followed by one or more
125 // fields containing the actual information. The header is always 10
126 // bytes and laid out as follows:
127 //
128 // Frame ID $xx xx xx xx (four characters)
129 // Size 4 * %0xxxxxxx
130 // Flags $xx xx
131
132 $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
133 if (!empty($thisfile_id3v2['exthead']['length'])) {
134 $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
135 }
136 if (!empty($thisfile_id3v2_flags['isfooter'])) {
137 $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
138 }
139 if ($sizeofframes > 0) {
140
141 $framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
142
143 // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
144 if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
145 $framedata = $this->DeUnsynchronise($framedata);
146 }
147 // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
148 // of on tag level, making it easier to skip frames, increasing the streamability
149 // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
150 // there exists an unsynchronised frame, while the new unsynchronisation flag in
151 // the frame header [S:4.1.2] indicates unsynchronisation.
152
153
154 //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
155 $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
156
157
158 // Extended Header
159 if (!empty($thisfile_id3v2_flags['exthead'])) {
160 $extended_header_offset = 0;
161
162 if ($id3v2_majorversion == 3) {
163
164 // v2.3 definition:
165 //Extended header size $xx xx xx xx // 32-bit integer
166 //Extended Flags $xx xx
167 // %x0000000 %00000000 // v2.3
168 // x - CRC data present
169 //Size of padding $xx xx xx xx
170
171 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
172 $extended_header_offset += 4;
173
174 $thisfile_id3v2['exthead']['flag_bytes'] = 2;
175 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
176 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
177
178 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
179
180 $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
181 $extended_header_offset += 4;
182
183 if ($thisfile_id3v2['exthead']['flags']['crc']) {
184 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
185 $extended_header_offset += 4;
186 }
187 $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
188
189 } elseif ($id3v2_majorversion == 4) {
190
191 // v2.4 definition:
192 //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
193 //Number of flag bytes $01
194 //Extended Flags $xx
195 // %0bcd0000 // v2.4
196 // b - Tag is an update
197 // Flag data length $00
198 // c - CRC data present
199 // Flag data length $05
200 // Total frame CRC 5 * %0xxxxxxx
201 // d - Tag restrictions
202 // Flag data length $01
203
204 $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
205 $extended_header_offset += 4;
206
207 $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
208 $extended_header_offset += 1;
209
210 $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
211 $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
212
213 $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
214 $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
215 $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
216
217 if ($thisfile_id3v2['exthead']['flags']['update']) {
218 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
219 $extended_header_offset += 1;
220 }
221
222 if ($thisfile_id3v2['exthead']['flags']['crc']) {
223 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
224 $extended_header_offset += 1;
225 $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
226 $extended_header_offset += $ext_header_chunk_length;
227 }
228
229 if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
230 $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
231 $extended_header_offset += 1;
232
233 // %ppqrrstt
234 $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
235 $extended_header_offset += 1;
236 $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
237 $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
238 $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
239 $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
240 $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
241
242 $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
243 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
244 $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
245 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
246 $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
247 }
248
249 if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
250 $this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
251 }
252 }
253
254 $framedataoffset += $extended_header_offset;
255 $framedata = substr($framedata, $extended_header_offset);
256 } // end extended header
257
258
259 while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
260 if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
261 // insufficient room left in ID3v2 header for actual data - must be padding
262 $thisfile_id3v2['padding']['start'] = $framedataoffset;
263 $thisfile_id3v2['padding']['length'] = strlen($framedata);
264 $thisfile_id3v2['padding']['valid'] = true;
265 for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
266 if ($framedata[$i] != "\x00") {
267 $thisfile_id3v2['padding']['valid'] = false;
268 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
269 $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
270 break;
271 }
272 }
273 break; // skip rest of ID3v2 header
274 }
275 $frame_header = null;
276 $frame_name = null;
277 $frame_size = null;
278 $frame_flags = null;
279 if ($id3v2_majorversion == 2) {
280 // Frame ID $xx xx xx (three characters)
281 // Size $xx xx xx (24-bit integer)
282 // Flags $xx xx
283
284 $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
285 $framedata = substr($framedata, 6); // and leave the rest in $framedata
286 $frame_name = substr($frame_header, 0, 3);
287 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
288 $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
289
290 } elseif ($id3v2_majorversion > 2) {
291
292 // Frame ID $xx xx xx xx (four characters)
293 // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
294 // Flags $xx xx
295
296 $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
297 $framedata = substr($framedata, 10); // and leave the rest in $framedata
298
299 $frame_name = substr($frame_header, 0, 4);
300 if ($id3v2_majorversion == 3) {
301 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
302 } else { // ID3v2.4+
303 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
304 }
305
306 if ($frame_size < (strlen($framedata) + 4)) {
307 $nextFrameID = substr($framedata, $frame_size, 4);
308 if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
309 // next frame is OK
310 } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
311 // MP3ext known broken frames - "ok" for the purposes of this test
312 } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
313 $this->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');
314 $id3v2_majorversion = 3;
315 $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
316 }
317 }
318
319
320 $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
321 }
322
323 if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
324 // padding encountered
325
326 $thisfile_id3v2['padding']['start'] = $framedataoffset;
327 $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
328 $thisfile_id3v2['padding']['valid'] = true;
329
330 $len = strlen($framedata);
331 for ($i = 0; $i < $len; $i++) {
332 if ($framedata[$i] != "\x00") {
333 $thisfile_id3v2['padding']['valid'] = false;
334 $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
335 $this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
336 break;
337 }
338 }
339 break; // skip rest of ID3v2 header
340 }
341
342 if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
343 $this->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", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
344 $frame_name = $iTunesBrokenFrameNameFixed;
345 }
346 if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
347
348 $parsedFrame = array();
349 $parsedFrame['frame_name'] = $frame_name;
350 $parsedFrame['frame_flags_raw'] = $frame_flags;
351 $parsedFrame['data'] = substr($framedata, 0, $frame_size);
352 $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size);
353 $parsedFrame['dataoffset'] = $framedataoffset;
354
355 $this->ParseID3v2Frame($parsedFrame);
356 $thisfile_id3v2[$frame_name][] = $parsedFrame;
357
358 $framedata = substr($framedata, $frame_size);
359
360 } else { // invalid frame length or FrameID
361
362 if ($frame_size <= strlen($framedata)) {
363
364 if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
365
366 // next frame is valid, just skip the current frame
367 $framedata = substr($framedata, $frame_size);
368 $this->warning('Next ID3v2 frame is valid, skipping current frame.');
369
370 } else {
371
372 // next frame is invalid too, abort processing
373 //unset($framedata);
374 $framedata = null;
375 $this->error('Next ID3v2 frame is also invalid, aborting processing.');
376
377 }
378
379 } elseif ($frame_size == strlen($framedata)) {
380
381 // this is the last frame, just skip
382 $this->warning('This was the last ID3v2 frame.');
383
384 } else {
385
386 // next frame is invalid too, abort processing
387 //unset($framedata);
388 $framedata = null;
389 $this->warning('Invalid ID3v2 frame size, aborting.');
390
391 }
392 if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
393
394 switch ($frame_name) {
395 case "\x00\x00".'MP':
396 case "\x00".'MP3':
397 case ' MP3':
398 case 'MP3e':
399 case "\x00".'MP':
400 case ' MP':
401 case 'MP3':
402 $this->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/)"]');
403 break;
404
405 default:
406 $this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
407 break;
408 }
409
410 } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
411
412 $this->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').')).');
413
414 } else {
415
416 $this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
417
418 }
419
420 }
421 $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
422
423 }
424
425 }
426
427
428 // Footer
429
430 // The footer is a copy of the header, but with a different identifier.
431 // ID3v2 identifier "3DI"
432 // ID3v2 version $04 00
433 // ID3v2 flags %abcd0000
434 // ID3v2 size 4 * %0xxxxxxx
435
436 if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
437 $footer = $this->fread(10);
438 if (substr($footer, 0, 3) == '3DI') {
439 $thisfile_id3v2['footer'] = true;
440 $thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
441 $thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
442 }
443 if ($thisfile_id3v2['majorversion_footer'] <= 4) {
444 $id3_flags = ord($footer[5]);
445 $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
446 $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
447 $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
448 $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
449
450 $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
451 }
452 } // end footer
453
454 if (isset($thisfile_id3v2['comments']['genre'])) {
455 $genres = array();
456 foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
457 foreach ($this->ParseID3v2GenreString($value) as $genre) {
458 $genres[] = $genre;
459 }
460 }
461 $thisfile_id3v2['comments']['genre'] = array_unique($genres);
462 unset($key, $value, $genres, $genre);
463 }
464
465 if (isset($thisfile_id3v2['comments']['track_number'])) {
466 foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
467 if (strstr($value, '/')) {
468 list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
469 }
470 }
471 }
472
473 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)) {
474 $thisfile_id3v2['comments']['year'] = array($matches[1]);
475 }
476
477
478 if (!empty($thisfile_id3v2['TXXX'])) {
479 // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
480 foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
481 switch ($txxx_array['description']) {
482 case 'replaygain_track_gain':
483 if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
484 $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
485 }
486 break;
487 case 'replaygain_track_peak':
488 if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
489 $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
490 }
491 break;
492 case 'replaygain_album_gain':
493 if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
494 $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
495 }
496 break;
497 }
498 }
499 }
500
501
502 // Set avdataoffset
503 $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
504 if (isset($thisfile_id3v2['footer'])) {
505 $info['avdataoffset'] += 10;
506 }
507
508 return true;
509 }
510
511 /**
512 * @param string $genrestring
513 *
514 * @return array
515 */
516 public function ParseID3v2GenreString($genrestring) {
517 // Parse genres into arrays of genreName and genreID
518 // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
519 // ID3v2.4.x: '21' $00 'Eurodisco' $00
520 $clean_genres = array();
521
522 // hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
523 if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
524 // note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
525 // replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
526 if (strpos($genrestring, '/') !== false) {
527 $LegitimateSlashedGenreList = array( // https://github.com/JamesHeinrich/getID3/issues/223
528 'Pop/Funk', // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
529 'Cut-up/DJ', // Discogs - https://www.discogs.com/style/cut-up/dj
530 'RnB/Swing', // Discogs - https://www.discogs.com/style/rnb/swing
531 'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
532 );
533 $genrestring = str_replace('/', "\x00", $genrestring);
534 foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
535 $genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
536 }
537 }
538
539 // some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
540 if (strpos($genrestring, ';') !== false) {
541 $genrestring = str_replace(';', "\x00", $genrestring);
542 }
543 }
544
545
546 if (strpos($genrestring, "\x00") === false) {
547 $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
548 }
549
550 $genre_elements = explode("\x00", $genrestring);
551 foreach ($genre_elements as $element) {
552 $element = trim($element);
553 if ($element) {
554 if (preg_match('#^[0-9]{1,3}$#', $element)) {
555 $clean_genres[] = getid3_id3v1::LookupGenreName($element);
556 } else {
557 $clean_genres[] = str_replace('((', '(', $element);
558 }
559 }
560 }
561 return $clean_genres;
562 }
563
564 /**
565 * @param array $parsedFrame
566 *
567 * @return bool
568 */
569 public function ParseID3v2Frame(&$parsedFrame) {
570
571 // shortcuts
572 $info = &$this->getid3->info;
573 $id3v2_majorversion = $info['id3v2']['majorversion'];
574
575 $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
576 if (empty($parsedFrame['framenamelong'])) {
577 unset($parsedFrame['framenamelong']);
578 }
579 $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
580 if (empty($parsedFrame['framenameshort'])) {
581 unset($parsedFrame['framenameshort']);
582 }
583
584 if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
585 if ($id3v2_majorversion == 3) {
586 // Frame Header Flags
587 // %abc00000 %ijk00000
588 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
589 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
590 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
591 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
592 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
593 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
594
595 } elseif ($id3v2_majorversion == 4) {
596 // Frame Header Flags
597 // %0abc0000 %0h00kmnp
598 $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
599 $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
600 $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
601 $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
602 $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
603 $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
604 $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
605 $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
606
607 // Frame-level de-unsynchronisation - ID3v2.4
608 if ($parsedFrame['flags']['Unsynchronisation']) {
609 $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
610 }
611
612 if ($parsedFrame['flags']['DataLengthIndicator']) {
613 $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
614 $parsedFrame['data'] = substr($parsedFrame['data'], 4);
615 }
616 }
617
618 // Frame-level de-compression
619 if ($parsedFrame['flags']['compression']) {
620 $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
621 if (!function_exists('gzuncompress')) {
622 $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
623 } else {
624 if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
625 //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
626 $parsedFrame['data'] = $decompresseddata;
627 unset($decompresseddata);
628 } else {
629 $this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
630 }
631 }
632 }
633 }
634
635 if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
636 if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
637 $this->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');
638 }
639 }
640
641 if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
642
643 $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
644 switch ($parsedFrame['frame_name']) {
645 case 'WCOM':
646 $warning .= ' (this is known to happen with files tagged by RioPort)';
647 break;
648
649 default:
650 break;
651 }
652 $this->warning($warning);
653
654 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
655 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
656 // There may be more than one 'UFID' frame in a tag,
657 // but only one with the same 'Owner identifier'.
658 // <Header for 'Unique file identifier', ID: 'UFID'>
659 // Owner identifier <text string> $00
660 // Identifier <up to 64 bytes binary data>
661 $exploded = explode("\x00", $parsedFrame['data'], 2);
662 $parsedFrame['ownerid'] = $exploded[0];
663 $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : '');
664
665 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
666 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
667 // There may be more than one 'TXXX' frame in each tag,
668 // but only one with the same description.
669 // <Header for 'User defined text information frame', ID: 'TXXX'>
670 // Text encoding $xx
671 // Description <text string according to encoding> $00 (00)
672 // Value <text string according to encoding>
673
674 $frame_offset = 0;
675 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
676 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
677 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
678 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
679 $frame_textencoding_terminator = "\x00";
680 }
681 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
682 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
683 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
684 }
685 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
686 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
687 $parsedFrame['encodingid'] = $frame_textencoding;
688 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
689
690 $parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
691 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
692 $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
693 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
694 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
695 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
696 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
697 } else {
698 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
699 }
700 }
701 //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
702
703
704 } elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
705 // There may only be one text information frame of its kind in an tag.
706 // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
707 // excluding 'TXXX' described in 4.2.6.>
708 // Text encoding $xx
709 // Information <text string(s) according to encoding>
710
711 $frame_offset = 0;
712 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
713 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
714 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
715 }
716
717 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
718 $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
719
720 $parsedFrame['encodingid'] = $frame_textencoding;
721 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
722 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
723 // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
724 // This of course breaks when an artist name contains slash character, e.g. "AC/DC"
725 // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
726 // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
727 switch ($parsedFrame['encoding']) {
728 case 'UTF-16':
729 case 'UTF-16BE':
730 case 'UTF-16LE':
731 $wordsize = 2;
732 break;
733 case 'ISO-8859-1':
734 case 'UTF-8':
735 default:
736 $wordsize = 1;
737 break;
738 }
739 $Txxx_elements = array();
740 $Txxx_elements_start_offset = 0;
741 for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
742 if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
743 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
744 $Txxx_elements_start_offset = $i + $wordsize;
745 }
746 }
747 $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
748 foreach ($Txxx_elements as $Txxx_element) {
749 $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
750 if (!empty($string)) {
751 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
752 }
753 }
754 unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
755 }
756
757 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
758 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
759 // There may be more than one 'WXXX' frame in each tag,
760 // but only one with the same description
761 // <Header for 'User defined URL link frame', ID: 'WXXX'>
762 // Text encoding $xx
763 // Description <text string according to encoding> $00 (00)
764 // URL <text string>
765
766 $frame_offset = 0;
767 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
768 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
769 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
770 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
771 $frame_textencoding_terminator = "\x00";
772 }
773 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
774 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
775 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
776 }
777 $parsedFrame['encodingid'] = $frame_textencoding;
778 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
779 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); // according to the frame text encoding
780 $parsedFrame['url'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
781 $parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
782 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
783
784 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
785 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
786 }
787 unset($parsedFrame['data']);
788
789
790 } elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
791 // There may only be one URL link frame of its kind in a tag,
792 // except when stated otherwise in the frame description
793 // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
794 // described in 4.3.2.>
795 // URL <text string>
796
797 $parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
798 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
799 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
800 }
801 unset($parsedFrame['data']);
802
803
804 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
805 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
806 // http://id3.org/id3v2.3.0#sec4.4
807 // There may only be one 'IPL' frame in each tag
808 // <Header for 'User defined URL link frame', ID: 'IPL'>
809 // Text encoding $xx
810 // People list strings <textstrings>
811
812 $frame_offset = 0;
813 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
814 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
815 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
816 }
817 $parsedFrame['encodingid'] = $frame_textencoding;
818 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
819 $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
820
821 // https://www.getid3.org/phpBB3/viewtopic.php?t=1369
822 // "this tag typically contains null terminated strings, which are associated in pairs"
823 // "there are users that use the tag incorrectly"
824 $IPLS_parts = array();
825 if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
826 $IPLS_parts_unsorted = array();
827 if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
828 // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
829 $thisILPS = '';
830 for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
831 $twobytes = substr($parsedFrame['data_raw'], $i, 2);
832 if ($twobytes === "\x00\x00") {
833 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
834 $thisILPS = '';
835 } else {
836 $thisILPS .= $twobytes;
837 }
838 }
839 if (strlen($thisILPS) > 2) { // 2-byte BOM
840 $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
841 }
842 } else {
843 // ISO-8859-1 or UTF-8 or other single-byte-null character set
844 $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
845 }
846 if (count($IPLS_parts_unsorted) == 1) {
847 // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
848 foreach ($IPLS_parts_unsorted as $key => $value) {
849 $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
850 $position = '';
851 foreach ($IPLS_parts_sorted as $person) {
852 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
853 }
854 }
855 } elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
856 $position = '';
857 $person = '';
858 foreach ($IPLS_parts_unsorted as $key => $value) {
859 if (($key % 2) == 0) {
860 $position = $value;
861 } else {
862 $person = $value;
863 $IPLS_parts[] = array('position'=>$position, 'person'=>$person);
864 $position = '';
865 $person = '';
866 }
867 }
868 } else {
869 foreach ($IPLS_parts_unsorted as $key => $value) {
870 $IPLS_parts[] = array($value);
871 }
872 }
873
874 } else {
875 $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
876 }
877 $parsedFrame['data'] = $IPLS_parts;
878
879 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
880 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
881 }
882
883
884 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
885 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
886 // There may only be one 'MCDI' frame in each tag
887 // <Header for 'Music CD identifier', ID: 'MCDI'>
888 // CD TOC <binary data>
889
890 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
891 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
892 }
893
894
895 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
896 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
897 // There may only be one 'ETCO' frame in each tag
898 // <Header for 'Event timing codes', ID: 'ETCO'>
899 // Time stamp format $xx
900 // Where time stamp format is:
901 // $01 (32-bit value) MPEG frames from beginning of file
902 // $02 (32-bit value) milliseconds from beginning of file
903 // Followed by a list of key events in the following format:
904 // Type of event $xx
905 // Time stamp $xx (xx ...)
906 // The 'Time stamp' is set to zero if directly at the beginning of the sound
907 // or after the previous event. All events MUST be sorted in chronological order.
908
909 $frame_offset = 0;
910 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
911
912 while ($frame_offset < strlen($parsedFrame['data'])) {
913 $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1);
914 $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
915 $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
916 $frame_offset += 4;
917 }
918 unset($parsedFrame['data']);
919
920
921 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
922 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
923 // There may only be one 'MLLT' frame in each tag
924 // <Header for 'Location lookup table', ID: 'MLLT'>
925 // MPEG frames between reference $xx xx
926 // Bytes between reference $xx xx xx
927 // Milliseconds between reference $xx xx xx
928 // Bits for bytes deviation $xx
929 // Bits for milliseconds dev. $xx
930 // Then for every reference the following data is included;
931 // Deviation in bytes %xxx....
932 // Deviation in milliseconds %xxx....
933
934 $frame_offset = 0;
935 $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
936 $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
937 $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
938 $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
939 $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
940 $parsedFrame['data'] = substr($parsedFrame['data'], 10);
941 $deviationbitstream = '';
942 while ($frame_offset < strlen($parsedFrame['data'])) {
943 $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
944 }
945 $reference_counter = 0;
946 while (strlen($deviationbitstream) > 0) {
947 $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
948 $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
949 $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
950 $reference_counter++;
951 }
952 unset($parsedFrame['data']);
953
954
955 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
956 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
957 // There may only be one 'SYTC' frame in each tag
958 // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
959 // Time stamp format $xx
960 // Tempo data <binary data>
961 // Where time stamp format is:
962 // $01 (32-bit value) MPEG frames from beginning of file
963 // $02 (32-bit value) milliseconds from beginning of file
964
965 $frame_offset = 0;
966 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
967 $timestamp_counter = 0;
968 while ($frame_offset < strlen($parsedFrame['data'])) {
969 $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
970 if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
971 $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
972 }
973 $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
974 $frame_offset += 4;
975 $timestamp_counter++;
976 }
977 unset($parsedFrame['data']);
978
979
980 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
981 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
982 // There may be more than one 'Unsynchronised lyrics/text transcription' frame
983 // in each tag, but only one with the same language and content descriptor.
984 // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
985 // Text encoding $xx
986 // Language $xx xx xx
987 // Content descriptor <text string according to encoding> $00 (00)
988 // Lyrics/text <full text string according to encoding>
989
990 $frame_offset = 0;
991 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
992 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
993 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
994 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
995 $frame_textencoding_terminator = "\x00";
996 }
997 if (strlen($parsedFrame['data']) >= (4 + strlen($frame_textencoding_terminator))) { // shouldn't be an issue but badly-written files have been spotted in the wild with not only no contents but also missing the required language field, see https://github.com/JamesHeinrich/getID3/issues/315
998 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
999 $frame_offset += 3;
1000 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1001 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1002 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1003 }
1004 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1005 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1006 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1007 $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
1008
1009 $parsedFrame['encodingid'] = $frame_textencoding;
1010 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1011
1012 $parsedFrame['language'] = $frame_language;
1013 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1014 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1015 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1016 }
1017 } else {
1018 $this->warning('Invalid data in frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset']);
1019 }
1020 unset($parsedFrame['data']);
1021
1022
1023 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text
1024 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
1025 // There may be more than one 'SYLT' frame in each tag,
1026 // but only one with the same language and content descriptor.
1027 // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
1028 // Text encoding $xx
1029 // Language $xx xx xx
1030 // Time stamp format $xx
1031 // $01 (32-bit value) MPEG frames from beginning of file
1032 // $02 (32-bit value) milliseconds from beginning of file
1033 // Content type $xx
1034 // Content descriptor <text string according to encoding> $00 (00)
1035 // Terminated text to be synced (typically a syllable)
1036 // Sync identifier (terminator to above string) $00 (00)
1037 // Time stamp $xx (xx ...)
1038
1039 $frame_offset = 0;
1040 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1041 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1042 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1043 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1044 $frame_textencoding_terminator = "\x00";
1045 }
1046 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1047 $frame_offset += 3;
1048 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1049 $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1050 $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1051 $parsedFrame['encodingid'] = $frame_textencoding;
1052 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1053
1054 $parsedFrame['language'] = $frame_language;
1055 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1056
1057 $timestampindex = 0;
1058 $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1059 while (strlen($frame_remainingdata)) {
1060 $frame_offset = 0;
1061 $frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
1062 if ($frame_terminatorpos === false) {
1063 $frame_remainingdata = '';
1064 } else {
1065 if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1066 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1067 }
1068 $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1069
1070 $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
1071 if (strlen($frame_remainingdata)) { // https://github.com/JamesHeinrich/getID3/issues/444
1072 if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
1073 // timestamp probably omitted for first data item
1074 } else {
1075 $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1076 $frame_remainingdata = substr($frame_remainingdata, 4);
1077 }
1078 $timestampindex++;
1079 }
1080 }
1081 }
1082 unset($parsedFrame['data']);
1083
1084
1085 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments
1086 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
1087 // There may be more than one comment frame in each tag,
1088 // but only one with the same language and content descriptor.
1089 // <Header for 'Comment', ID: 'COMM'>
1090 // Text encoding $xx
1091 // Language $xx xx xx
1092 // Short content descrip. <text string according to encoding> $00 (00)
1093 // The actual text <full text string according to encoding>
1094
1095 if (strlen($parsedFrame['data']) < 5) {
1096
1097 $this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
1098
1099 } else {
1100
1101 $frame_offset = 0;
1102 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1103 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1104 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1105 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1106 $frame_textencoding_terminator = "\x00";
1107 }
1108 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1109 $frame_offset += 3;
1110 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1111 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1112 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1113 }
1114 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1115 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1116 $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1117 $frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);
1118
1119 $parsedFrame['encodingid'] = $frame_textencoding;
1120 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1121
1122 $parsedFrame['language'] = $frame_language;
1123 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1124 $parsedFrame['data'] = $frame_text;
1125 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1126 $commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
1127 if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
1128 $info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1129 } else {
1130 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1131 }
1132 }
1133
1134 }
1135
1136 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1137 // There may be more than one 'RVA2' frame in each tag,
1138 // but only one with the same identification string
1139 // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1140 // Identification <text string> $00
1141 // The 'identification' string is used to identify the situation and/or
1142 // device where this adjustment should apply. The following is then
1143 // repeated for every channel:
1144 // Type of channel $xx
1145 // Volume adjustment $xx xx
1146 // Bits representing peak $xx
1147 // Peak volume $xx (xx ...)
1148
1149 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1150 $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1151 if (ord($frame_idstring) === 0) {
1152 $frame_idstring = '';
1153 }
1154 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1155 $parsedFrame['description'] = $frame_idstring;
1156 $RVA2channelcounter = 0;
1157 while (strlen($frame_remainingdata) >= 5) {
1158 $frame_offset = 0;
1159 $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1160 $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1161 $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1162 $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1163 $frame_offset += 2;
1164 $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1165 if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1166 $this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
1167 break;
1168 }
1169 $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1170 $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1171 $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1172 $RVA2channelcounter++;
1173 }
1174 unset($parsedFrame['data']);
1175
1176
1177 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1178 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1179 // There may only be one 'RVA' frame in each tag
1180 // <Header for 'Relative volume adjustment', ID: 'RVA'>
1181 // ID3v2.2 => Increment/decrement %000000ba
1182 // ID3v2.3 => Increment/decrement %00fedcba
1183 // Bits used for volume descr. $xx
1184 // Relative volume change, right $xx xx (xx ...) // a
1185 // Relative volume change, left $xx xx (xx ...) // b
1186 // Peak volume right $xx xx (xx ...)
1187 // Peak volume left $xx xx (xx ...)
1188 // ID3v2.3 only, optional (not present in ID3v2.2):
1189 // Relative volume change, right back $xx xx (xx ...) // c
1190 // Relative volume change, left back $xx xx (xx ...) // d
1191 // Peak volume right back $xx xx (xx ...)
1192 // Peak volume left back $xx xx (xx ...)
1193 // ID3v2.3 only, optional (not present in ID3v2.2):
1194 // Relative volume change, center $xx xx (xx ...) // e
1195 // Peak volume center $xx xx (xx ...)
1196 // ID3v2.3 only, optional (not present in ID3v2.2):
1197 // Relative volume change, bass $xx xx (xx ...) // f
1198 // Peak volume bass $xx xx (xx ...)
1199
1200 $frame_offset = 0;
1201 $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1202 $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1203 $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1204 $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1205 $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1206 $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1207 if ($parsedFrame['incdec']['right'] === false) {
1208 $parsedFrame['volumechange']['right'] *= -1;
1209 }
1210 $frame_offset += $frame_bytesvolume;
1211 $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1212 if ($parsedFrame['incdec']['left'] === false) {
1213 $parsedFrame['volumechange']['left'] *= -1;
1214 }
1215 $frame_offset += $frame_bytesvolume;
1216 $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1217 $frame_offset += $frame_bytesvolume;
1218 $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1219 $frame_offset += $frame_bytesvolume;
1220 if ($id3v2_majorversion == 3) {
1221 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1222 if (strlen($parsedFrame['data']) > 0) {
1223 $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1224 $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1225 $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1226 if ($parsedFrame['incdec']['rightrear'] === false) {
1227 $parsedFrame['volumechange']['rightrear'] *= -1;
1228 }
1229 $frame_offset += $frame_bytesvolume;
1230 $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1231 if ($parsedFrame['incdec']['leftrear'] === false) {
1232 $parsedFrame['volumechange']['leftrear'] *= -1;
1233 }
1234 $frame_offset += $frame_bytesvolume;
1235 $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1236 $frame_offset += $frame_bytesvolume;
1237 $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1238 $frame_offset += $frame_bytesvolume;
1239 }
1240 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1241 if (strlen($parsedFrame['data']) > 0) {
1242 $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1243 $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1244 if ($parsedFrame['incdec']['center'] === false) {
1245 $parsedFrame['volumechange']['center'] *= -1;
1246 }
1247 $frame_offset += $frame_bytesvolume;
1248 $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1249 $frame_offset += $frame_bytesvolume;
1250 }
1251 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1252 if (strlen($parsedFrame['data']) > 0) {
1253 $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1254 $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1255 if ($parsedFrame['incdec']['bass'] === false) {
1256 $parsedFrame['volumechange']['bass'] *= -1;
1257 }
1258 $frame_offset += $frame_bytesvolume;
1259 $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1260 $frame_offset += $frame_bytesvolume;
1261 }
1262 }
1263 unset($parsedFrame['data']);
1264
1265
1266 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1267 // There may be more than one 'EQU2' frame in each tag,
1268 // but only one with the same identification string
1269 // <Header of 'Equalisation (2)', ID: 'EQU2'>
1270 // Interpolation method $xx
1271 // $00 Band
1272 // $01 Linear
1273 // Identification <text string> $00
1274 // The following is then repeated for every adjustment point
1275 // Frequency $xx xx
1276 // Volume adjustment $xx xx
1277
1278 $frame_offset = 0;
1279 $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1280 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1281 $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1282 if (ord($frame_idstring) === 0) {
1283 $frame_idstring = '';
1284 }
1285 $parsedFrame['description'] = $frame_idstring;
1286 $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1287 while (strlen($frame_remainingdata)) {
1288 $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1289 $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1290 $frame_remainingdata = substr($frame_remainingdata, 4);
1291 }
1292 $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1293 unset($parsedFrame['data']);
1294
1295
1296 } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only)
1297 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1298 // There may only be one 'EQUA' frame in each tag
1299 // <Header for 'Relative volume adjustment', ID: 'EQU'>
1300 // Adjustment bits $xx
1301 // This is followed by 2 bytes + ('adjustment bits' rounded up to the
1302 // nearest byte) for every equalisation band in the following format,
1303 // giving a frequency range of 0 - 32767Hz:
1304 // Increment/decrement %x (MSB of the Frequency)
1305 // Frequency (lower 15 bits)
1306 // Adjustment $xx (xx ...)
1307
1308 $frame_offset = 0;
1309 $parsedFrame['adjustmentbits'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1310 $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1311
1312 $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1313 while (strlen($frame_remainingdata) > 0) {
1314 $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1315 $frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1316 $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1317 $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1318 $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1319 if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1320 $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1321 }
1322 $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1323 }
1324 unset($parsedFrame['data']);
1325
1326
1327 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb
1328 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1329 // There may only be one 'RVRB' frame in each tag.
1330 // <Header for 'Reverb', ID: 'RVRB'>
1331 // Reverb left (ms) $xx xx
1332 // Reverb right (ms) $xx xx
1333 // Reverb bounces, left $xx
1334 // Reverb bounces, right $xx
1335 // Reverb feedback, left to left $xx
1336 // Reverb feedback, left to right $xx
1337 // Reverb feedback, right to right $xx
1338 // Reverb feedback, right to left $xx
1339 // Premix left to right $xx
1340 // Premix right to left $xx
1341
1342 $frame_offset = 0;
1343 $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1344 $frame_offset += 2;
1345 $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1346 $frame_offset += 2;
1347 $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1348 $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1349 $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1350 $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1351 $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1352 $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1353 $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1354 $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1355 unset($parsedFrame['data']);
1356
1357
1358 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture
1359 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1360 // There may be several pictures attached to one file,
1361 // each in their individual 'APIC' frame, but only one
1362 // with the same content descriptor
1363 // <Header for 'Attached picture', ID: 'APIC'>
1364 // Text encoding $xx
1365 // ID3v2.3+ => MIME type <text string> $00
1366 // ID3v2.2 => Image format $xx xx xx
1367 // Picture type $xx
1368 // Description <text string according to encoding> $00 (00)
1369 // Picture data <binary data>
1370
1371 $frame_offset = 0;
1372 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1373 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1374 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1375 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1376 $frame_textencoding_terminator = "\x00";
1377 }
1378
1379 $frame_imagetype = null;
1380 $frame_mimetype = null;
1381 if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1382 $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1383 if (strtolower($frame_imagetype) == 'ima') {
1384 // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1385 // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
1386 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1387 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1388 if (ord($frame_mimetype) === 0) {
1389 $frame_mimetype = '';
1390 }
1391 $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1392 if ($frame_imagetype == 'JPEG') {
1393 $frame_imagetype = 'JPG';
1394 }
1395 $frame_offset = $frame_terminatorpos + strlen("\x00");
1396 } else {
1397 $frame_offset += 3;
1398 }
1399 }
1400 if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1401 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1402 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1403 if (ord($frame_mimetype) === 0) {
1404 $frame_mimetype = '';
1405 }
1406 $frame_offset = $frame_terminatorpos + strlen("\x00");
1407 }
1408
1409 $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1410
1411 if ($frame_offset >= $parsedFrame['datalength']) {
1412 $this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
1413 } else {
1414 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1415 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1416 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1417 }
1418 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1419 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1420 $parsedFrame['encodingid'] = $frame_textencoding;
1421 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1422
1423 if ($id3v2_majorversion == 2) {
1424 $parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
1425 } else {
1426 $parsedFrame['mime'] = isset($frame_mimetype) ? $frame_mimetype : null;
1427 }
1428 $parsedFrame['picturetypeid'] = $frame_picturetype;
1429 $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1430 $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
1431 $parsedFrame['datalength'] = strlen($parsedFrame['data']);
1432
1433 $parsedFrame['image_mime'] = '';
1434 $imageinfo = array();
1435 if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
1436 if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1437 $parsedFrame['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
1438 if ($imagechunkcheck[0]) {
1439 $parsedFrame['image_width'] = $imagechunkcheck[0];
1440 }
1441 if ($imagechunkcheck[1]) {
1442 $parsedFrame['image_height'] = $imagechunkcheck[1];
1443 }
1444 }
1445 }
1446
1447 do {
1448 if ($this->getid3->option_save_attachments === false) {
1449 // skip entirely
1450 unset($parsedFrame['data']);
1451 break;
1452 }
1453 $dir = '';
1454 if ($this->getid3->option_save_attachments === true) {
1455 // great
1456/*
1457 } elseif (is_int($this->getid3->option_save_attachments)) {
1458 if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1459 // too big, skip
1460 $this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
1461 unset($parsedFrame['data']);
1462 break;
1463 }
1464*/
1465 } elseif (is_string($this->getid3->option_save_attachments)) {
1466 $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1467 if (!is_dir($dir) || !getID3::is_writable($dir)) {
1468 // cannot write, skip
1469 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
1470 unset($parsedFrame['data']);
1471 break;
1472 }
1473 }
1474 // if we get this far, must be OK
1475 if (is_string($this->getid3->option_save_attachments)) {
1476 $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1477 if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
1478 file_put_contents($destination_filename, $parsedFrame['data']);
1479 } else {
1480 $this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
1481 }
1482 $parsedFrame['data_filename'] = $destination_filename;
1483 unset($parsedFrame['data']);
1484 } else {
1485 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1486 if (!isset($info['id3v2']['comments']['picture'])) {
1487 $info['id3v2']['comments']['picture'] = array();
1488 }
1489 $comments_picture_data = array();
1490 foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
1491 if (isset($parsedFrame[$picture_key])) {
1492 $comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
1493 }
1494 }
1495 $info['id3v2']['comments']['picture'][] = $comments_picture_data;
1496 unset($comments_picture_data);
1497 }
1498 }
1499 } while (false); // @phpstan-ignore-line
1500 }
1501
1502 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
1503 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1504 // There may be more than one 'GEOB' frame in each tag,
1505 // but only one with the same content descriptor
1506 // <Header for 'General encapsulated object', ID: 'GEOB'>
1507 // Text encoding $xx
1508 // MIME type <text string> $00
1509 // Filename <text string according to encoding> $00 (00)
1510 // Content description <text string according to encoding> $00 (00)
1511 // Encapsulated object <binary data>
1512
1513 $frame_offset = 0;
1514 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1515 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1516 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1517 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1518 $frame_textencoding_terminator = "\x00";
1519 }
1520 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1521 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1522 if (ord($frame_mimetype) === 0) {
1523 $frame_mimetype = '';
1524 }
1525 $frame_offset = $frame_terminatorpos + strlen("\x00");
1526
1527 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1528 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1529 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1530 }
1531 $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1532 if (ord($frame_filename) === 0) {
1533 $frame_filename = '';
1534 }
1535 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1536
1537 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1538 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1539 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1540 }
1541 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1542 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1543 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1544
1545 $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1546 $parsedFrame['encodingid'] = $frame_textencoding;
1547 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1548
1549 $parsedFrame['mime'] = $frame_mimetype;
1550 $parsedFrame['filename'] = $frame_filename;
1551 unset($parsedFrame['data']);
1552
1553
1554 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter
1555 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1556 // There may only be one 'PCNT' frame in each tag.
1557 // When the counter reaches all one's, one byte is inserted in
1558 // front of the counter thus making the counter eight bits bigger
1559 // <Header for 'Play counter', ID: 'PCNT'>
1560 // Counter $xx xx xx xx (xx ...)
1561
1562 $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']);
1563
1564
1565 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter
1566 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1567 // There may be more than one 'POPM' frame in each tag,
1568 // but only one with the same email address
1569 // <Header for 'Popularimeter', ID: 'POPM'>
1570 // Email to user <text string> $00
1571 // Rating $xx
1572 // Counter $xx xx xx xx (xx ...)
1573
1574 $frame_offset = 0;
1575 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1576 $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1577 if (ord($frame_emailaddress) === 0) {
1578 $frame_emailaddress = '';
1579 }
1580 $frame_offset = $frame_terminatorpos + strlen("\x00");
1581 $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1582 $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1583 $parsedFrame['email'] = $frame_emailaddress;
1584 $parsedFrame['rating'] = $frame_rating;
1585 unset($parsedFrame['data']);
1586
1587
1588 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size
1589 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1590 // There may only be one 'RBUF' frame in each tag
1591 // <Header for 'Recommended buffer size', ID: 'RBUF'>
1592 // Buffer size $xx xx xx
1593 // Embedded info flag %0000000x
1594 // Offset to next tag $xx xx xx xx
1595
1596 $frame_offset = 0;
1597 $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1598 $frame_offset += 3;
1599
1600 $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1601 $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1602 $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1603 unset($parsedFrame['data']);
1604
1605
1606 } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1607 // There may be more than one 'CRM' frame in a tag,
1608 // but only one with the same 'owner identifier'
1609 // <Header for 'Encrypted meta frame', ID: 'CRM'>
1610 // Owner identifier <textstring> $00 (00)
1611 // Content/explanation <textstring> $00 (00)
1612 // Encrypted datablock <binary data>
1613
1614 $frame_offset = 0;
1615 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1616 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1617 $frame_offset = $frame_terminatorpos + strlen("\x00");
1618
1619 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1620 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1621 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1622 $frame_offset = $frame_terminatorpos + strlen("\x00");
1623
1624 $parsedFrame['ownerid'] = $frame_ownerid;
1625 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1626 unset($parsedFrame['data']);
1627
1628
1629 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption
1630 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1631 // There may be more than one 'AENC' frames in a tag,
1632 // but only one with the same 'Owner identifier'
1633 // <Header for 'Audio encryption', ID: 'AENC'>
1634 // Owner identifier <text string> $00
1635 // Preview start $xx xx
1636 // Preview length $xx xx
1637 // Encryption info <binary data>
1638
1639 $frame_offset = 0;
1640 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1641 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1642 if (ord($frame_ownerid) === 0) {
1643 $frame_ownerid = '';
1644 }
1645 $frame_offset = $frame_terminatorpos + strlen("\x00");
1646 $parsedFrame['ownerid'] = $frame_ownerid;
1647 $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1648 $frame_offset += 2;
1649 $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1650 $frame_offset += 2;
1651 $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1652 unset($parsedFrame['data']);
1653
1654
1655 } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information
1656 (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1657 // There may be more than one 'LINK' frame in a tag,
1658 // but only one with the same contents
1659 // <Header for 'Linked information', ID: 'LINK'>
1660 // ID3v2.3+ => Frame identifier $xx xx xx xx
1661 // ID3v2.2 => Frame identifier $xx xx xx
1662 // URL <text string> $00
1663 // ID and additional data <text string(s)>
1664
1665 $frame_offset = 0;
1666 if ($id3v2_majorversion == 2) {
1667 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1668 $frame_offset += 3;
1669 } else {
1670 $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1671 $frame_offset += 4;
1672 }
1673
1674 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1675 $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1676 if (ord($frame_url) === 0) {
1677 $frame_url = '';
1678 }
1679 $frame_offset = $frame_terminatorpos + strlen("\x00");
1680 $parsedFrame['url'] = $frame_url;
1681
1682 $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1683 if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1684 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
1685 }
1686 unset($parsedFrame['data']);
1687
1688
1689 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1690 // There may only be one 'POSS' frame in each tag
1691 // <Head for 'Position synchronisation', ID: 'POSS'>
1692 // Time stamp format $xx
1693 // Position $xx (xx ...)
1694
1695 $frame_offset = 0;
1696 $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1697 $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1698 unset($parsedFrame['data']);
1699
1700
1701 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1702 // There may be more than one 'Terms of use' frame in a tag,
1703 // but only one with the same 'Language'
1704 // <Header for 'Terms of use frame', ID: 'USER'>
1705 // Text encoding $xx
1706 // Language $xx xx xx
1707 // The actual text <text string according to encoding>
1708
1709 $frame_offset = 0;
1710 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1711 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1712 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1713 }
1714 $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1715 $frame_offset += 3;
1716 $parsedFrame['language'] = $frame_language;
1717 $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1718 $parsedFrame['encodingid'] = $frame_textencoding;
1719 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1720
1721 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1722 $parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1723 if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1724 $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1725 }
1726 unset($parsedFrame['data']);
1727
1728
1729 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1730 // There may only be one 'OWNE' frame in a tag
1731 // <Header for 'Ownership frame', ID: 'OWNE'>
1732 // Text encoding $xx
1733 // Price paid <text string> $00
1734 // Date of purch. <text string>
1735 // Seller <text string according to encoding>
1736
1737 $frame_offset = 0;
1738 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1739 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1740 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1741 }
1742 $parsedFrame['encodingid'] = $frame_textencoding;
1743 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1744
1745 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1746 $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1747 $frame_offset = $frame_terminatorpos + strlen("\x00");
1748
1749 $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1750 $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1751 $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1752
1753 $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1754 if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1755 $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1756 }
1757 $frame_offset += 8;
1758
1759 $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1760 $parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
1761 unset($parsedFrame['data']);
1762
1763
1764 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1765 // There may be more than one 'commercial frame' in a tag,
1766 // but no two may be identical
1767 // <Header for 'Commercial frame', ID: 'COMR'>
1768 // Text encoding $xx
1769 // Price string <text string> $00
1770 // Valid until <text string>
1771 // Contact URL <text string> $00
1772 // Received as $xx
1773 // Name of seller <text string according to encoding> $00 (00)
1774 // Description <text string according to encoding> $00 (00)
1775 // Picture MIME type <string> $00
1776 // Seller logo <binary data>
1777
1778 $frame_offset = 0;
1779 $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1780 $frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
1781 if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1782 $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
1783 $frame_textencoding_terminator = "\x00";
1784 }
1785
1786 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1787 $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1788 $frame_offset = $frame_terminatorpos + strlen("\x00");
1789 $frame_rawpricearray = explode('/', $frame_pricestring);
1790 foreach ($frame_rawpricearray as $key => $val) {
1791 $frame_currencyid = substr($val, 0, 3);
1792 $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1793 $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1794 }
1795
1796 $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1797 $frame_offset += 8;
1798
1799 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1800 $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1801 $frame_offset = $frame_terminatorpos + strlen("\x00");
1802
1803 $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1804
1805 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1806 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1807 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1808 }
1809 $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1810 if (ord($frame_sellername) === 0) {
1811 $frame_sellername = '';
1812 }
1813 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1814
1815 $frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
1816 if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
1817 $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1818 }
1819 $parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1820 $parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
1821 $frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
1822
1823 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1824 $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1825 $frame_offset = $frame_terminatorpos + strlen("\x00");
1826
1827 $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1828
1829 $parsedFrame['encodingid'] = $frame_textencoding;
1830 $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1831
1832 $parsedFrame['pricevaliduntil'] = $frame_datestring;
1833 $parsedFrame['contacturl'] = $frame_contacturl;
1834 $parsedFrame['receivedasid'] = $frame_receivedasid;
1835 $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1836 $parsedFrame['sellername'] = $frame_sellername;
1837 $parsedFrame['mime'] = $frame_mimetype;
1838 $parsedFrame['logo'] = $frame_sellerlogo;
1839 unset($parsedFrame['data']);
1840
1841
1842 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1843 // There may be several 'ENCR' frames in a tag,
1844 // but only one containing the same symbol
1845 // and only one containing the same owner identifier
1846 // <Header for 'Encryption method registration', ID: 'ENCR'>
1847 // Owner identifier <text string> $00
1848 // Method symbol $xx
1849 // Encryption data <binary data>
1850
1851 $frame_offset = 0;
1852 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1853 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1854 if (ord($frame_ownerid) === 0) {
1855 $frame_ownerid = '';
1856 }
1857 $frame_offset = $frame_terminatorpos + strlen("\x00");
1858
1859 $parsedFrame['ownerid'] = $frame_ownerid;
1860 $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1861 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1862
1863
1864 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1865
1866 // There may be several 'GRID' frames in a tag,
1867 // but only one containing the same symbol
1868 // and only one containing the same owner identifier
1869 // <Header for 'Group ID registration', ID: 'GRID'>
1870 // Owner identifier <text string> $00
1871 // Group symbol $xx
1872 // Group dependent data <binary data>
1873
1874 $frame_offset = 0;
1875 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1876 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1877 if (ord($frame_ownerid) === 0) {
1878 $frame_ownerid = '';
1879 }
1880 $frame_offset = $frame_terminatorpos + strlen("\x00");
1881
1882 $parsedFrame['ownerid'] = $frame_ownerid;
1883 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1884 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1885
1886
1887 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1888 // The tag may contain more than one 'PRIV' frame
1889 // but only with different contents
1890 // <Header for 'Private frame', ID: 'PRIV'>
1891 // Owner identifier <text string> $00
1892 // The private data <binary data>
1893
1894 $frame_offset = 0;
1895 $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1896 $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1897 if (ord($frame_ownerid) === 0) {
1898 $frame_ownerid = '';
1899 }
1900 $frame_offset = $frame_terminatorpos + strlen("\x00");
1901
1902 $parsedFrame['ownerid'] = $frame_ownerid;
1903 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1904
1905
1906 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1907 // There may be more than one 'signature frame' in a tag,
1908 // but no two may be identical
1909 // <Header for 'Signature frame', ID: 'SIGN'>
1910 // Group symbol $xx
1911 // Signature <binary data>
1912
1913 $frame_offset = 0;
1914 $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1915 $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1916
1917
1918 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1919 // There may only be one 'seek frame' in a tag
1920 // <Header for 'Seek frame', ID: 'SEEK'>
1921 // Minimum offset to next tag $xx xx xx xx
1922
1923 $frame_offset = 0;
1924 $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1925
1926
1927 } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1928 // There may only be one 'audio seek point index' frame in a tag
1929 // <Header for 'Seek Point Index', ID: 'ASPI'>
1930 // Indexed data start (S) $xx xx xx xx
1931 // Indexed data length (L) $xx xx xx xx
1932 // Number of index points (N) $xx xx
1933 // Bits per index point (b) $xx
1934 // Then for every index point the following data is included:
1935 // Fraction at index (Fi) $xx (xx)
1936
1937 $frame_offset = 0;
1938 $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1939 $frame_offset += 4;
1940 $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1941 $frame_offset += 4;
1942 $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1943 $frame_offset += 2;
1944 $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1945 $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1946 for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
1947 $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1948 $frame_offset += $frame_bytesperpoint;
1949 }
1950 unset($parsedFrame['data']);
1951
1952 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1953 // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1954 // There may only be one 'RGAD' frame in a tag
1955 // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1956 // Peak Amplitude $xx $xx $xx $xx
1957 // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1958 // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1959 // a - name code
1960 // b - originator code
1961 // c - sign bit
1962 // d - replay gain adjustment
1963
1964 $frame_offset = 0;
1965 $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1966 $frame_offset += 4;
1967 foreach (array('track','album') as $rgad_entry_type) {
1968 $rg_adjustment_word = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1969 $frame_offset += 2;
1970 $parsedFrame['raw'][$rgad_entry_type]['name'] = ($rg_adjustment_word & 0xE000) >> 13;
1971 $parsedFrame['raw'][$rgad_entry_type]['originator'] = ($rg_adjustment_word & 0x1C00) >> 10;
1972 $parsedFrame['raw'][$rgad_entry_type]['signbit'] = ($rg_adjustment_word & 0x0200) >> 9;
1973 $parsedFrame['raw'][$rgad_entry_type]['adjustment'] = ($rg_adjustment_word & 0x0100);
1974 }
1975 $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1976 $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1977 $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1978 $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1979 $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1980 $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1981
1982 $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
1983 $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1984 $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1985 $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1986 $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1987
1988 unset($parsedFrame['data']);
1989
1990 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
1991 // http://id3.org/id3v2-chapters-1.0
1992 // <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes)
1993 // Element ID <text string> $00
1994 // Start time $xx xx xx xx
1995 // End time $xx xx xx xx
1996 // Start offset $xx xx xx xx
1997 // End offset $xx xx xx xx
1998 // <Optional embedded sub-frames>
1999
2000 $frame_offset = 0;
2001 @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2002 $frame_offset += strlen($parsedFrame['element_id']."\x00");
2003 $parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2004 $frame_offset += 4;
2005 $parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2006 $frame_offset += 4;
2007 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2008 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2009 $parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2010 }
2011 $frame_offset += 4;
2012 if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
2013 // "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
2014 $parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2015 }
2016 $frame_offset += 4;
2017
2018 if ($frame_offset < strlen($parsedFrame['data'])) {
2019 $parsedFrame['subframes'] = array();
2020 while ($frame_offset < strlen($parsedFrame['data'])) {
2021 // <Optional embedded sub-frames>
2022 $subframe = array();
2023 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2024 $frame_offset += 4;
2025 $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2026 $frame_offset += 4;
2027 $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2028 $frame_offset += 2;
2029 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2030 $this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2031 break;
2032 }
2033 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2034 $frame_offset += $subframe['size'];
2035
2036 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2037 $subframe['text'] = substr($subframe_rawdata, 1);
2038 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2039 $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
2040 switch (substr($encoding_converted_text, 0, 2)) {
2041 case "\xFF\xFE":
2042 case "\xFE\xFF":
2043 switch (strtoupper($info['id3v2']['encoding'])) {
2044 case 'ISO-8859-1':
2045 case 'UTF-8':
2046 $encoding_converted_text = substr($encoding_converted_text, 2);
2047 // remove unwanted byte-order-marks
2048 break;
2049 default:
2050 // ignore
2051 break;
2052 }
2053 break;
2054 default:
2055 // do not remove BOM
2056 break;
2057 }
2058
2059 switch ($subframe['name']) {
2060 case 'TIT2':
2061 $parsedFrame['chapter_name'] = $encoding_converted_text;
2062 $parsedFrame['subframes'][] = $subframe;
2063 break;
2064 case 'TIT3':
2065 $parsedFrame['chapter_description'] = $encoding_converted_text;
2066 $parsedFrame['subframes'][] = $subframe;
2067 break;
2068 case 'WXXX':
2069 @list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
2070 $parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
2071 $parsedFrame['subframes'][] = $subframe;
2072 break;
2073 case 'APIC':
2074 if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
2075 list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
2076 $subframe['image_mime'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
2077 $subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
2078 $subframe['description'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
2079 if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
2080 // the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
2081 // the above regex assumes one byte, if it's actually two then strip the second one here
2082 $subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
2083 }
2084 $subframe['data'] = $subframe_apic_picturedata;
2085 unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
2086 unset($subframe['text'], $parsedFrame['text']);
2087 $parsedFrame['subframes'][] = $subframe;
2088 $parsedFrame['picture_present'] = true;
2089 } else {
2090 $this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
2091 }
2092 break;
2093 default:
2094 $this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
2095 break;
2096 }
2097 }
2098 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2099 unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
2100 }
2101
2102 $id3v2_chapter_entry = array();
2103 foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
2104 if (isset($parsedFrame[$id3v2_chapter_key])) {
2105 $id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
2106 }
2107 }
2108 if (!isset($info['id3v2']['chapters'])) {
2109 $info['id3v2']['chapters'] = array();
2110 }
2111 $info['id3v2']['chapters'][] = $id3v2_chapter_entry;
2112 unset($id3v2_chapter_entry, $id3v2_chapter_key);
2113
2114
2115 } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
2116 // http://id3.org/id3v2-chapters-1.0
2117 // <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes)
2118 // Element ID <text string> $00
2119 // CTOC flags %xx
2120 // Entry count $xx
2121 // Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */
2122 // <Optional embedded sub-frames>
2123
2124 $frame_offset = 0;
2125 @list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
2126 $frame_offset += strlen($parsedFrame['element_id']."\x00");
2127 $ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
2128 $frame_offset += 1;
2129 $parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
2130 $frame_offset += 1;
2131
2132 $terminator_position = null;
2133 for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
2134 $terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
2135 $parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
2136 $frame_offset = $terminator_position + 1;
2137 }
2138
2139 $parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01);
2140 $parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
2141
2142 unset($ctoc_flags_raw, $terminator_position);
2143
2144 if ($frame_offset < strlen($parsedFrame['data'])) {
2145 $parsedFrame['subframes'] = array();
2146 while ($frame_offset < strlen($parsedFrame['data'])) {
2147 // <Optional embedded sub-frames>
2148 $subframe = array();
2149 $subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
2150 $frame_offset += 4;
2151 $subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
2152 $frame_offset += 4;
2153 $subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
2154 $frame_offset += 2;
2155 if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
2156 $this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
2157 break;
2158 }
2159 $subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
2160 $frame_offset += $subframe['size'];
2161
2162 $subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
2163 $subframe['text'] = substr($subframe_rawdata, 1);
2164 $subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
2165 $encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
2166 switch (substr($encoding_converted_text, 0, 2)) {
2167 case "\xFF\xFE":
2168 case "\xFE\xFF":
2169 switch (strtoupper($info['id3v2']['encoding'])) {
2170 case 'ISO-8859-1':
2171 case 'UTF-8':
2172 $encoding_converted_text = substr($encoding_converted_text, 2);
2173 // remove unwanted byte-order-marks
2174 break;
2175 default:
2176 // ignore
2177 break;
2178 }
2179 break;
2180 default:
2181 // do not remove BOM
2182 break;
2183 }
2184
2185 if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
2186 if ($subframe['name'] == 'TIT2') {
2187 $parsedFrame['toc_name'] = $encoding_converted_text;
2188 } elseif ($subframe['name'] == 'TIT3') {
2189 $parsedFrame['toc_description'] = $encoding_converted_text;
2190 }
2191 $parsedFrame['subframes'][] = $subframe;
2192 } else {
2193 $this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
2194 }
2195 }
2196 unset($subframe_rawdata, $subframe, $encoding_converted_text);
2197 }
2198
2199 }
2200
2201 return true;
2202 }
2203
2204 /**
2205 * @param string $data
2206 *
2207 * @return string
2208 */
2209 public function DeUnsynchronise($data) {
2210 return str_replace("\xFF\x00", "\xFF", $data);
2211 }
2212
2213 /**
2214 * @param int $index
2215 *
2216 * @return string
2217 */
2218 public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
2219 static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
2220 0x00 => 'No more than 128 frames and 1 MB total tag size',
2221 0x01 => 'No more than 64 frames and 128 KB total tag size',
2222 0x02 => 'No more than 32 frames and 40 KB total tag size',
2223 0x03 => 'No more than 32 frames and 4 KB total tag size',
2224 );
2225 return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
2226 }
2227
2228 /**
2229 * @param int $index
2230 *
2231 * @return string
2232 */
2233 public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
2234 static $LookupExtendedHeaderRestrictionsTextEncodings = array(
2235 0x00 => 'No restrictions',
2236 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
2237 );
2238 return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
2239 }
2240
2241 /**
2242 * @param int $index
2243 *
2244 * @return string
2245 */
2246 public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
2247 static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
2248 0x00 => 'No restrictions',
2249 0x01 => 'No string is longer than 1024 characters',
2250 0x02 => 'No string is longer than 128 characters',
2251 0x03 => 'No string is longer than 30 characters',
2252 );
2253 return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
2254 }
2255
2256 /**
2257 * @param int $index
2258 *
2259 * @return string
2260 */
2261 public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
2262 static $LookupExtendedHeaderRestrictionsImageEncoding = array(
2263 0x00 => 'No restrictions',
2264 0x01 => 'Images are encoded only with PNG or JPEG',
2265 );
2266 return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
2267 }
2268
2269 /**
2270 * @param int $index
2271 *
2272 * @return string
2273 */
2274 public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
2275 static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
2276 0x00 => 'No restrictions',
2277 0x01 => 'All images are 256x256 pixels or smaller',
2278 0x02 => 'All images are 64x64 pixels or smaller',
2279 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
2280 );
2281 return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
2282 }
2283
2284 /**
2285 * @param string $currencyid
2286 *
2287 * @return string
2288 */
2289 public function LookupCurrencyUnits($currencyid) {
2290
2291 $begin = __LINE__;
2292
2293 /** This is not a comment!
2294
2295
2296 AED Dirhams
2297 AFA Afghanis
2298 ALL Leke
2299 AMD Drams
2300 ANG Guilders
2301 AOA Kwanza
2302 ARS Pesos
2303 ATS Schillings
2304 AUD Dollars
2305 AWG Guilders
2306 AZM Manats
2307 BAM Convertible Marka
2308 BBD Dollars
2309 BDT Taka
2310 BEF Francs
2311 BGL Leva
2312 BHD Dinars
2313 BIF Francs
2314 BMD Dollars
2315 BND Dollars
2316 BOB Bolivianos
2317 BRL Brazil Real
2318 BSD Dollars
2319 BTN Ngultrum
2320 BWP Pulas
2321 BYR Rubles
2322 BZD Dollars
2323 CAD Dollars
2324 CDF Congolese Francs
2325 CHF Francs
2326 CLP Pesos
2327 CNY Yuan Renminbi
2328 COP Pesos
2329 CRC Colones
2330 CUP Pesos
2331 CVE Escudos
2332 CYP Pounds
2333 CZK Koruny
2334 DEM Deutsche Marks
2335 DJF Francs
2336 DKK Kroner
2337 DOP Pesos
2338 DZD Algeria Dinars
2339 EEK Krooni
2340 EGP Pounds
2341 ERN Nakfa
2342 ESP Pesetas
2343 ETB Birr
2344 EUR Euro
2345 FIM Markkaa
2346 FJD Dollars
2347 FKP Pounds
2348 FRF Francs
2349 GBP Pounds
2350 GEL Lari
2351 GGP Pounds
2352 GHC Cedis
2353 GIP Pounds
2354 GMD Dalasi
2355 GNF Francs
2356 GRD Drachmae
2357 GTQ Quetzales
2358 GYD Dollars
2359 HKD Dollars
2360 HNL Lempiras
2361 HRK Kuna
2362 HTG Gourdes
2363 HUF Forints
2364 IDR Rupiahs
2365 IEP Pounds
2366 ILS New Shekels
2367 IMP Pounds
2368 INR Rupees
2369 IQD Dinars
2370 IRR Rials
2371 ISK Kronur
2372 ITL Lire
2373 JEP Pounds
2374 JMD Dollars
2375 JOD Dinars
2376 JPY Yen
2377 KES Shillings
2378 KGS Soms
2379 KHR Riels
2380 KMF Francs
2381 KPW Won
2382 KWD Dinars
2383 KYD Dollars
2384 KZT Tenge
2385 LAK Kips
2386 LBP Pounds
2387 LKR Rupees
2388 LRD Dollars
2389 LSL Maloti
2390 LTL Litai
2391 LUF Francs
2392 LVL Lati
2393 LYD Dinars
2394 MAD Dirhams
2395 MDL Lei
2396 MGF Malagasy Francs
2397 MKD Denars
2398 MMK Kyats
2399 MNT Tugriks
2400 MOP Patacas
2401 MRO Ouguiyas
2402 MTL Liri
2403 MUR Rupees
2404 MVR Rufiyaa
2405 MWK Kwachas
2406 MXN Pesos
2407 MYR Ringgits
2408 MZM Meticais
2409 NAD Dollars
2410 NGN Nairas
2411 NIO Gold Cordobas
2412 NLG Guilders
2413 NOK Krone
2414 NPR Nepal Rupees
2415 NZD Dollars
2416 OMR Rials
2417 PAB Balboa
2418 PEN Nuevos Soles
2419 PGK Kina
2420 PHP Pesos
2421 PKR Rupees
2422 PLN Zlotych
2423 PTE Escudos
2424 PYG Guarani
2425 QAR Rials
2426 ROL Lei
2427 RUR Rubles
2428 RWF Rwanda Francs
2429 SAR Riyals
2430 SBD Dollars
2431 SCR Rupees
2432 SDD Dinars
2433 SEK Kronor
2434 SGD Dollars
2435 SHP Pounds
2436 SIT Tolars
2437 SKK Koruny
2438 SLL Leones
2439 SOS Shillings
2440 SPL Luigini
2441 SRG Guilders
2442 STD Dobras
2443 SVC Colones
2444 SYP Pounds
2445 SZL Emalangeni
2446 THB Baht
2447 TJR Rubles
2448 TMM Manats
2449 TND Dinars
2450 TOP Pa'anga
2451 TRL Liras (old)
2452 TRY Liras
2453 TTD Dollars
2454 TVD Tuvalu Dollars
2455 TWD New Dollars
2456 TZS Shillings
2457 UAH Hryvnia
2458 UGX Shillings
2459 USD Dollars
2460 UYU Pesos
2461 UZS Sums
2462 VAL Lire
2463 VEB Bolivares
2464 VND Dong
2465 VUV Vatu
2466 WST Tala
2467 XAF Francs
2468 XAG Ounces
2469 XAU Ounces
2470 XCD Dollars
2471 XDR Special Drawing Rights
2472 XPD Ounces
2473 XPF Francs
2474 XPT Ounces
2475 YER Rials
2476 YUM New Dinars
2477 ZAR Rand
2478 ZMK Kwacha
2479 ZWD Zimbabwe Dollars
2480
2481 */
2482
2483 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2484 }
2485
2486 /**
2487 * @param string $currencyid
2488 *
2489 * @return string
2490 */
2491 public function LookupCurrencyCountry($currencyid) {
2492
2493 $begin = __LINE__;
2494
2495 /** This is not a comment!
2496
2497 AED United Arab Emirates
2498 AFA Afghanistan
2499 ALL Albania
2500 AMD Armenia
2501 ANG Netherlands Antilles
2502 AOA Angola
2503 ARS Argentina
2504 ATS Austria
2505 AUD Australia
2506 AWG Aruba
2507 AZM Azerbaijan
2508 BAM Bosnia and Herzegovina
2509 BBD Barbados
2510 BDT Bangladesh
2511 BEF Belgium
2512 BGL Bulgaria
2513 BHD Bahrain
2514 BIF Burundi
2515 BMD Bermuda
2516 BND Brunei Darussalam
2517 BOB Bolivia
2518 BRL Brazil
2519 BSD Bahamas
2520 BTN Bhutan
2521 BWP Botswana
2522 BYR Belarus
2523 BZD Belize
2524 CAD Canada
2525 CDF Congo/Kinshasa
2526 CHF Switzerland
2527 CLP Chile
2528 CNY China
2529 COP Colombia
2530 CRC Costa Rica
2531 CUP Cuba
2532 CVE Cape Verde
2533 CYP Cyprus
2534 CZK Czech Republic
2535 DEM Germany
2536 DJF Djibouti
2537 DKK Denmark
2538 DOP Dominican Republic
2539 DZD Algeria
2540 EEK Estonia
2541 EGP Egypt
2542 ERN Eritrea
2543 ESP Spain
2544 ETB Ethiopia
2545 EUR Euro Member Countries
2546 FIM Finland
2547 FJD Fiji
2548 FKP Falkland Islands (Malvinas)
2549 FRF France
2550 GBP United Kingdom
2551 GEL Georgia
2552 GGP Guernsey
2553 GHC Ghana
2554 GIP Gibraltar
2555 GMD Gambia
2556 GNF Guinea
2557 GRD Greece
2558 GTQ Guatemala
2559 GYD Guyana
2560 HKD Hong Kong
2561 HNL Honduras
2562 HRK Croatia
2563 HTG Haiti
2564 HUF Hungary
2565 IDR Indonesia
2566 IEP Ireland (Eire)
2567 ILS Israel
2568 IMP Isle of Man
2569 INR India
2570 IQD Iraq
2571 IRR Iran
2572 ISK Iceland
2573 ITL Italy
2574 JEP Jersey
2575 JMD Jamaica
2576 JOD Jordan
2577 JPY Japan
2578 KES Kenya
2579 KGS Kyrgyzstan
2580 KHR Cambodia
2581 KMF Comoros
2582 KPW Korea
2583 KWD Kuwait
2584 KYD Cayman Islands
2585 KZT Kazakstan
2586 LAK Laos
2587 LBP Lebanon
2588 LKR Sri Lanka
2589 LRD Liberia
2590 LSL Lesotho
2591 LTL Lithuania
2592 LUF Luxembourg
2593 LVL Latvia
2594 LYD Libya
2595 MAD Morocco
2596 MDL Moldova
2597 MGF Madagascar
2598 MKD Macedonia
2599 MMK Myanmar (Burma)
2600 MNT Mongolia
2601 MOP Macau
2602 MRO Mauritania
2603 MTL Malta
2604 MUR Mauritius
2605 MVR Maldives (Maldive Islands)
2606 MWK Malawi
2607 MXN Mexico
2608 MYR Malaysia
2609 MZM Mozambique
2610 NAD Namibia
2611 NGN Nigeria
2612 NIO Nicaragua
2613 NLG Netherlands (Holland)
2614 NOK Norway
2615 NPR Nepal
2616 NZD New Zealand
2617 OMR Oman
2618 PAB Panama
2619 PEN Peru
2620 PGK Papua New Guinea
2621 PHP Philippines
2622 PKR Pakistan
2623 PLN Poland
2624 PTE Portugal
2625 PYG Paraguay
2626 QAR Qatar
2627 ROL Romania
2628 RUR Russia
2629 RWF Rwanda
2630 SAR Saudi Arabia
2631 SBD Solomon Islands
2632 SCR Seychelles
2633 SDD Sudan
2634 SEK Sweden
2635 SGD Singapore
2636 SHP Saint Helena
2637 SIT Slovenia
2638 SKK Slovakia
2639 SLL Sierra Leone
2640 SOS Somalia
2641 SPL Seborga
2642 SRG Suriname
2643 STD São Tome and Principe
2644 SVC El Salvador
2645 SYP Syria
2646 SZL Swaziland
2647 THB Thailand
2648 TJR Tajikistan
2649 TMM Turkmenistan
2650 TND Tunisia
2651 TOP Tonga
2652 TRL Turkey
2653 TRY Turkey
2654 TTD Trinidad and Tobago
2655 TVD Tuvalu
2656 TWD Taiwan
2657 TZS Tanzania
2658 UAH Ukraine
2659 UGX Uganda
2660 USD United States of America
2661 UYU Uruguay
2662 UZS Uzbekistan
2663 VAL Vatican City
2664 VEB Venezuela
2665 VND Viet Nam
2666 VUV Vanuatu
2667 WST Samoa
2668 XAF Communauté Financière Africaine
2669 XAG Silver
2670 XAU Gold
2671 XCD East Caribbean
2672 XDR International Monetary Fund
2673 XPD Palladium
2674 XPF Comptoirs Français du Pacifique
2675 XPT Platinum
2676 YER Yemen
2677 YUM Yugoslavia
2678 ZAR South Africa
2679 ZMK Zambia
2680 ZWD Zimbabwe
2681
2682 */
2683
2684 return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2685 }
2686
2687 /**
2688 * @param string $languagecode
2689 * @param bool $casesensitive
2690 *
2691 * @return string
2692 */
2693 public static function LanguageLookup($languagecode, $casesensitive=false) {
2694
2695 if (!$casesensitive) {
2696 $languagecode = strtolower($languagecode);
2697 }
2698
2699 // http://www.id3.org/id3v2.4.0-structure.txt
2700 // [4. ID3v2 frame overview]
2701 // The three byte language field, present in several frames, is used to
2702 // describe the language of the frame's content, according to ISO-639-2
2703 // [ISO-639-2]. The language should be represented in lower case. If the
2704 // language is not known the string "XXX" should be used.
2705
2706
2707 // ISO 639-2 - http://www.id3.org/iso639-2.html
2708
2709 $begin = __LINE__;
2710
2711 /** This is not a comment!
2712
2713 XXX unknown
2714 xxx unknown
2715 aar Afar
2716 abk Abkhazian
2717 ace Achinese
2718 ach Acoli
2719 ada Adangme
2720 afa Afro-Asiatic (Other)
2721 afh Afrihili
2722 afr Afrikaans
2723 aka Akan
2724 akk Akkadian
2725 alb Albanian
2726 ale Aleut
2727 alg Algonquian Languages
2728 amh Amharic
2729 ang English, Old (ca. 450-1100)
2730 apa Apache Languages
2731 ara Arabic
2732 arc Aramaic
2733 arm Armenian
2734 arn Araucanian
2735 arp Arapaho
2736 art Artificial (Other)
2737 arw Arawak
2738 asm Assamese
2739 ath Athapascan Languages
2740 ava Avaric
2741 ave Avestan
2742 awa Awadhi
2743 aym Aymara
2744 aze Azerbaijani
2745 bad Banda
2746 bai Bamileke Languages
2747 bak Bashkir
2748 bal Baluchi
2749 bam Bambara
2750 ban Balinese
2751 baq Basque
2752 bas Basa
2753 bat Baltic (Other)
2754 bej Beja
2755 bel Byelorussian
2756 bem Bemba
2757 ben Bengali
2758 ber Berber (Other)
2759 bho Bhojpuri
2760 bih Bihari
2761 bik Bikol
2762 bin Bini
2763 bis Bislama
2764 bla Siksika
2765 bnt Bantu (Other)
2766 bod Tibetan
2767 bra Braj
2768 bre Breton
2769 bua Buriat
2770 bug Buginese
2771 bul Bulgarian
2772 bur Burmese
2773 cad Caddo
2774 cai Central American Indian (Other)
2775 car Carib
2776 cat Catalan
2777 cau Caucasian (Other)
2778 ceb Cebuano
2779 cel Celtic (Other)
2780 ces Czech
2781 cha Chamorro
2782 chb Chibcha
2783 che Chechen
2784 chg Chagatai
2785 chi Chinese
2786 chm Mari
2787 chn Chinook jargon
2788 cho Choctaw
2789 chr Cherokee
2790 chu Church Slavic
2791 chv Chuvash
2792 chy Cheyenne
2793 cop Coptic
2794 cor Cornish
2795 cos Corsican
2796 cpe Creoles and Pidgins, English-based (Other)
2797 cpf Creoles and Pidgins, French-based (Other)
2798 cpp Creoles and Pidgins, Portuguese-based (Other)
2799 cre Cree
2800 crp Creoles and Pidgins (Other)
2801 cus Cushitic (Other)
2802 cym Welsh
2803 cze Czech
2804 dak Dakota
2805 dan Danish
2806 del Delaware
2807 deu German
2808 din Dinka
2809 div Divehi
2810 doi Dogri
2811 dra Dravidian (Other)
2812 dua Duala
2813 dum Dutch, Middle (ca. 1050-1350)
2814 dut Dutch
2815 dyu Dyula
2816 dzo Dzongkha
2817 efi Efik
2818 egy Egyptian (Ancient)
2819 eka Ekajuk
2820 ell Greek, Modern (1453-)
2821 elx Elamite
2822 eng English
2823 enm English, Middle (ca. 1100-1500)
2824 epo Esperanto
2825 esk Eskimo (Other)
2826 esl Spanish
2827 est Estonian
2828 eus Basque
2829 ewe Ewe
2830 ewo Ewondo
2831 fan Fang
2832 fao Faroese
2833 fas Persian
2834 fat Fanti
2835 fij Fijian
2836 fin Finnish
2837 fiu Finno-Ugrian (Other)
2838 fon Fon
2839 fra French
2840 fre French
2841 frm French, Middle (ca. 1400-1600)
2842 fro French, Old (842- ca. 1400)
2843 fry Frisian
2844 ful Fulah
2845 gaa Ga
2846 gae Gaelic (Scots)
2847 gai Irish
2848 gay Gayo
2849 gdh Gaelic (Scots)
2850 gem Germanic (Other)
2851 geo Georgian
2852 ger German
2853 gez Geez
2854 gil Gilbertese
2855 glg Gallegan
2856 gmh German, Middle High (ca. 1050-1500)
2857 goh German, Old High (ca. 750-1050)
2858 gon Gondi
2859 got Gothic
2860 grb Grebo
2861 grc Greek, Ancient (to 1453)
2862 gre Greek, Modern (1453-)
2863 grn Guarani
2864 guj Gujarati
2865 hai Haida
2866 hau Hausa
2867 haw Hawaiian
2868 heb Hebrew
2869 her Herero
2870 hil Hiligaynon
2871 him Himachali
2872 hin Hindi
2873 hmo Hiri Motu
2874 hun Hungarian
2875 hup Hupa
2876 hye Armenian
2877 iba Iban
2878 ibo Igbo
2879 ice Icelandic
2880 ijo Ijo
2881 iku Inuktitut
2882 ilo Iloko
2883 ina Interlingua (International Auxiliary language Association)
2884 inc Indic (Other)
2885 ind Indonesian
2886 ine Indo-European (Other)
2887 ine Interlingue
2888 ipk Inupiak
2889 ira Iranian (Other)
2890 iri Irish
2891 iro Iroquoian uages
2892 isl Icelandic
2893 ita Italian
2894 jav Javanese
2895 jaw Javanese
2896 jpn Japanese
2897 jpr Judeo-Persian
2898 jrb Judeo-Arabic
2899 kaa Kara-Kalpak
2900 kab Kabyle
2901 kac Kachin
2902 kal Greenlandic
2903 kam Kamba
2904 kan Kannada
2905 kar Karen
2906 kas Kashmiri
2907 kat Georgian
2908 kau Kanuri
2909 kaw Kawi
2910 kaz Kazakh
2911 kha Khasi
2912 khi Khoisan (Other)
2913 khm Khmer
2914 kho Khotanese
2915 kik Kikuyu
2916 kin Kinyarwanda
2917 kir Kirghiz
2918 kok Konkani
2919 kom Komi
2920 kon Kongo
2921 kor Korean
2922 kpe Kpelle
2923 kro Kru
2924 kru Kurukh
2925 kua Kuanyama
2926 kum Kumyk
2927 kur Kurdish
2928 kus Kusaie
2929 kut Kutenai
2930 lad Ladino
2931 lah Lahnda
2932 lam Lamba
2933 lao Lao
2934 lat Latin
2935 lav Latvian
2936 lez Lezghian
2937 lin Lingala
2938 lit Lithuanian
2939 lol Mongo
2940 loz Lozi
2941 ltz Letzeburgesch
2942 lub Luba-Katanga
2943 lug Ganda
2944 lui Luiseno
2945 lun Lunda
2946 luo Luo (Kenya and Tanzania)
2947 mac Macedonian
2948 mad Madurese
2949 mag Magahi
2950 mah Marshall
2951 mai Maithili
2952 mak Macedonian
2953 mak Makasar
2954 mal Malayalam
2955 man Mandingo
2956 mao Maori
2957 map Austronesian (Other)
2958 mar Marathi
2959 mas Masai
2960 max Manx
2961 may Malay
2962 men Mende
2963 mga Irish, Middle (900 - 1200)
2964 mic Micmac
2965 min Minangkabau
2966 mis Miscellaneous (Other)
2967 mkh Mon-Kmer (Other)
2968 mlg Malagasy
2969 mlt Maltese
2970 mni Manipuri
2971 mno Manobo Languages
2972 moh Mohawk
2973 mol Moldavian
2974 mon Mongolian
2975 mos Mossi
2976 mri Maori
2977 msa Malay
2978 mul Multiple Languages
2979 mun Munda Languages
2980 mus Creek
2981 mwr Marwari
2982 mya Burmese
2983 myn Mayan Languages
2984 nah Aztec
2985 nai North American Indian (Other)
2986 nau Nauru
2987 nav Navajo
2988 nbl Ndebele, South
2989 nde Ndebele, North
2990 ndo Ndongo
2991 nep Nepali
2992 new Newari
2993 nic Niger-Kordofanian (Other)
2994 niu Niuean
2995 nla Dutch
2996 nno Norwegian (Nynorsk)
2997 non Norse, Old
2998 nor Norwegian
2999 nso Sotho, Northern
3000 nub Nubian Languages
3001 nya Nyanja
3002 nym Nyamwezi
3003 nyn Nyankole
3004 nyo Nyoro
3005 nzi Nzima
3006 oci Langue d'Oc (post 1500)
3007 oji Ojibwa
3008 ori Oriya
3009 orm Oromo
3010 osa Osage
3011 oss Ossetic
3012 ota Turkish, Ottoman (1500 - 1928)
3013 oto Otomian Languages
3014 paa Papuan-Australian (Other)
3015 pag Pangasinan
3016 pal Pahlavi
3017 pam Pampanga
3018 pan Panjabi
3019 pap Papiamento
3020 pau Palauan
3021 peo Persian, Old (ca 600 - 400 B.C.)
3022 per Persian
3023 phn Phoenician
3024 pli Pali
3025 pol Polish
3026 pon Ponape
3027 por Portuguese
3028 pra Prakrit uages
3029 pro Provencal, Old (to 1500)
3030 pus Pushto
3031 que Quechua
3032 raj Rajasthani
3033 rar Rarotongan
3034 roa Romance (Other)
3035 roh Rhaeto-Romance
3036 rom Romany
3037 ron Romanian
3038 rum Romanian
3039 run Rundi
3040 rus Russian
3041 sad Sandawe
3042 sag Sango
3043 sah Yakut
3044 sai South American Indian (Other)
3045 sal Salishan Languages
3046 sam Samaritan Aramaic
3047 san Sanskrit
3048 sco Scots
3049 scr Serbo-Croatian
3050 sel Selkup
3051 sem Semitic (Other)
3052 sga Irish, Old (to 900)
3053 shn Shan
3054 sid Sidamo
3055 sin Singhalese
3056 sio Siouan Languages
3057 sit Sino-Tibetan (Other)
3058 sla Slavic (Other)
3059 slk Slovak
3060 slo Slovak
3061 slv Slovenian
3062 smi Sami Languages
3063 smo Samoan
3064 sna Shona
3065 snd Sindhi
3066 sog Sogdian
3067 som Somali
3068 son Songhai
3069 sot Sotho, Southern
3070 spa Spanish
3071 sqi Albanian
3072 srd Sardinian
3073 srr Serer
3074 ssa Nilo-Saharan (Other)
3075 ssw Siswant
3076 ssw Swazi
3077 suk Sukuma
3078 sun Sudanese
3079 sus Susu
3080 sux Sumerian
3081 sve Swedish
3082 swa Swahili
3083 swe Swedish
3084 syr Syriac
3085 tah Tahitian
3086 tam Tamil
3087 tat Tatar
3088 tel Telugu
3089 tem Timne
3090 ter Tereno
3091 tgk Tajik
3092 tgl Tagalog
3093 tha Thai
3094 tib Tibetan
3095 tig Tigre
3096 tir Tigrinya
3097 tiv Tivi
3098 tli Tlingit
3099 tmh Tamashek
3100 tog Tonga (Nyasa)
3101 ton Tonga (Tonga Islands)
3102 tru Truk
3103 tsi Tsimshian
3104 tsn Tswana
3105 tso Tsonga
3106 tuk Turkmen
3107 tum Tumbuka
3108 tur Turkish
3109 tut Altaic (Other)
3110 twi Twi
3111 tyv Tuvinian
3112 uga Ugaritic
3113 uig Uighur
3114 ukr Ukrainian
3115 umb Umbundu
3116 und Undetermined
3117 urd Urdu
3118 uzb Uzbek
3119 vai Vai
3120 ven Venda
3121 vie Vietnamese
3122 vol Volapük
3123 vot Votic
3124 wak Wakashan Languages
3125 wal Walamo
3126 war Waray
3127 was Washo
3128 wel Welsh
3129 wen Sorbian Languages
3130 wol Wolof
3131 xho Xhosa
3132 yao Yao
3133 yap Yap
3134 yid Yiddish
3135 yor Yoruba
3136 zap Zapotec
3137 zen Zenaga
3138 zha Zhuang
3139 zho Chinese
3140 zul Zulu
3141 zun Zuni
3142
3143 */
3144
3145 return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
3146 }
3147
3148 /**
3149 * @param int $index
3150 *
3151 * @return string
3152 */
3153 public static function ETCOEventLookup($index) {
3154 if (($index >= 0x17) && ($index <= 0xDF)) {
3155 return 'reserved for future use';
3156 }
3157 if (($index >= 0xE0) && ($index <= 0xEF)) {
3158 return 'not predefined synch 0-F';
3159 }
3160 if (($index >= 0xF0) && ($index <= 0xFC)) {
3161 return 'reserved for future use';
3162 }
3163
3164 static $EventLookup = array(
3165 0x00 => 'padding (has no meaning)',
3166 0x01 => 'end of initial silence',
3167 0x02 => 'intro start',
3168 0x03 => 'main part start',
3169 0x04 => 'outro start',
3170 0x05 => 'outro end',
3171 0x06 => 'verse start',
3172 0x07 => 'refrain start',
3173 0x08 => 'interlude start',
3174 0x09 => 'theme start',
3175 0x0A => 'variation start',
3176 0x0B => 'key change',
3177 0x0C => 'time change',
3178 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
3179 0x0E => 'sustained noise',
3180 0x0F => 'sustained noise end',
3181 0x10 => 'intro end',
3182 0x11 => 'main part end',
3183 0x12 => 'verse end',
3184 0x13 => 'refrain end',
3185 0x14 => 'theme end',
3186 0x15 => 'profanity',
3187 0x16 => 'profanity end',
3188 0xFD => 'audio end (start of silence)',
3189 0xFE => 'audio file ends',
3190 0xFF => 'one more byte of events follows'
3191 );
3192
3193 return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
3194 }
3195
3196 /**
3197 * @param int $index
3198 *
3199 * @return string
3200 */
3201 public static function SYTLContentTypeLookup($index) {
3202 static $SYTLContentTypeLookup = array(
3203 0x00 => 'other',
3204 0x01 => 'lyrics',
3205 0x02 => 'text transcription',
3206 0x03 => 'movement/part name', // (e.g. 'Adagio')
3207 0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
3208 0x05 => 'chord', // (e.g. 'Bb F Fsus')
3209 0x06 => 'trivia/\'pop up\' information',
3210 0x07 => 'URLs to webpages',
3211 0x08 => 'URLs to images'
3212 );
3213
3214 return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
3215 }
3216
3217 /**
3218 * @param int $index
3219 * @param bool $returnarray
3220 *
3221 * @return array|string
3222 */
3223 public static function APICPictureTypeLookup($index, $returnarray=false) {
3224 static $APICPictureTypeLookup = array(
3225 0x00 => 'Other',
3226 0x01 => '32x32 pixels \'file icon\' (PNG only)',
3227 0x02 => 'Other file icon',
3228 0x03 => 'Cover (front)',
3229 0x04 => 'Cover (back)',
3230 0x05 => 'Leaflet page',
3231 0x06 => 'Media (e.g. label side of CD)',
3232 0x07 => 'Lead artist/lead performer/soloist',
3233 0x08 => 'Artist/performer',
3234 0x09 => 'Conductor',
3235 0x0A => 'Band/Orchestra',
3236 0x0B => 'Composer',
3237 0x0C => 'Lyricist/text writer',
3238 0x0D => 'Recording Location',
3239 0x0E => 'During recording',
3240 0x0F => 'During performance',
3241 0x10 => 'Movie/video screen capture',
3242 0x11 => 'A bright coloured fish',
3243 0x12 => 'Illustration',
3244 0x13 => 'Band/artist logotype',
3245 0x14 => 'Publisher/Studio logotype'
3246 );
3247 if ($returnarray) {
3248 return $APICPictureTypeLookup;
3249 }
3250 return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
3251 }
3252
3253 /**
3254 * @param int $index
3255 *
3256 * @return string
3257 */
3258 public static function COMRReceivedAsLookup($index) {
3259 static $COMRReceivedAsLookup = array(
3260 0x00 => 'Other',
3261 0x01 => 'Standard CD album with other songs',
3262 0x02 => 'Compressed audio on CD',
3263 0x03 => 'File over the Internet',
3264 0x04 => 'Stream over the Internet',
3265 0x05 => 'As note sheets',
3266 0x06 => 'As note sheets in a book with other sheets',
3267 0x07 => 'Music on other media',
3268 0x08 => 'Non-musical merchandise'
3269 );
3270
3271 return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
3272 }
3273
3274 /**
3275 * @param int $index
3276 *
3277 * @return string
3278 */
3279 public static function RVA2ChannelTypeLookup($index) {
3280 static $RVA2ChannelTypeLookup = array(
3281 0x00 => 'Other',
3282 0x01 => 'Master volume',
3283 0x02 => 'Front right',
3284 0x03 => 'Front left',
3285 0x04 => 'Back right',
3286 0x05 => 'Back left',
3287 0x06 => 'Front centre',
3288 0x07 => 'Back centre',
3289 0x08 => 'Subwoofer'
3290 );
3291
3292 return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
3293 }
3294
3295 /**
3296 * @param string $framename
3297 *
3298 * @return string
3299 */
3300 public static function FrameNameLongLookup($framename) {
3301
3302 $begin = __LINE__;
3303
3304 /** This is not a comment!
3305
3306 AENC Audio encryption
3307 APIC Attached picture
3308 ASPI Audio seek point index
3309 BUF Recommended buffer size
3310 CNT Play counter
3311 COM Comments
3312 COMM Comments
3313 COMR Commercial frame
3314 CRA Audio encryption
3315 CRM Encrypted meta frame
3316 ENCR Encryption method registration
3317 EQU Equalisation
3318 EQU2 Equalisation (2)
3319 EQUA Equalisation
3320 ETC Event timing codes
3321 ETCO Event timing codes
3322 GEO General encapsulated object
3323 GEOB General encapsulated object
3324 GRID Group identification registration
3325 IPL Involved people list
3326 IPLS Involved people list
3327 LINK Linked information
3328 LNK Linked information
3329 MCDI Music CD identifier
3330 MCI Music CD Identifier
3331 MLL MPEG location lookup table
3332 MLLT MPEG location lookup table
3333 OWNE Ownership frame
3334 PCNT Play counter
3335 PIC Attached picture
3336 POP Popularimeter
3337 POPM Popularimeter
3338 POSS Position synchronisation frame
3339 PRIV Private frame
3340 RBUF Recommended buffer size
3341 REV Reverb
3342 RVA Relative volume adjustment
3343 RVA2 Relative volume adjustment (2)
3344 RVAD Relative volume adjustment
3345 RVRB Reverb
3346 SEEK Seek frame
3347 SIGN Signature frame
3348 SLT Synchronised lyric/text
3349 STC Synced tempo codes
3350 SYLT Synchronised lyric/text
3351 SYTC Synchronised tempo codes
3352 TAL Album/Movie/Show title
3353 TALB Album/Movie/Show title
3354 TBP BPM (Beats Per Minute)
3355 TBPM BPM (beats per minute)
3356 TCM Composer
3357 TCMP Part of a compilation
3358 TCO Content type
3359 TCOM Composer
3360 TCON Content type
3361 TCOP Copyright message
3362 TCP Part of a compilation
3363 TCR Copyright message
3364 TDA Date
3365 TDAT Date
3366 TDEN Encoding time
3367 TDLY Playlist delay
3368 TDOR Original release time
3369 TDRC Recording time
3370 TDRL Release time
3371 TDTG Tagging time
3372 TDY Playlist delay
3373 TEN Encoded by
3374 TENC Encoded by
3375 TEXT Lyricist/Text writer
3376 TFLT File type
3377 TFT File type
3378 TIM Time
3379 TIME Time
3380 TIPL Involved people list
3381 TIT1 Content group description
3382 TIT2 Title/songname/content description
3383 TIT3 Subtitle/Description refinement
3384 TKE Initial key
3385 TKEY Initial key
3386 TLA Language(s)
3387 TLAN Language(s)
3388 TLE Length
3389 TLEN Length
3390 TMCL Musician credits list
3391 TMED Media type
3392 TMOO Mood
3393 TMT Media type
3394 TOA Original artist(s)/performer(s)
3395 TOAL Original album/movie/show title
3396 TOF Original filename
3397 TOFN Original filename
3398 TOL Original Lyricist(s)/text writer(s)
3399 TOLY Original lyricist(s)/text writer(s)
3400 TOPE Original artist(s)/performer(s)
3401 TOR Original release year
3402 TORY Original release year
3403 TOT Original album/Movie/Show title
3404 TOWN File owner/licensee
3405 TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3406 TP2 Band/Orchestra/Accompaniment
3407 TP3 Conductor/Performer refinement
3408 TP4 Interpreted, remixed, or otherwise modified by
3409 TPA Part of a set
3410 TPB Publisher
3411 TPE1 Lead performer(s)/Soloist(s)
3412 TPE2 Band/orchestra/accompaniment
3413 TPE3 Conductor/performer refinement
3414 TPE4 Interpreted, remixed, or otherwise modified by
3415 TPOS Part of a set
3416 TPRO Produced notice
3417 TPUB Publisher
3418 TRC ISRC (International Standard Recording Code)
3419 TRCK Track number/Position in set
3420 TRD Recording dates
3421 TRDA Recording dates
3422 TRK Track number/Position in set
3423 TRSN Internet radio station name
3424 TRSO Internet radio station owner
3425 TS2 Album-Artist sort order
3426 TSA Album sort order
3427 TSC Composer sort order
3428 TSI Size
3429 TSIZ Size
3430 TSO2 Album-Artist sort order
3431 TSOA Album sort order
3432 TSOC Composer sort order
3433 TSOP Performer sort order
3434 TSOT Title sort order
3435 TSP Performer sort order
3436 TSRC ISRC (international standard recording code)
3437 TSS Software/hardware and settings used for encoding
3438 TSSE Software/Hardware and settings used for encoding
3439 TSST Set subtitle
3440 TST Title sort order
3441 TT1 Content group description
3442 TT2 Title/Songname/Content description
3443 TT3 Subtitle/Description refinement
3444 TXT Lyricist/text writer
3445 TXX User defined text information frame
3446 TXXX User defined text information frame
3447 TYE Year
3448 TYER Year
3449 UFI Unique file identifier
3450 UFID Unique file identifier
3451 ULT Unsynchronised lyric/text transcription
3452 USER Terms of use
3453 USLT Unsynchronised lyric/text transcription
3454 WAF Official audio file webpage
3455 WAR Official artist/performer webpage
3456 WAS Official audio source webpage
3457 WCM Commercial information
3458 WCOM Commercial information
3459 WCOP Copyright/Legal information
3460 WCP Copyright/Legal information
3461 WOAF Official audio file webpage
3462 WOAR Official artist/performer webpage
3463 WOAS Official audio source webpage
3464 WORS Official Internet radio station homepage
3465 WPAY Payment
3466 WPB Publishers official webpage
3467 WPUB Publishers official webpage
3468 WXX User defined URL link frame
3469 WXXX User defined URL link frame
3470 TFEA Featured Artist
3471 TSTU Recording Studio
3472 rgad Replay Gain Adjustment
3473
3474 */
3475
3476 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3477
3478 // Last three:
3479 // from Helium2 [www.helium2.com]
3480 // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3481 }
3482
3483 /**
3484 * @param string $framename
3485 *
3486 * @return string
3487 */
3488 public static function FrameNameShortLookup($framename) {
3489
3490 $begin = __LINE__;
3491
3492 /** This is not a comment!
3493
3494 AENC audio_encryption
3495 APIC attached_picture
3496 ASPI audio_seek_point_index
3497 BUF recommended_buffer_size
3498 CNT play_counter
3499 COM comment
3500 COMM comment
3501 COMR commercial_frame
3502 CRA audio_encryption
3503 CRM encrypted_meta_frame
3504 ENCR encryption_method_registration
3505 EQU equalisation
3506 EQU2 equalisation
3507 EQUA equalisation
3508 ETC event_timing_codes
3509 ETCO event_timing_codes
3510 GEO general_encapsulated_object
3511 GEOB general_encapsulated_object
3512 GRID group_identification_registration
3513 IPL involved_people_list
3514 IPLS involved_people_list
3515 LINK linked_information
3516 LNK linked_information
3517 MCDI music_cd_identifier
3518 MCI music_cd_identifier
3519 MLL mpeg_location_lookup_table
3520 MLLT mpeg_location_lookup_table
3521 OWNE ownership_frame
3522 PCNT play_counter
3523 PIC attached_picture
3524 POP popularimeter
3525 POPM popularimeter
3526 POSS position_synchronisation_frame
3527 PRIV private_frame
3528 RBUF recommended_buffer_size
3529 REV reverb
3530 RVA relative_volume_adjustment
3531 RVA2 relative_volume_adjustment
3532 RVAD relative_volume_adjustment
3533 RVRB reverb
3534 SEEK seek_frame
3535 SIGN signature_frame
3536 SLT synchronised_lyric
3537 STC synced_tempo_codes
3538 SYLT synchronised_lyric
3539 SYTC synchronised_tempo_codes
3540 TAL album
3541 TALB album
3542 TBP bpm
3543 TBPM bpm
3544 TCM composer
3545 TCMP part_of_a_compilation
3546 TCO genre
3547 TCOM composer
3548 TCON genre
3549 TCOP copyright_message
3550 TCP part_of_a_compilation
3551 TCR copyright_message
3552 TDA date
3553 TDAT date
3554 TDEN encoding_time
3555 TDLY playlist_delay
3556 TDOR original_release_time
3557 TDRC recording_time
3558 TDRL release_time
3559 TDTG tagging_time
3560 TDY playlist_delay
3561 TEN encoded_by
3562 TENC encoded_by
3563 TEXT lyricist
3564 TFLT file_type
3565 TFT file_type
3566 TIM time
3567 TIME time
3568 TIPL involved_people_list
3569 TIT1 content_group_description
3570 TIT2 title
3571 TIT3 subtitle
3572 TKE initial_key
3573 TKEY initial_key
3574 TLA language
3575 TLAN language
3576 TLE length
3577 TLEN length
3578 TMCL musician_credits_list
3579 TMED media_type
3580 TMOO mood
3581 TMT media_type
3582 TOA original_artist
3583 TOAL original_album
3584 TOF original_filename
3585 TOFN original_filename
3586 TOL original_lyricist
3587 TOLY original_lyricist
3588 TOPE original_artist
3589 TOR original_year
3590 TORY original_year
3591 TOT original_album
3592 TOWN file_owner
3593 TP1 artist
3594 TP2 band
3595 TP3 conductor
3596 TP4 remixer
3597 TPA part_of_a_set
3598 TPB publisher
3599 TPE1 artist
3600 TPE2 band
3601 TPE3 conductor
3602 TPE4 remixer
3603 TPOS part_of_a_set
3604 TPRO produced_notice
3605 TPUB publisher
3606 TRC isrc
3607 TRCK track_number
3608 TRD recording_dates
3609 TRDA recording_dates
3610 TRK track_number
3611 TRSN internet_radio_station_name
3612 TRSO internet_radio_station_owner
3613 TS2 album_artist_sort_order
3614 TSA album_sort_order
3615 TSC composer_sort_order
3616 TSI size
3617 TSIZ size
3618 TSO2 album_artist_sort_order
3619 TSOA album_sort_order
3620 TSOC composer_sort_order
3621 TSOP performer_sort_order
3622 TSOT title_sort_order
3623 TSP performer_sort_order
3624 TSRC isrc
3625 TSS encoder_settings
3626 TSSE encoder_settings
3627 TSST set_subtitle
3628 TST title_sort_order
3629 TT1 content_group_description
3630 TT2 title
3631 TT3 subtitle
3632 TXT lyricist
3633 TXX text
3634 TXXX text
3635 TYE year
3636 TYER year
3637 UFI unique_file_identifier
3638 UFID unique_file_identifier
3639 ULT unsynchronised_lyric
3640 USER terms_of_use
3641 USLT unsynchronised_lyric
3642 WAF url_file
3643 WAR url_artist
3644 WAS url_source
3645 WCM commercial_information
3646 WCOM commercial_information
3647 WCOP copyright
3648 WCP copyright
3649 WOAF url_file
3650 WOAR url_artist
3651 WOAS url_source
3652 WORS url_station
3653 WPAY url_payment
3654 WPB url_publisher
3655 WPUB url_publisher
3656 WXX url_user
3657 WXXX url_user
3658 TFEA featured_artist
3659 TSTU recording_studio
3660 rgad replay_gain_adjustment
3661
3662 */
3663
3664 return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3665 }
3666
3667 /**
3668 * @param string $encoding
3669 *
3670 * @return string
3671 */
3672 public static function TextEncodingTerminatorLookup($encoding) {
3673 // http://www.id3.org/id3v2.4.0-structure.txt
3674 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3675 static $TextEncodingTerminatorLookup = array(
3676 0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3677 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3678 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3679 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3680 255 => "\x00\x00"
3681 );
3682 return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
3683 }
3684
3685 /**
3686 * @param int $encoding
3687 *
3688 * @return string
3689 */
3690 public static function TextEncodingNameLookup($encoding) {
3691 // http://www.id3.org/id3v2.4.0-structure.txt
3692 // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3693 static $TextEncodingNameLookup = array(
3694 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3695 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3696 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3697 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3698 255 => 'UTF-16BE'
3699 );
3700 return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3701 }
3702
3703 /**
3704 * @param string $string
3705 * @param string $terminator
3706 *
3707 * @return string
3708 */
3709 public static function RemoveStringTerminator($string, $terminator) {
3710 // Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
3711 // https://github.com/JamesHeinrich/getID3/issues/121
3712 // https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
3713 if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
3714 $string = substr($string, 0, -strlen($terminator));
3715 }
3716 return $string;
3717 }
3718
3719 /**
3720 * @param string $string
3721 *
3722 * @return string
3723 */
3724 public static function MakeUTF16emptyStringEmpty($string) {
3725 if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
3726 // if string only contains a BOM or terminator then make it actually an empty string
3727 $string = '';
3728 }
3729 return $string;
3730 }
3731
3732 /**
3733 * @param string $framename
3734 * @param int $id3v2majorversion
3735 *
3736 * @return bool|int
3737 */
3738 public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3739 switch ($id3v2majorversion) {
3740 case 2:
3741 return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3742
3743 case 3:
3744 case 4:
3745 return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3746 }
3747 return false;
3748 }
3749
3750 /**
3751 * @param string $numberstring
3752 * @param bool $allowdecimal
3753 * @param bool $allownegative
3754 *
3755 * @return bool
3756 */
3757 public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3758 $pattern = '#^';
3759 $pattern .= ($allownegative ? '\\-?' : '');
3760 $pattern .= '[0-9]+';
3761 $pattern .= ($allowdecimal ? '(\\.[0-9]+)?' : '');
3762 $pattern .= '$#';
3763 return preg_match($pattern, $numberstring);
3764 }
3765
3766 /**
3767 * @param string $datestamp
3768 *
3769 * @return bool
3770 */
3771 public static function IsValidDateStampString($datestamp) {
3772 if (!preg_match('#^[12][0-9]{3}[01][0-9][0123][0-9]$#', $datestamp)) {
3773 return false;
3774 }
3775 $year = substr($datestamp, 0, 4);
3776 $month = substr($datestamp, 4, 2);
3777 $day = substr($datestamp, 6, 2);
3778 if (($year == 0) || ($month == 0) || ($day == 0)) {
3779 return false;
3780 }
3781 if ($month > 12) {
3782 return false;
3783 }
3784 if ($day > 31) {
3785 return false;
3786 }
3787 if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3788 return false;
3789 }
3790 if (($day > 29) && ($month == 2)) {
3791 return false;
3792 }
3793 return true;
3794 }
3795
3796 /**
3797 * @param int $majorversion
3798 *
3799 * @return int
3800 */
3801 public static function ID3v2HeaderLength($majorversion) {
3802 return (($majorversion == 2) ? 6 : 10);
3803 }
3804
3805 /**
3806 * @param string $frame_name
3807 *
3808 * @return string|false
3809 */
3810 public static function ID3v22iTunesBrokenFrameName($frame_name) {
3811 // iTunes (multiple versions) has been known to write ID3v2.3 style frames
3812 // but use ID3v2.2 frame names, right-padded using either [space] or [null]
3813 // to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
3814 // This function will detect and translate the corrupt frame name into ID3v2.3 standard.
3815 static $ID3v22_iTunes_BrokenFrames = array(
3816 'BUF' => 'RBUF', // Recommended buffer size
3817 'CNT' => 'PCNT', // Play counter
3818 'COM' => 'COMM', // Comments
3819 'CRA' => 'AENC', // Audio encryption
3820 'EQU' => 'EQUA', // Equalisation
3821 'ETC' => 'ETCO', // Event timing codes
3822 'GEO' => 'GEOB', // General encapsulated object
3823 'IPL' => 'IPLS', // Involved people list
3824 'LNK' => 'LINK', // Linked information
3825 'MCI' => 'MCDI', // Music CD identifier
3826 'MLL' => 'MLLT', // MPEG location lookup table
3827 'PIC' => 'APIC', // Attached picture
3828 'POP' => 'POPM', // Popularimeter
3829 'REV' => 'RVRB', // Reverb
3830 'RVA' => 'RVAD', // Relative volume adjustment
3831 'SLT' => 'SYLT', // Synchronised lyric/text
3832 'STC' => 'SYTC', // Synchronised tempo codes
3833 'TAL' => 'TALB', // Album/Movie/Show title
3834 'TBP' => 'TBPM', // BPM (beats per minute)
3835 'TCM' => 'TCOM', // Composer
3836 'TCO' => 'TCON', // Content type
3837 'TCP' => 'TCMP', // Part of a compilation
3838 'TCR' => 'TCOP', // Copyright message
3839 'TDA' => 'TDAT', // Date
3840 'TDY' => 'TDLY', // Playlist delay
3841 'TEN' => 'TENC', // Encoded by
3842 'TFT' => 'TFLT', // File type
3843 'TIM' => 'TIME', // Time
3844 'TKE' => 'TKEY', // Initial key
3845 'TLA' => 'TLAN', // Language(s)
3846 'TLE' => 'TLEN', // Length
3847 'TMT' => 'TMED', // Media type
3848 'TOA' => 'TOPE', // Original artist(s)/performer(s)
3849 'TOF' => 'TOFN', // Original filename
3850 'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
3851 'TOR' => 'TORY', // Original release year
3852 'TOT' => 'TOAL', // Original album/movie/show title
3853 'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
3854 'TP2' => 'TPE2', // Band/orchestra/accompaniment
3855 'TP3' => 'TPE3', // Conductor/performer refinement
3856 'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
3857 'TPA' => 'TPOS', // Part of a set
3858 'TPB' => 'TPUB', // Publisher
3859 'TRC' => 'TSRC', // ISRC (international standard recording code)
3860 'TRD' => 'TRDA', // Recording dates
3861 'TRK' => 'TRCK', // Track number/Position in set
3862 'TS2' => 'TSO2', // Album-Artist sort order
3863 'TSA' => 'TSOA', // Album sort order
3864 'TSC' => 'TSOC', // Composer sort order
3865 'TSI' => 'TSIZ', // Size
3866 'TSP' => 'TSOP', // Performer sort order
3867 'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
3868 'TST' => 'TSOT', // Title sort order
3869 'TT1' => 'TIT1', // Content group description
3870 'TT2' => 'TIT2', // Title/songname/content description
3871 'TT3' => 'TIT3', // Subtitle/Description refinement
3872 'TXT' => 'TEXT', // Lyricist/Text writer
3873 'TXX' => 'TXXX', // User defined text information frame
3874 'TYE' => 'TYER', // Year
3875 'UFI' => 'UFID', // Unique file identifier
3876 'ULT' => 'USLT', // Unsynchronised lyric/text transcription
3877 'WAF' => 'WOAF', // Official audio file webpage
3878 'WAR' => 'WOAR', // Official artist/performer webpage
3879 'WAS' => 'WOAS', // Official audio source webpage
3880 'WCM' => 'WCOM', // Commercial information
3881 'WCP' => 'WCOP', // Copyright/Legal information
3882 'WPB' => 'WPUB', // Publishers official webpage
3883 'WXX' => 'WXXX', // User defined URL link frame
3884 );
3885 if (strlen($frame_name) == 4) {
3886 if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
3887 if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
3888 return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
3889 }
3890 }
3891 }
3892 return false;
3893 }
3894
3895}
3896
3897