2
/////////////////////////////////////////////////////////////////
3
/// getID3() by James Heinrich <info@getid3.org> //
4
// available at http://getid3.sourceforge.net //
5
// or http://www.getid3.org //
6
/////////////////////////////////////////////////////////////////
7
// See readme.txt for more details //
8
/////////////////////////////////////////////////////////////////
10
// module.tag.id3v2.php //
11
// module for analyzing ID3v2 tags //
12
// dependencies: module.tag.id3v1.php //
14
/////////////////////////////////////////////////////////////////
16
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
18
class getid3_id3v2 extends getid3_handler
20
public $StartingOffset = 0;
22
public function Analyze() {
23
$info = &$this->getid3->info;
25
// Overall tag structure:
26
// +-----------------------------+
27
// | Header (10 bytes) |
28
// +-----------------------------+
29
// | Extended Header |
30
// | (variable length, OPTIONAL) |
31
// +-----------------------------+
32
// | Frames (variable length) |
33
// +-----------------------------+
35
// | (variable length, OPTIONAL) |
36
// +-----------------------------+
37
// | Footer (10 bytes, OPTIONAL) |
38
// +-----------------------------+
41
// ID3v2/file identifier "ID3"
42
// ID3v2 version $04 00
43
// ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
44
// ID3v2 size 4 * %0xxxxxxx
48
$info['id3v2']['header'] = true;
49
$thisfile_id3v2 = &$info['id3v2'];
50
$thisfile_id3v2['flags'] = array();
51
$thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
54
fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET);
55
$header = fread($this->getid3->fp, 10);
56
if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
58
$thisfile_id3v2['majorversion'] = ord($header{3});
59
$thisfile_id3v2['minorversion'] = ord($header{4});
62
$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
66
unset($info['id3v2']);
71
if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
73
$info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
78
$id3_flags = ord($header{5});
79
switch ($id3v2_majorversion) {
82
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
83
$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
88
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
89
$thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
90
$thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
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
$thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
102
$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
104
$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
105
$thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
109
// create 'encoding' key - used by getid3::HandleAllTags()
110
// in ID3v2 every field can have it's own encoding type
111
// so force everything to UTF-8 so it can be handled consistantly
112
$thisfile_id3v2['encoding'] = 'UTF-8';
117
// All ID3v2 frames consists of one frame header followed by one or more
118
// fields containing the actual information. The header is always 10
119
// bytes and laid out as follows:
121
// Frame ID $xx xx xx xx (four characters)
122
// Size 4 * %0xxxxxxx
125
$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
126
if (!empty($thisfile_id3v2['exthead']['length'])) {
127
$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
129
if (!empty($thisfile_id3v2_flags['isfooter'])) {
130
$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
132
if ($sizeofframes > 0) {
134
$framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable
136
// if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
137
if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
138
$framedata = $this->DeUnsynchronise($framedata);
140
// [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
141
// of on tag level, making it easier to skip frames, increasing the streamability
142
// of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
143
// there exists an unsynchronised frame, while the new unsynchronisation flag in
144
// the frame header [S:4.1.2] indicates unsynchronisation.
147
//$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)
148
$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
152
if (!empty($thisfile_id3v2_flags['exthead'])) {
153
$extended_header_offset = 0;
155
if ($id3v2_majorversion == 3) {
158
//Extended header size $xx xx xx xx // 32-bit integer
159
//Extended Flags $xx xx
160
// %x0000000 %00000000 // v2.3
161
// x - CRC data present
162
//Size of padding $xx xx xx xx
164
$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
165
$extended_header_offset += 4;
167
$thisfile_id3v2['exthead']['flag_bytes'] = 2;
168
$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
169
$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
171
$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
173
$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
174
$extended_header_offset += 4;
176
if ($thisfile_id3v2['exthead']['flags']['crc']) {
177
$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
178
$extended_header_offset += 4;
180
$extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
182
} elseif ($id3v2_majorversion == 4) {
185
//Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
186
//Number of flag bytes $01
189
// b - Tag is an update
190
// Flag data length $00
191
// c - CRC data present
192
// Flag data length $05
193
// Total frame CRC 5 * %0xxxxxxx
194
// d - Tag restrictions
195
// Flag data length $01
197
$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
198
$extended_header_offset += 4;
200
$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
201
$extended_header_offset += 1;
203
$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
204
$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
206
$thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
207
$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
208
$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
210
if ($thisfile_id3v2['exthead']['flags']['update']) {
211
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
212
$extended_header_offset += 1;
215
if ($thisfile_id3v2['exthead']['flags']['crc']) {
216
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
217
$extended_header_offset += 1;
218
$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
219
$extended_header_offset += $ext_header_chunk_length;
222
if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
223
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
224
$extended_header_offset += 1;
227
$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
228
$extended_header_offset += 1;
229
$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
230
$thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
231
$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
232
$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
233
$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
235
$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
236
$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
237
$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
238
$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
239
$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
242
if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
243
$info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
247
$framedataoffset += $extended_header_offset;
248
$framedata = substr($framedata, $extended_header_offset);
249
} // end extended header
252
while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
253
if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
254
// insufficient room left in ID3v2 header for actual data - must be padding
255
$thisfile_id3v2['padding']['start'] = $framedataoffset;
256
$thisfile_id3v2['padding']['length'] = strlen($framedata);
257
$thisfile_id3v2['padding']['valid'] = true;
258
for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
259
if ($framedata{$i} != "\x00") {
260
$thisfile_id3v2['padding']['valid'] = false;
261
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
262
$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
266
break; // skip rest of ID3v2 header
268
if ($id3v2_majorversion == 2) {
269
// Frame ID $xx xx xx (three characters)
270
// Size $xx xx xx (24-bit integer)
273
$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
274
$framedata = substr($framedata, 6); // and leave the rest in $framedata
275
$frame_name = substr($frame_header, 0, 3);
276
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
277
$frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
279
} elseif ($id3v2_majorversion > 2) {
281
// Frame ID $xx xx xx xx (four characters)
282
// Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
285
$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
286
$framedata = substr($framedata, 10); // and leave the rest in $framedata
288
$frame_name = substr($frame_header, 0, 4);
289
if ($id3v2_majorversion == 3) {
290
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
292
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
295
if ($frame_size < (strlen($framedata) + 4)) {
296
$nextFrameID = substr($framedata, $frame_size, 4);
297
if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
299
} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
300
// MP3ext known broken frames - "ok" for the purposes of this test
301
} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
302
$info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
303
$id3v2_majorversion = 3;
304
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
309
$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
312
if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
313
// padding encountered
315
$thisfile_id3v2['padding']['start'] = $framedataoffset;
316
$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
317
$thisfile_id3v2['padding']['valid'] = true;
319
$len = strlen($framedata);
320
for ($i = 0; $i < $len; $i++) {
321
if ($framedata{$i} != "\x00") {
322
$thisfile_id3v2['padding']['valid'] = false;
323
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
324
$info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
328
break; // skip rest of ID3v2 header
331
if ($frame_name == 'COM ') {
332
$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
333
$frame_name = 'COMM';
335
if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
338
$parsedFrame['frame_name'] = $frame_name;
339
$parsedFrame['frame_flags_raw'] = $frame_flags;
340
$parsedFrame['data'] = substr($framedata, 0, $frame_size);
341
$parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size);
342
$parsedFrame['dataoffset'] = $framedataoffset;
344
$this->ParseID3v2Frame($parsedFrame);
345
$thisfile_id3v2[$frame_name][] = $parsedFrame;
347
$framedata = substr($framedata, $frame_size);
349
} else { // invalid frame length or FrameID
351
if ($frame_size <= strlen($framedata)) {
353
if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
355
// next frame is valid, just skip the current frame
356
$framedata = substr($framedata, $frame_size);
357
$info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
361
// next frame is invalid too, abort processing
364
$info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
368
} elseif ($frame_size == strlen($framedata)) {
370
// this is the last frame, just skip
371
$info['warning'][] = 'This was the last ID3v2 frame.';
375
// next frame is invalid too, abort processing
378
$info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
381
if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
383
switch ($frame_name) {
384
case "\x00\x00".'MP':
391
$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
395
$info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
399
} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
401
$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
405
$info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
410
$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
419
// The footer is a copy of the header, but with a different identifier.
420
// ID3v2 identifier "3DI"
421
// ID3v2 version $04 00
422
// ID3v2 flags %abcd0000
423
// ID3v2 size 4 * %0xxxxxxx
425
if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
426
$footer = fread($this->getid3->fp, 10);
427
if (substr($footer, 0, 3) == '3DI') {
428
$thisfile_id3v2['footer'] = true;
429
$thisfile_id3v2['majorversion_footer'] = ord($footer{3});
430
$thisfile_id3v2['minorversion_footer'] = ord($footer{4});
432
if ($thisfile_id3v2['majorversion_footer'] <= 4) {
433
$id3_flags = ord(substr($footer{5}));
434
$thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
435
$thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
436
$thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
437
$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
439
$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
443
if (isset($thisfile_id3v2['comments']['genre'])) {
444
foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
445
unset($thisfile_id3v2['comments']['genre'][$key]);
446
$thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
450
if (isset($thisfile_id3v2['comments']['track'])) {
451
foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
452
if (strstr($value, '/')) {
453
list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
458
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)) {
459
$thisfile_id3v2['comments']['year'] = array($matches[1]);
463
if (!empty($thisfile_id3v2['TXXX'])) {
464
// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
465
foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
466
switch ($txxx_array['description']) {
467
case 'replaygain_track_gain':
468
if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
469
$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
472
case 'replaygain_track_peak':
473
if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
474
$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
477
case 'replaygain_album_gain':
478
if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
479
$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
488
$info['avdataoffset'] = $thisfile_id3v2['headerlength'];
489
if (isset($thisfile_id3v2['footer'])) {
490
$info['avdataoffset'] += 10;
497
public function ParseID3v2GenreString($genrestring) {
498
// Parse genres into arrays of genreName and genreID
499
// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
500
// ID3v2.4.x: '21' $00 'Eurodisco' $00
501
$clean_genres = array();
502
if (strpos($genrestring, "\x00") === false) {
503
$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
505
$genre_elements = explode("\x00", $genrestring);
506
foreach ($genre_elements as $element) {
507
$element = trim($element);
509
if (preg_match('#^[0-9]{1,3}#', $element)) {
510
$clean_genres[] = getid3_id3v1::LookupGenreName($element);
512
$clean_genres[] = str_replace('((', '(', $element);
516
return $clean_genres;
520
public function ParseID3v2Frame(&$parsedFrame) {
523
$info = &$this->getid3->info;
524
$id3v2_majorversion = $info['id3v2']['majorversion'];
526
$parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
527
if (empty($parsedFrame['framenamelong'])) {
528
unset($parsedFrame['framenamelong']);
530
$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
531
if (empty($parsedFrame['framenameshort'])) {
532
unset($parsedFrame['framenameshort']);
535
if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
536
if ($id3v2_majorversion == 3) {
537
// Frame Header Flags
538
// %abc00000 %ijk00000
539
$parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
540
$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
541
$parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
542
$parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
543
$parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
544
$parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
546
} elseif ($id3v2_majorversion == 4) {
547
// Frame Header Flags
548
// %0abc0000 %0h00kmnp
549
$parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
550
$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
551
$parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
552
$parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
553
$parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
554
$parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
555
$parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
556
$parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
558
// Frame-level de-unsynchronisation - ID3v2.4
559
if ($parsedFrame['flags']['Unsynchronisation']) {
560
$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
563
if ($parsedFrame['flags']['DataLengthIndicator']) {
564
$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
565
$parsedFrame['data'] = substr($parsedFrame['data'], 4);
569
// Frame-level de-compression
570
if ($parsedFrame['flags']['compression']) {
571
$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
572
if (!function_exists('gzuncompress')) {
573
$info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
575
if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
576
//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
577
$parsedFrame['data'] = $decompresseddata;
578
unset($decompresseddata);
580
$info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
586
if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
587
if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
588
$info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
592
if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
594
$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
595
switch ($parsedFrame['frame_name']) {
597
$warning .= ' (this is known to happen with files tagged by RioPort)';
603
$info['warning'][] = $warning;
605
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
606
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
607
// There may be more than one 'UFID' frame in a tag,
608
// but only one with the same 'Owner identifier'.
609
// <Header for 'Unique file identifier', ID: 'UFID'>
610
// Owner identifier <text string> $00
611
// Identifier <up to 64 bytes binary data>
612
$exploded = explode("\x00", $parsedFrame['data'], 2);
613
$parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
614
$parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : '');
616
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
617
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
618
// There may be more than one 'TXXX' frame in each tag,
619
// but only one with the same description.
620
// <Header for 'User defined text information frame', ID: 'TXXX'>
622
// Description <text string according to encoding> $00 (00)
623
// Value <text string according to encoding>
626
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
628
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
629
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
631
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
632
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
633
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
635
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
636
if (ord($frame_description) === 0) {
637
$frame_description = '';
639
$parsedFrame['encodingid'] = $frame_textencoding;
640
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
642
$parsedFrame['description'] = $frame_description;
643
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
644
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
645
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
647
//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
650
} elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
651
// There may only be one text information frame of its kind in an tag.
652
// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
653
// excluding 'TXXX' described in 4.2.6.>
655
// Information <text string(s) according to encoding>
658
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
659
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
660
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
663
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
665
$parsedFrame['encodingid'] = $frame_textencoding;
666
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
668
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
669
// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
670
// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
671
// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
672
// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
673
switch ($parsedFrame['encoding']) {
685
$Txxx_elements = array();
686
$Txxx_elements_start_offset = 0;
687
for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
688
if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
689
$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
690
$Txxx_elements_start_offset = $i + $wordsize;
693
$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
694
foreach ($Txxx_elements as $Txxx_element) {
695
$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
696
if (!empty($string)) {
697
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
700
unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
703
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
704
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
705
// There may be more than one 'WXXX' frame in each tag,
706
// but only one with the same description
707
// <Header for 'User defined URL link frame', ID: 'WXXX'>
709
// Description <text string according to encoding> $00 (00)
713
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
714
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
715
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
717
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
718
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
719
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
721
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
723
if (ord($frame_description) === 0) {
724
$frame_description = '';
726
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
728
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
729
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
730
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
732
if ($frame_terminatorpos) {
733
// there are null bytes after the data - this is not according to spec
734
// only use data up to first null byte
735
$frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
737
// no null bytes following data, just use all data
738
$frame_urldata = (string) $parsedFrame['data'];
741
$parsedFrame['encodingid'] = $frame_textencoding;
742
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
744
$parsedFrame['url'] = $frame_urldata;
745
$parsedFrame['description'] = $frame_description;
746
if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
747
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
749
unset($parsedFrame['data']);
752
} elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
753
// There may only be one URL link frame of its kind in a tag,
754
// except when stated otherwise in the frame description
755
// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
756
// described in 4.3.2.>
759
$parsedFrame['url'] = trim($parsedFrame['data']);
760
if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
761
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
763
unset($parsedFrame['data']);
766
} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
767
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
768
// http://id3.org/id3v2.3.0#sec4.4
769
// There may only be one 'IPL' frame in each tag
770
// <Header for 'User defined URL link frame', ID: 'IPL'>
772
// People list strings <textstrings>
775
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
776
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
777
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
779
$parsedFrame['encodingid'] = $frame_textencoding;
780
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
781
$parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
783
// http://www.getid3.org/phpBB3/viewtopic.php?t=1369
784
// "this tag typically contains null terminated strings, which are associated in pairs"
785
// "there are users that use the tag incorrectly"
786
$IPLS_parts = array();
787
if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
788
$IPLS_parts_unsorted = array();
789
if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
790
// 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
792
for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
793
$twobytes = substr($parsedFrame['data_raw'], $i, 2);
794
if ($twobytes === "\x00\x00") {
795
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
798
$thisILPS .= $twobytes;
801
if (strlen($thisILPS) > 2) { // 2-byte BOM
802
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
805
// ISO-8859-1 or UTF-8 or other single-byte-null character set
806
$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
808
if (count($IPLS_parts_unsorted) == 1) {
809
// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
810
foreach ($IPLS_parts_unsorted as $key => $value) {
811
$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
813
foreach ($IPLS_parts_sorted as $person) {
814
$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
817
} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
820
foreach ($IPLS_parts_unsorted as $key => $value) {
821
if (($key % 2) == 0) {
825
$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
831
foreach ($IPLS_parts_unsorted as $key => $value) {
832
$IPLS_parts[] = array($value);
837
$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
839
$parsedFrame['data'] = $IPLS_parts;
841
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
842
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
846
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
847
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
848
// There may only be one 'MCDI' frame in each tag
849
// <Header for 'Music CD identifier', ID: 'MCDI'>
850
// CD TOC <binary data>
852
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
853
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
857
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
858
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
859
// There may only be one 'ETCO' frame in each tag
860
// <Header for 'Event timing codes', ID: 'ETCO'>
861
// Time stamp format $xx
862
// Where time stamp format is:
863
// $01 (32-bit value) MPEG frames from beginning of file
864
// $02 (32-bit value) milliseconds from beginning of file
865
// Followed by a list of key events in the following format:
867
// Time stamp $xx (xx ...)
868
// The 'Time stamp' is set to zero if directly at the beginning of the sound
869
// or after the previous event. All events MUST be sorted in chronological order.
872
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
874
while ($frame_offset < strlen($parsedFrame['data'])) {
875
$parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1);
876
$parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
877
$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
880
unset($parsedFrame['data']);
883
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
884
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
885
// There may only be one 'MLLT' frame in each tag
886
// <Header for 'Location lookup table', ID: 'MLLT'>
887
// MPEG frames between reference $xx xx
888
// Bytes between reference $xx xx xx
889
// Milliseconds between reference $xx xx xx
890
// Bits for bytes deviation $xx
891
// Bits for milliseconds dev. $xx
892
// Then for every reference the following data is included;
893
// Deviation in bytes %xxx....
894
// Deviation in milliseconds %xxx....
897
$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
898
$parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
899
$parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
900
$parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
901
$parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
902
$parsedFrame['data'] = substr($parsedFrame['data'], 10);
903
while ($frame_offset < strlen($parsedFrame['data'])) {
904
$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
906
$reference_counter = 0;
907
while (strlen($deviationbitstream) > 0) {
908
$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
909
$parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
910
$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
911
$reference_counter++;
913
unset($parsedFrame['data']);
916
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
917
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
918
// There may only be one 'SYTC' frame in each tag
919
// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
920
// Time stamp format $xx
921
// Tempo data <binary data>
922
// Where time stamp format is:
923
// $01 (32-bit value) MPEG frames from beginning of file
924
// $02 (32-bit value) milliseconds from beginning of file
927
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
928
$timestamp_counter = 0;
929
while ($frame_offset < strlen($parsedFrame['data'])) {
930
$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
931
if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
932
$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
934
$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
936
$timestamp_counter++;
938
unset($parsedFrame['data']);
941
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
942
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
943
// There may be more than one 'Unsynchronised lyrics/text transcription' frame
944
// in each tag, but only one with the same language and content descriptor.
945
// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
947
// Language $xx xx xx
948
// Content descriptor <text string according to encoding> $00 (00)
949
// Lyrics/text <full text string according to encoding>
952
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
953
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
954
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
956
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
958
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
959
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
960
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
962
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
963
if (ord($frame_description) === 0) {
964
$frame_description = '';
966
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
968
$parsedFrame['encodingid'] = $frame_textencoding;
969
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
971
$parsedFrame['data'] = $parsedFrame['data'];
972
$parsedFrame['language'] = $frame_language;
973
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
974
$parsedFrame['description'] = $frame_description;
975
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
976
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
978
unset($parsedFrame['data']);
981
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text
982
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
983
// There may be more than one 'SYLT' frame in each tag,
984
// but only one with the same language and content descriptor.
985
// <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
987
// Language $xx xx xx
988
// Time stamp format $xx
989
// $01 (32-bit value) MPEG frames from beginning of file
990
// $02 (32-bit value) milliseconds from beginning of file
992
// Content descriptor <text string according to encoding> $00 (00)
993
// Terminated text to be synced (typically a syllable)
994
// Sync identifier (terminator to above string) $00 (00)
995
// Time stamp $xx (xx ...)
998
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
999
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1000
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1002
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1004
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1005
$parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1006
$parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
1007
$parsedFrame['encodingid'] = $frame_textencoding;
1008
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1010
$parsedFrame['language'] = $frame_language;
1011
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1013
$timestampindex = 0;
1014
$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
1015
while (strlen($frame_remainingdata)) {
1017
$frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
1018
if ($frame_terminatorpos === false) {
1019
$frame_remainingdata = '';
1021
if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1022
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1024
$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
1026
$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1027
if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
1028
// timestamp probably omitted for first data item
1030
$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
1031
$frame_remainingdata = substr($frame_remainingdata, 4);
1036
unset($parsedFrame['data']);
1039
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments
1040
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
1041
// There may be more than one comment frame in each tag,
1042
// but only one with the same language and content descriptor.
1043
// <Header for 'Comment', ID: 'COMM'>
1044
// Text encoding $xx
1045
// Language $xx xx xx
1046
// Short content descrip. <text string according to encoding> $00 (00)
1047
// The actual text <full text string according to encoding>
1049
if (strlen($parsedFrame['data']) < 5) {
1051
$info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
1056
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1057
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1058
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1060
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1062
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1063
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1064
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1066
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1067
if (ord($frame_description) === 0) {
1068
$frame_description = '';
1070
$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1072
$parsedFrame['encodingid'] = $frame_textencoding;
1073
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1075
$parsedFrame['language'] = $frame_language;
1076
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1077
$parsedFrame['description'] = $frame_description;
1078
$parsedFrame['data'] = $frame_text;
1079
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1080
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1085
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1086
// There may be more than one 'RVA2' frame in each tag,
1087
// but only one with the same identification string
1088
// <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1089
// Identification <text string> $00
1090
// The 'identification' string is used to identify the situation and/or
1091
// device where this adjustment should apply. The following is then
1092
// repeated for every channel:
1093
// Type of channel $xx
1094
// Volume adjustment $xx xx
1095
// Bits representing peak $xx
1096
// Peak volume $xx (xx ...)
1098
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1099
$frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1100
if (ord($frame_idstring) === 0) {
1101
$frame_idstring = '';
1103
$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1104
$parsedFrame['description'] = $frame_idstring;
1105
$RVA2channelcounter = 0;
1106
while (strlen($frame_remainingdata) >= 5) {
1108
$frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1109
$parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1110
$parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1111
$parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1113
$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1114
if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1115
$info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
1118
$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1119
$parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1120
$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1121
$RVA2channelcounter++;
1123
unset($parsedFrame['data']);
1126
} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1127
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1128
// There may only be one 'RVA' frame in each tag
1129
// <Header for 'Relative volume adjustment', ID: 'RVA'>
1130
// ID3v2.2 => Increment/decrement %000000ba
1131
// ID3v2.3 => Increment/decrement %00fedcba
1132
// Bits used for volume descr. $xx
1133
// Relative volume change, right $xx xx (xx ...) // a
1134
// Relative volume change, left $xx xx (xx ...) // b
1135
// Peak volume right $xx xx (xx ...)
1136
// Peak volume left $xx xx (xx ...)
1137
// ID3v2.3 only, optional (not present in ID3v2.2):
1138
// Relative volume change, right back $xx xx (xx ...) // c
1139
// Relative volume change, left back $xx xx (xx ...) // d
1140
// Peak volume right back $xx xx (xx ...)
1141
// Peak volume left back $xx xx (xx ...)
1142
// ID3v2.3 only, optional (not present in ID3v2.2):
1143
// Relative volume change, center $xx xx (xx ...) // e
1144
// Peak volume center $xx xx (xx ...)
1145
// ID3v2.3 only, optional (not present in ID3v2.2):
1146
// Relative volume change, bass $xx xx (xx ...) // f
1147
// Peak volume bass $xx xx (xx ...)
1150
$frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1151
$parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1152
$parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1153
$parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1154
$frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1155
$parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1156
if ($parsedFrame['incdec']['right'] === false) {
1157
$parsedFrame['volumechange']['right'] *= -1;
1159
$frame_offset += $frame_bytesvolume;
1160
$parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1161
if ($parsedFrame['incdec']['left'] === false) {
1162
$parsedFrame['volumechange']['left'] *= -1;
1164
$frame_offset += $frame_bytesvolume;
1165
$parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1166
$frame_offset += $frame_bytesvolume;
1167
$parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1168
$frame_offset += $frame_bytesvolume;
1169
if ($id3v2_majorversion == 3) {
1170
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1171
if (strlen($parsedFrame['data']) > 0) {
1172
$parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1173
$parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1174
$parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1175
if ($parsedFrame['incdec']['rightrear'] === false) {
1176
$parsedFrame['volumechange']['rightrear'] *= -1;
1178
$frame_offset += $frame_bytesvolume;
1179
$parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1180
if ($parsedFrame['incdec']['leftrear'] === false) {
1181
$parsedFrame['volumechange']['leftrear'] *= -1;
1183
$frame_offset += $frame_bytesvolume;
1184
$parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1185
$frame_offset += $frame_bytesvolume;
1186
$parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1187
$frame_offset += $frame_bytesvolume;
1189
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1190
if (strlen($parsedFrame['data']) > 0) {
1191
$parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1192
$parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1193
if ($parsedFrame['incdec']['center'] === false) {
1194
$parsedFrame['volumechange']['center'] *= -1;
1196
$frame_offset += $frame_bytesvolume;
1197
$parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1198
$frame_offset += $frame_bytesvolume;
1200
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1201
if (strlen($parsedFrame['data']) > 0) {
1202
$parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1203
$parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1204
if ($parsedFrame['incdec']['bass'] === false) {
1205
$parsedFrame['volumechange']['bass'] *= -1;
1207
$frame_offset += $frame_bytesvolume;
1208
$parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1209
$frame_offset += $frame_bytesvolume;
1212
unset($parsedFrame['data']);
1215
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1216
// There may be more than one 'EQU2' frame in each tag,
1217
// but only one with the same identification string
1218
// <Header of 'Equalisation (2)', ID: 'EQU2'>
1219
// Interpolation method $xx
1222
// Identification <text string> $00
1223
// The following is then repeated for every adjustment point
1225
// Volume adjustment $xx xx
1228
$frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1229
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1230
$frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1231
if (ord($frame_idstring) === 0) {
1232
$frame_idstring = '';
1234
$parsedFrame['description'] = $frame_idstring;
1235
$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1236
while (strlen($frame_remainingdata)) {
1237
$frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1238
$parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1239
$frame_remainingdata = substr($frame_remainingdata, 4);
1241
$parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1242
unset($parsedFrame['data']);
1245
} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only)
1246
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1247
// There may only be one 'EQUA' frame in each tag
1248
// <Header for 'Relative volume adjustment', ID: 'EQU'>
1249
// Adjustment bits $xx
1250
// This is followed by 2 bytes + ('adjustment bits' rounded up to the
1251
// nearest byte) for every equalisation band in the following format,
1252
// giving a frequency range of 0 - 32767Hz:
1253
// Increment/decrement %x (MSB of the Frequency)
1254
// Frequency (lower 15 bits)
1255
// Adjustment $xx (xx ...)
1258
$parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1259
$frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1261
$frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1262
while (strlen($frame_remainingdata) > 0) {
1263
$frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1264
$frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1265
$frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1266
$parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1267
$parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1268
if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1269
$parsedFrame[$frame_frequency]['adjustment'] *= -1;
1271
$frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1273
unset($parsedFrame['data']);
1276
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb
1277
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1278
// There may only be one 'RVRB' frame in each tag.
1279
// <Header for 'Reverb', ID: 'RVRB'>
1280
// Reverb left (ms) $xx xx
1281
// Reverb right (ms) $xx xx
1282
// Reverb bounces, left $xx
1283
// Reverb bounces, right $xx
1284
// Reverb feedback, left to left $xx
1285
// Reverb feedback, left to right $xx
1286
// Reverb feedback, right to right $xx
1287
// Reverb feedback, right to left $xx
1288
// Premix left to right $xx
1289
// Premix right to left $xx
1292
$parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1294
$parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1296
$parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1297
$parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1298
$parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1299
$parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1300
$parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1301
$parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1302
$parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1303
$parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1304
unset($parsedFrame['data']);
1307
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture
1308
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1309
// There may be several pictures attached to one file,
1310
// each in their individual 'APIC' frame, but only one
1311
// with the same content descriptor
1312
// <Header for 'Attached picture', ID: 'APIC'>
1313
// Text encoding $xx
1314
// ID3v2.3+ => MIME type <text string> $00
1315
// ID3v2.2 => Image format $xx xx xx
1317
// Description <text string according to encoding> $00 (00)
1318
// Picture data <binary data>
1321
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1322
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1323
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1326
if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1327
$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1328
if (strtolower($frame_imagetype) == 'ima') {
1329
// complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1330
// MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
1331
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1332
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1333
if (ord($frame_mimetype) === 0) {
1334
$frame_mimetype = '';
1336
$frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1337
if ($frame_imagetype == 'JPEG') {
1338
$frame_imagetype = 'JPG';
1340
$frame_offset = $frame_terminatorpos + strlen("\x00");
1345
if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1346
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1347
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1348
if (ord($frame_mimetype) === 0) {
1349
$frame_mimetype = '';
1351
$frame_offset = $frame_terminatorpos + strlen("\x00");
1354
$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1356
if ($frame_offset >= $parsedFrame['datalength']) {
1357
$info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1359
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1360
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1361
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1363
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1364
if (ord($frame_description) === 0) {
1365
$frame_description = '';
1367
$parsedFrame['encodingid'] = $frame_textencoding;
1368
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1370
if ($id3v2_majorversion == 2) {
1371
$parsedFrame['imagetype'] = $frame_imagetype;
1373
$parsedFrame['mime'] = $frame_mimetype;
1375
$parsedFrame['picturetypeid'] = $frame_picturetype;
1376
$parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1377
$parsedFrame['description'] = $frame_description;
1378
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1379
$parsedFrame['datalength'] = strlen($parsedFrame['data']);
1381
$parsedFrame['image_mime'] = '';
1382
$imageinfo = array();
1383
$imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
1384
if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1385
$parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
1386
if ($imagechunkcheck[0]) {
1387
$parsedFrame['image_width'] = $imagechunkcheck[0];
1389
if ($imagechunkcheck[1]) {
1390
$parsedFrame['image_height'] = $imagechunkcheck[1];
1395
if ($this->getid3->option_save_attachments === false) {
1397
unset($parsedFrame['data']);
1400
if ($this->getid3->option_save_attachments === true) {
1403
} elseif (is_int($this->getid3->option_save_attachments)) {
1404
if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
1406
$info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1407
unset($parsedFrame['data']);
1411
} elseif (is_string($this->getid3->option_save_attachments)) {
1412
$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
1413
if (!is_dir($dir) || !is_writable($dir)) {
1414
// cannot write, skip
1415
$info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)';
1416
unset($parsedFrame['data']);
1420
// if we get this far, must be OK
1421
if (is_string($this->getid3->option_save_attachments)) {
1422
$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1423
if (!file_exists($destination_filename) || is_writable($destination_filename)) {
1424
file_put_contents($destination_filename, $parsedFrame['data']);
1426
$info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1428
$parsedFrame['data_filename'] = $destination_filename;
1429
unset($parsedFrame['data']);
1431
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1432
if (!isset($info['id3v2']['comments']['picture'])) {
1433
$info['id3v2']['comments']['picture'] = array();
1435
$info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
1441
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
1442
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1443
// There may be more than one 'GEOB' frame in each tag,
1444
// but only one with the same content descriptor
1445
// <Header for 'General encapsulated object', ID: 'GEOB'>
1446
// Text encoding $xx
1447
// MIME type <text string> $00
1448
// Filename <text string according to encoding> $00 (00)
1449
// Content description <text string according to encoding> $00 (00)
1450
// Encapsulated object <binary data>
1453
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1454
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1455
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1457
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1458
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1459
if (ord($frame_mimetype) === 0) {
1460
$frame_mimetype = '';
1462
$frame_offset = $frame_terminatorpos + strlen("\x00");
1464
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1465
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1466
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1468
$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1469
if (ord($frame_filename) === 0) {
1470
$frame_filename = '';
1472
$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1474
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1475
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1476
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1478
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1479
if (ord($frame_description) === 0) {
1480
$frame_description = '';
1482
$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1484
$parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1485
$parsedFrame['encodingid'] = $frame_textencoding;
1486
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1488
$parsedFrame['mime'] = $frame_mimetype;
1489
$parsedFrame['filename'] = $frame_filename;
1490
$parsedFrame['description'] = $frame_description;
1491
unset($parsedFrame['data']);
1494
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter
1495
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1496
// There may only be one 'PCNT' frame in each tag.
1497
// When the counter reaches all one's, one byte is inserted in
1498
// front of the counter thus making the counter eight bits bigger
1499
// <Header for 'Play counter', ID: 'PCNT'>
1500
// Counter $xx xx xx xx (xx ...)
1502
$parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']);
1505
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter
1506
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1507
// There may be more than one 'POPM' frame in each tag,
1508
// but only one with the same email address
1509
// <Header for 'Popularimeter', ID: 'POPM'>
1510
// Email to user <text string> $00
1512
// Counter $xx xx xx xx (xx ...)
1515
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1516
$frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1517
if (ord($frame_emailaddress) === 0) {
1518
$frame_emailaddress = '';
1520
$frame_offset = $frame_terminatorpos + strlen("\x00");
1521
$frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1522
$parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1523
$parsedFrame['email'] = $frame_emailaddress;
1524
$parsedFrame['rating'] = $frame_rating;
1525
unset($parsedFrame['data']);
1528
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size
1529
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1530
// There may only be one 'RBUF' frame in each tag
1531
// <Header for 'Recommended buffer size', ID: 'RBUF'>
1532
// Buffer size $xx xx xx
1533
// Embedded info flag %0000000x
1534
// Offset to next tag $xx xx xx xx
1537
$parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1540
$frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1541
$parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1542
$parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1543
unset($parsedFrame['data']);
1546
} elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1547
// There may be more than one 'CRM' frame in a tag,
1548
// but only one with the same 'owner identifier'
1549
// <Header for 'Encrypted meta frame', ID: 'CRM'>
1550
// Owner identifier <textstring> $00 (00)
1551
// Content/explanation <textstring> $00 (00)
1552
// Encrypted datablock <binary data>
1555
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1556
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1557
$frame_offset = $frame_terminatorpos + strlen("\x00");
1559
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1560
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1561
if (ord($frame_description) === 0) {
1562
$frame_description = '';
1564
$frame_offset = $frame_terminatorpos + strlen("\x00");
1566
$parsedFrame['ownerid'] = $frame_ownerid;
1567
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1568
$parsedFrame['description'] = $frame_description;
1569
unset($parsedFrame['data']);
1572
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption
1573
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1574
// There may be more than one 'AENC' frames in a tag,
1575
// but only one with the same 'Owner identifier'
1576
// <Header for 'Audio encryption', ID: 'AENC'>
1577
// Owner identifier <text string> $00
1578
// Preview start $xx xx
1579
// Preview length $xx xx
1580
// Encryption info <binary data>
1583
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1584
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1585
if (ord($frame_ownerid) === 0) {
1586
$frame_ownerid == '';
1588
$frame_offset = $frame_terminatorpos + strlen("\x00");
1589
$parsedFrame['ownerid'] = $frame_ownerid;
1590
$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1592
$parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1594
$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1595
unset($parsedFrame['data']);
1598
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information
1599
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1600
// There may be more than one 'LINK' frame in a tag,
1601
// but only one with the same contents
1602
// <Header for 'Linked information', ID: 'LINK'>
1603
// ID3v2.3+ => Frame identifier $xx xx xx xx
1604
// ID3v2.2 => Frame identifier $xx xx xx
1605
// URL <text string> $00
1606
// ID and additional data <text string(s)>
1609
if ($id3v2_majorversion == 2) {
1610
$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1613
$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1617
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1618
$frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1619
if (ord($frame_url) === 0) {
1622
$frame_offset = $frame_terminatorpos + strlen("\x00");
1623
$parsedFrame['url'] = $frame_url;
1625
$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1626
if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1627
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
1629
unset($parsedFrame['data']);
1632
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1633
// There may only be one 'POSS' frame in each tag
1634
// <Head for 'Position synchronisation', ID: 'POSS'>
1635
// Time stamp format $xx
1636
// Position $xx (xx ...)
1639
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1640
$parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1641
unset($parsedFrame['data']);
1644
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1645
// There may be more than one 'Terms of use' frame in a tag,
1646
// but only one with the same 'Language'
1647
// <Header for 'Terms of use frame', ID: 'USER'>
1648
// Text encoding $xx
1649
// Language $xx xx xx
1650
// The actual text <text string according to encoding>
1653
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1654
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1655
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1657
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1659
$parsedFrame['language'] = $frame_language;
1660
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1661
$parsedFrame['encodingid'] = $frame_textencoding;
1662
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1664
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1665
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1666
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1668
unset($parsedFrame['data']);
1671
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1672
// There may only be one 'OWNE' frame in a tag
1673
// <Header for 'Ownership frame', ID: 'OWNE'>
1674
// Text encoding $xx
1675
// Price paid <text string> $00
1676
// Date of purch. <text string>
1677
// Seller <text string according to encoding>
1680
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1681
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1682
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1684
$parsedFrame['encodingid'] = $frame_textencoding;
1685
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1687
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1688
$frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1689
$frame_offset = $frame_terminatorpos + strlen("\x00");
1691
$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1692
$parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1693
$parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1695
$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1696
if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1697
$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1701
$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1702
unset($parsedFrame['data']);
1705
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1706
// There may be more than one 'commercial frame' in a tag,
1707
// but no two may be identical
1708
// <Header for 'Commercial frame', ID: 'COMR'>
1709
// Text encoding $xx
1710
// Price string <text string> $00
1711
// Valid until <text string>
1712
// Contact URL <text string> $00
1714
// Name of seller <text string according to encoding> $00 (00)
1715
// Description <text string according to encoding> $00 (00)
1716
// Picture MIME type <string> $00
1717
// Seller logo <binary data>
1720
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1721
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1722
$info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1725
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1726
$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1727
$frame_offset = $frame_terminatorpos + strlen("\x00");
1728
$frame_rawpricearray = explode('/', $frame_pricestring);
1729
foreach ($frame_rawpricearray as $key => $val) {
1730
$frame_currencyid = substr($val, 0, 3);
1731
$parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1732
$parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1735
$frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1738
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1739
$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1740
$frame_offset = $frame_terminatorpos + strlen("\x00");
1742
$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1744
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1745
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1746
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1748
$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1749
if (ord($frame_sellername) === 0) {
1750
$frame_sellername = '';
1752
$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1754
$frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1755
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1756
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1758
$frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1759
if (ord($frame_description) === 0) {
1760
$frame_description = '';
1762
$frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1764
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1765
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1766
$frame_offset = $frame_terminatorpos + strlen("\x00");
1768
$frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1770
$parsedFrame['encodingid'] = $frame_textencoding;
1771
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1773
$parsedFrame['pricevaliduntil'] = $frame_datestring;
1774
$parsedFrame['contacturl'] = $frame_contacturl;
1775
$parsedFrame['receivedasid'] = $frame_receivedasid;
1776
$parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1777
$parsedFrame['sellername'] = $frame_sellername;
1778
$parsedFrame['description'] = $frame_description;
1779
$parsedFrame['mime'] = $frame_mimetype;
1780
$parsedFrame['logo'] = $frame_sellerlogo;
1781
unset($parsedFrame['data']);
1784
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1785
// There may be several 'ENCR' frames in a tag,
1786
// but only one containing the same symbol
1787
// and only one containing the same owner identifier
1788
// <Header for 'Encryption method registration', ID: 'ENCR'>
1789
// Owner identifier <text string> $00
1790
// Method symbol $xx
1791
// Encryption data <binary data>
1794
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1795
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1796
if (ord($frame_ownerid) === 0) {
1797
$frame_ownerid = '';
1799
$frame_offset = $frame_terminatorpos + strlen("\x00");
1801
$parsedFrame['ownerid'] = $frame_ownerid;
1802
$parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1803
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1806
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1808
// There may be several 'GRID' frames in a tag,
1809
// but only one containing the same symbol
1810
// and only one containing the same owner identifier
1811
// <Header for 'Group ID registration', ID: 'GRID'>
1812
// Owner identifier <text string> $00
1814
// Group dependent data <binary data>
1817
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1818
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1819
if (ord($frame_ownerid) === 0) {
1820
$frame_ownerid = '';
1822
$frame_offset = $frame_terminatorpos + strlen("\x00");
1824
$parsedFrame['ownerid'] = $frame_ownerid;
1825
$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1826
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1829
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1830
// The tag may contain more than one 'PRIV' frame
1831
// but only with different contents
1832
// <Header for 'Private frame', ID: 'PRIV'>
1833
// Owner identifier <text string> $00
1834
// The private data <binary data>
1837
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1838
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1839
if (ord($frame_ownerid) === 0) {
1840
$frame_ownerid = '';
1842
$frame_offset = $frame_terminatorpos + strlen("\x00");
1844
$parsedFrame['ownerid'] = $frame_ownerid;
1845
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1848
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1849
// There may be more than one 'signature frame' in a tag,
1850
// but no two may be identical
1851
// <Header for 'Signature frame', ID: 'SIGN'>
1853
// Signature <binary data>
1856
$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1857
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1860
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1861
// There may only be one 'seek frame' in a tag
1862
// <Header for 'Seek frame', ID: 'SEEK'>
1863
// Minimum offset to next tag $xx xx xx xx
1866
$parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1869
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1870
// There may only be one 'audio seek point index' frame in a tag
1871
// <Header for 'Seek Point Index', ID: 'ASPI'>
1872
// Indexed data start (S) $xx xx xx xx
1873
// Indexed data length (L) $xx xx xx xx
1874
// Number of index points (N) $xx xx
1875
// Bits per index point (b) $xx
1876
// Then for every index point the following data is included:
1877
// Fraction at index (Fi) $xx (xx)
1880
$parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1882
$parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1884
$parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1886
$parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1887
$frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1888
for ($i = 0; $i < $frame_indexpoints; $i++) {
1889
$parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1890
$frame_offset += $frame_bytesperpoint;
1892
unset($parsedFrame['data']);
1894
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1895
// http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1896
// There may only be one 'RGAD' frame in a tag
1897
// <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1898
// Peak Amplitude $xx $xx $xx $xx
1899
// Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1900
// Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1902
// b - originator code
1904
// d - replay gain adjustment
1907
$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1909
$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1911
$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1913
$parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1914
$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1915
$parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1916
$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1917
$parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1918
$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1919
$parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1920
$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1921
$parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1922
$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1923
$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1924
$parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1925
$parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1926
$parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1928
$info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
1929
$info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1930
$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1931
$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1932
$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1934
unset($parsedFrame['data']);
1942
public function DeUnsynchronise($data) {
1943
return str_replace("\xFF\x00", "\xFF", $data);
1946
public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
1947
static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
1948
0x00 => 'No more than 128 frames and 1 MB total tag size',
1949
0x01 => 'No more than 64 frames and 128 KB total tag size',
1950
0x02 => 'No more than 32 frames and 40 KB total tag size',
1951
0x03 => 'No more than 32 frames and 4 KB total tag size',
1953
return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
1956
public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
1957
static $LookupExtendedHeaderRestrictionsTextEncodings = array(
1958
0x00 => 'No restrictions',
1959
0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
1961
return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
1964
public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
1965
static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
1966
0x00 => 'No restrictions',
1967
0x01 => 'No string is longer than 1024 characters',
1968
0x02 => 'No string is longer than 128 characters',
1969
0x03 => 'No string is longer than 30 characters',
1971
return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
1974
public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
1975
static $LookupExtendedHeaderRestrictionsImageEncoding = array(
1976
0x00 => 'No restrictions',
1977
0x01 => 'Images are encoded only with PNG or JPEG',
1979
return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
1982
public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
1983
static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
1984
0x00 => 'No restrictions',
1985
0x01 => 'All images are 256x256 pixels or smaller',
1986
0x02 => 'All images are 64x64 pixels or smaller',
1987
0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
1989
return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
1992
public function LookupCurrencyUnits($currencyid) {
1996
/** This is not a comment!
2010
BAM Convertible Marka
2027
CDF Congolese Francs
2173
XDR Special Drawing Rights
2181
ZWD Zimbabwe Dollars
2185
return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2189
public function LookupCurrencyCountry($currencyid) {
2193
/** This is not a comment!
2195
AED United Arab Emirates
2199
ANG Netherlands Antilles
2206
BAM Bosnia and Herzegovina
2214
BND Brunei Darussalam
2236
DOP Dominican Republic
2243
EUR Euro Member Countries
2246
FKP Falkland Islands (Malvinas)
2303
MVR Maldives (Maldive Islands)
2311
NLG Netherlands (Holland)
2318
PGK Papua New Guinea
2341
STD São Tome and Principe
2351
TTD Trinidad and Tobago
2357
USD United States of America
2365
XAF Communauté Financière Africaine
2369
XDR International Monetary Fund
2371
XPF Comptoirs Français du Pacifique
2381
return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2386
public static function LanguageLookup($languagecode, $casesensitive=false) {
2388
if (!$casesensitive) {
2389
$languagecode = strtolower($languagecode);
2392
// http://www.id3.org/id3v2.4.0-structure.txt
2393
// [4. ID3v2 frame overview]
2394
// The three byte language field, present in several frames, is used to
2395
// describe the language of the frame's content, according to ISO-639-2
2396
// [ISO-639-2]. The language should be represented in lower case. If the
2397
// language is not known the string "XXX" should be used.
2400
// ISO 639-2 - http://www.id3.org/iso639-2.html
2404
/** This is not a comment!
2413
afa Afro-Asiatic (Other)
2420
alg Algonquian Languages
2422
ang English, Old (ca. 450-1100)
2423
apa Apache Languages
2429
art Artificial (Other)
2432
ath Athapascan Languages
2439
bai Bamileke Languages
2467
cai Central American Indian (Other)
2470
cau Caucasian (Other)
2489
cpe Creoles and Pidgins, English-based (Other)
2490
cpf Creoles and Pidgins, French-based (Other)
2491
cpp Creoles and Pidgins, Portuguese-based (Other)
2493
crp Creoles and Pidgins (Other)
2494
cus Cushitic (Other)
2504
dra Dravidian (Other)
2506
dum Dutch, Middle (ca. 1050-1350)
2511
egy Egyptian (Ancient)
2513
ell Greek, Modern (1453-)
2516
enm English, Middle (ca. 1100-1500)
2530
fiu Finno-Ugrian (Other)
2534
frm French, Middle (ca. 1400-1600)
2535
fro French, Old (842- ca. 1400)
2543
gem Germanic (Other)
2549
gmh German, Middle High (ca. 1050-1500)
2550
goh German, Old High (ca. 750-1050)
2554
grc Greek, Ancient (to 1453)
2555
gre Greek, Modern (1453-)
2576
ina Interlingua (International Auxiliary language Association)
2579
ine Indo-European (Other)
2639
luo Luo (Kenya and Tanzania)
2650
map Austronesian (Other)
2656
mga Irish, Middle (900 - 1200)
2659
mis Miscellaneous (Other)
2660
mkh Mon-Kmer (Other)
2664
mno Manobo Languages
2671
mul Multiple Languages
2678
nai North American Indian (Other)
2686
nic Niger-Kordofanian (Other)
2689
nno Norwegian (Nynorsk)
2693
nub Nubian Languages
2699
oci Langue d'Oc (post 1500)
2705
ota Turkish, Ottoman (1500 - 1928)
2706
oto Otomian Languages
2707
paa Papuan-Australian (Other)
2714
peo Persian, Old (ca 600 - 400 B.C.)
2722
pro Provencal, Old (to 1500)
2737
sai South American Indian (Other)
2738
sal Salishan Languages
2739
sam Samaritan Aramaic
2745
sga Irish, Old (to 900)
2749
sio Siouan Languages
2750
sit Sino-Tibetan (Other)
2767
ssa Nilo-Saharan (Other)
2794
ton Tonga (Tonga Islands)
2817
wak Wakashan Languages
2822
wen Sorbian Languages
2838
return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
2842
public static function ETCOEventLookup($index) {
2843
if (($index >= 0x17) && ($index <= 0xDF)) {
2844
return 'reserved for future use';
2846
if (($index >= 0xE0) && ($index <= 0xEF)) {
2847
return 'not predefined synch 0-F';
2849
if (($index >= 0xF0) && ($index <= 0xFC)) {
2850
return 'reserved for future use';
2853
static $EventLookup = array(
2854
0x00 => 'padding (has no meaning)',
2855
0x01 => 'end of initial silence',
2856
0x02 => 'intro start',
2857
0x03 => 'main part start',
2858
0x04 => 'outro start',
2859
0x05 => 'outro end',
2860
0x06 => 'verse start',
2861
0x07 => 'refrain start',
2862
0x08 => 'interlude start',
2863
0x09 => 'theme start',
2864
0x0A => 'variation start',
2865
0x0B => 'key change',
2866
0x0C => 'time change',
2867
0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
2868
0x0E => 'sustained noise',
2869
0x0F => 'sustained noise end',
2870
0x10 => 'intro end',
2871
0x11 => 'main part end',
2872
0x12 => 'verse end',
2873
0x13 => 'refrain end',
2874
0x14 => 'theme end',
2875
0x15 => 'profanity',
2876
0x16 => 'profanity end',
2877
0xFD => 'audio end (start of silence)',
2878
0xFE => 'audio file ends',
2879
0xFF => 'one more byte of events follows'
2882
return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
2885
public static function SYTLContentTypeLookup($index) {
2886
static $SYTLContentTypeLookup = array(
2889
0x02 => 'text transcription',
2890
0x03 => 'movement/part name', // (e.g. 'Adagio')
2891
0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
2892
0x05 => 'chord', // (e.g. 'Bb F Fsus')
2893
0x06 => 'trivia/\'pop up\' information',
2894
0x07 => 'URLs to webpages',
2895
0x08 => 'URLs to images'
2898
return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
2901
public static function APICPictureTypeLookup($index, $returnarray=false) {
2902
static $APICPictureTypeLookup = array(
2904
0x01 => '32x32 pixels \'file icon\' (PNG only)',
2905
0x02 => 'Other file icon',
2906
0x03 => 'Cover (front)',
2907
0x04 => 'Cover (back)',
2908
0x05 => 'Leaflet page',
2909
0x06 => 'Media (e.g. label side of CD)',
2910
0x07 => 'Lead artist/lead performer/soloist',
2911
0x08 => 'Artist/performer',
2912
0x09 => 'Conductor',
2913
0x0A => 'Band/Orchestra',
2915
0x0C => 'Lyricist/text writer',
2916
0x0D => 'Recording Location',
2917
0x0E => 'During recording',
2918
0x0F => 'During performance',
2919
0x10 => 'Movie/video screen capture',
2920
0x11 => 'A bright coloured fish',
2921
0x12 => 'Illustration',
2922
0x13 => 'Band/artist logotype',
2923
0x14 => 'Publisher/Studio logotype'
2926
return $APICPictureTypeLookup;
2928
return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
2931
public static function COMRReceivedAsLookup($index) {
2932
static $COMRReceivedAsLookup = array(
2934
0x01 => 'Standard CD album with other songs',
2935
0x02 => 'Compressed audio on CD',
2936
0x03 => 'File over the Internet',
2937
0x04 => 'Stream over the Internet',
2938
0x05 => 'As note sheets',
2939
0x06 => 'As note sheets in a book with other sheets',
2940
0x07 => 'Music on other media',
2941
0x08 => 'Non-musical merchandise'
2944
return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
2947
public static function RVA2ChannelTypeLookup($index) {
2948
static $RVA2ChannelTypeLookup = array(
2950
0x01 => 'Master volume',
2951
0x02 => 'Front right',
2952
0x03 => 'Front left',
2953
0x04 => 'Back right',
2954
0x05 => 'Back left',
2955
0x06 => 'Front centre',
2956
0x07 => 'Back centre',
2960
return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
2963
public static function FrameNameLongLookup($framename) {
2967
/** This is not a comment!
2969
AENC Audio encryption
2970
APIC Attached picture
2971
ASPI Audio seek point index
2972
BUF Recommended buffer size
2976
COMR Commercial frame
2977
CRA Audio encryption
2978
CRM Encrypted meta frame
2979
ENCR Encryption method registration
2981
EQU2 Equalisation (2)
2983
ETC Event timing codes
2984
ETCO Event timing codes
2985
GEO General encapsulated object
2986
GEOB General encapsulated object
2987
GRID Group identification registration
2988
IPL Involved people list
2989
IPLS Involved people list
2990
LINK Linked information
2991
LNK Linked information
2992
MCDI Music CD identifier
2993
MCI Music CD Identifier
2994
MLL MPEG location lookup table
2995
MLLT MPEG location lookup table
2996
OWNE Ownership frame
2998
PIC Attached picture
3001
POSS Position synchronisation frame
3003
RBUF Recommended buffer size
3005
RVA Relative volume adjustment
3006
RVA2 Relative volume adjustment (2)
3007
RVAD Relative volume adjustment
3010
SIGN Signature frame
3011
SLT Synchronised lyric/text
3012
STC Synced tempo codes
3013
SYLT Synchronised lyric/text
3014
SYTC Synchronised tempo codes
3015
TAL Album/Movie/Show title
3016
TALB Album/Movie/Show title
3017
TBP BPM (Beats Per Minute)
3018
TBPM BPM (beats per minute)
3020
TCMP Part of a compilation
3024
TCOP Copyright message
3025
TCP Part of a compilation
3026
TCR Copyright message
3031
TDOR Original release time
3038
TEXT Lyricist/Text writer
3043
TIPL Involved people list
3044
TIT1 Content group description
3045
TIT2 Title/songname/content description
3046
TIT3 Subtitle/Description refinement
3053
TMCL Musician credits list
3057
TOA Original artist(s)/performer(s)
3058
TOAL Original album/movie/show title
3059
TOF Original filename
3060
TOFN Original filename
3061
TOL Original Lyricist(s)/text writer(s)
3062
TOLY Original lyricist(s)/text writer(s)
3063
TOPE Original artist(s)/performer(s)
3064
TOR Original release year
3065
TORY Original release year
3066
TOT Original album/Movie/Show title
3067
TOWN File owner/licensee
3068
TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
3069
TP2 Band/Orchestra/Accompaniment
3070
TP3 Conductor/Performer refinement
3071
TP4 Interpreted, remixed, or otherwise modified by
3074
TPE1 Lead performer(s)/Soloist(s)
3075
TPE2 Band/orchestra/accompaniment
3076
TPE3 Conductor/performer refinement
3077
TPE4 Interpreted, remixed, or otherwise modified by
3079
TPRO Produced notice
3081
TRC ISRC (International Standard Recording Code)
3082
TRCK Track number/Position in set
3084
TRDA Recording dates
3085
TRK Track number/Position in set
3086
TRSN Internet radio station name
3087
TRSO Internet radio station owner
3088
TS2 Album-Artist sort order
3089
TSA Album sort order
3090
TSC Composer sort order
3093
TSO2 Album-Artist sort order
3094
TSOA Album sort order
3095
TSOC Composer sort order
3096
TSOP Performer sort order
3097
TSOT Title sort order
3098
TSP Performer sort order
3099
TSRC ISRC (international standard recording code)
3100
TSS Software/hardware and settings used for encoding
3101
TSSE Software/Hardware and settings used for encoding
3103
TST Title sort order
3104
TT1 Content group description
3105
TT2 Title/Songname/Content description
3106
TT3 Subtitle/Description refinement
3107
TXT Lyricist/text writer
3108
TXX User defined text information frame
3109
TXXX User defined text information frame
3112
UFI Unique file identifier
3113
UFID Unique file identifier
3114
ULT Unsychronised lyric/text transcription
3116
USLT Unsynchronised lyric/text transcription
3117
WAF Official audio file webpage
3118
WAR Official artist/performer webpage
3119
WAS Official audio source webpage
3120
WCM Commercial information
3121
WCOM Commercial information
3122
WCOP Copyright/Legal information
3123
WCP Copyright/Legal information
3124
WOAF Official audio file webpage
3125
WOAR Official artist/performer webpage
3126
WOAS Official audio source webpage
3127
WORS Official Internet radio station homepage
3129
WPB Publishers official webpage
3130
WPUB Publishers official webpage
3131
WXX User defined URL link frame
3132
WXXX User defined URL link frame
3133
TFEA Featured Artist
3134
TSTU Recording Studio
3135
rgad Replay Gain Adjustment
3139
return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3142
// from Helium2 [www.helium2.com]
3143
// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3147
public static function FrameNameShortLookup($framename) {
3151
/** This is not a comment!
3153
AENC audio_encryption
3154
APIC attached_picture
3155
ASPI audio_seek_point_index
3156
BUF recommended_buffer_size
3160
COMR commercial_frame
3161
CRA audio_encryption
3162
CRM encrypted_meta_frame
3163
ENCR encryption_method_registration
3167
ETC event_timing_codes
3168
ETCO event_timing_codes
3169
GEO general_encapsulated_object
3170
GEOB general_encapsulated_object
3171
GRID group_identification_registration
3172
IPL involved_people_list
3173
IPLS involved_people_list
3174
LINK linked_information
3175
LNK linked_information
3176
MCDI music_cd_identifier
3177
MCI music_cd_identifier
3178
MLL mpeg_location_lookup_table
3179
MLLT mpeg_location_lookup_table
3180
OWNE ownership_frame
3182
PIC attached_picture
3185
POSS position_synchronisation_frame
3187
RBUF recommended_buffer_size
3189
RVA relative_volume_adjustment
3190
RVA2 relative_volume_adjustment
3191
RVAD relative_volume_adjustment
3194
SIGN signature_frame
3195
SLT synchronised_lyric
3196
STC synced_tempo_codes
3197
SYLT synchronised_lyric
3198
SYTC synchronised_tempo_codes
3204
TCMP part_of_a_compilation
3208
TCOP copyright_message
3209
TCP part_of_a_compilation
3210
TCR copyright_message
3215
TDOR original_release_time
3227
TIPL involved_people_list
3228
TIT1 content_group_description
3237
TMCL musician_credits_list
3243
TOF original_filename
3244
TOFN original_filename
3245
TOL original_lyricist
3246
TOLY original_lyricist
3247
TOPE original_artist
3263
TPRO produced_notice
3268
TRDA recording_dates
3270
TRSN internet_radio_station_name
3271
TRSO internet_radio_station_owner
3272
TS2 album_artist_sort_order
3273
TSA album_sort_order
3274
TSC composer_sort_order
3277
TSO2 album_artist_sort_order
3278
TSOA album_sort_order
3279
TSOC composer_sort_order
3280
TSOP performer_sort_order
3281
TSOT title_sort_order
3282
TSP performer_sort_order
3284
TSS encoder_settings
3285
TSSE encoder_settings
3287
TST title_sort_order
3288
TT1 content_group_description
3296
UFI unique_file_identifier
3297
UFID unique_file_identifier
3298
ULT unsychronised_lyric
3300
USLT unsynchronised_lyric
3304
WCM commercial_information
3305
WCOM commercial_information
3317
TFEA featured_artist
3318
TSTU recording_studio
3319
rgad replay_gain_adjustment
3323
return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3326
public static function TextEncodingTerminatorLookup($encoding) {
3327
// http://www.id3.org/id3v2.4.0-structure.txt
3328
// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3329
static $TextEncodingTerminatorLookup = array(
3330
0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3331
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.
3332
2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3333
3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3336
return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : '');
3339
public static function TextEncodingNameLookup($encoding) {
3340
// http://www.id3.org/id3v2.4.0-structure.txt
3341
// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3342
static $TextEncodingNameLookup = array(
3343
0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3344
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.
3345
2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3346
3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3349
return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3352
public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3353
switch ($id3v2majorversion) {
3355
return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3360
return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3366
public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3367
for ($i = 0; $i < strlen($numberstring); $i++) {
3368
if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) {
3369
if (($numberstring{$i} == '.') && $allowdecimal) {
3371
} elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3381
public static function IsValidDateStampString($datestamp) {
3382
if (strlen($datestamp) != 8) {
3385
if (!self::IsANumber($datestamp, false)) {
3388
$year = substr($datestamp, 0, 4);
3389
$month = substr($datestamp, 4, 2);
3390
$day = substr($datestamp, 6, 2);
3391
if (($year == 0) || ($month == 0) || ($day == 0)) {
3400
if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3403
if (($day > 29) && ($month == 2)) {
3409
public static function ID3v2HeaderLength($majorversion) {
3410
return (($majorversion == 2) ? 6 : 10);