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