|
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.audio.flac.php // |
|
12
|
|
|
// module for analyzing FLAC and OggFLAC audio files // |
|
13
|
|
|
// dependencies: module.audio.ogg.php // |
|
14
|
|
|
// /// |
|
15
|
|
|
///////////////////////////////////////////////////////////////// |
|
16
|
|
|
|
|
17
|
|
|
|
|
18
|
|
|
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* @tutorial http://flac.sourceforge.net/format.html |
|
22
|
|
|
*/ |
|
23
|
|
|
class getid3_flac extends getid3_handler |
|
24
|
|
|
{ |
|
25
|
|
|
const syncword = 'fLaC'; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* @return bool |
|
29
|
|
|
*/ |
|
30
|
|
|
public function Analyze() { |
|
31
|
|
|
$info = &$this->getid3->info; |
|
32
|
|
|
|
|
33
|
|
|
$this->fseek($info['avdataoffset']); |
|
34
|
|
|
$StreamMarker = $this->fread(4); |
|
35
|
|
|
if ($StreamMarker != self::syncword) { |
|
36
|
|
|
return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"'); |
|
|
|
|
|
|
37
|
|
|
} |
|
38
|
|
|
$info['fileformat'] = 'flac'; |
|
39
|
|
|
$info['audio']['dataformat'] = 'flac'; |
|
40
|
|
|
$info['audio']['bitrate_mode'] = 'vbr'; |
|
41
|
|
|
$info['audio']['lossless'] = true; |
|
42
|
|
|
|
|
43
|
|
|
// parse flac container |
|
44
|
|
|
return $this->parseMETAdata(); |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* @return bool |
|
49
|
|
|
*/ |
|
50
|
|
|
public function parseMETAdata() { |
|
51
|
|
|
$info = &$this->getid3->info; |
|
52
|
|
|
do { |
|
53
|
|
|
$BlockOffset = $this->ftell(); |
|
54
|
|
|
$BlockHeader = $this->fread(4); |
|
55
|
|
|
$LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); // LBFBT = LastBlockFlag + BlockType |
|
56
|
|
|
$LastBlockFlag = (bool) ($LBFBT & 0x80); |
|
57
|
|
|
$BlockType = ($LBFBT & 0x7F); |
|
58
|
|
|
$BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3)); |
|
59
|
|
|
$BlockTypeText = self::metaBlockTypeLookup($BlockType); |
|
60
|
|
|
|
|
61
|
|
|
if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) { |
|
62
|
|
|
$this->warning('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file'); |
|
63
|
|
|
break; |
|
64
|
|
|
} |
|
65
|
|
|
if ($BlockLength < 1) { |
|
66
|
|
|
if ($BlockTypeText != 'reserved') { |
|
67
|
|
|
// probably supposed to be zero-length |
|
68
|
|
|
$this->warning('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockTypeText.') at offset '.$BlockOffset.' is zero bytes'); |
|
69
|
|
|
continue; |
|
70
|
|
|
} |
|
71
|
|
|
$this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid'); |
|
72
|
|
|
break; |
|
73
|
|
|
} |
|
74
|
|
|
|
|
75
|
|
|
$info['flac'][$BlockTypeText]['raw'] = array(); |
|
76
|
|
|
$BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw']; |
|
77
|
|
|
|
|
78
|
|
|
$BlockTypeText_raw['offset'] = $BlockOffset; |
|
79
|
|
|
$BlockTypeText_raw['last_meta_block'] = $LastBlockFlag; |
|
80
|
|
|
$BlockTypeText_raw['block_type'] = $BlockType; |
|
81
|
|
|
$BlockTypeText_raw['block_type_text'] = $BlockTypeText; |
|
82
|
|
|
$BlockTypeText_raw['block_length'] = $BlockLength; |
|
83
|
|
|
if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically |
|
84
|
|
|
$BlockTypeText_raw['block_data'] = $this->fread($BlockLength); |
|
|
|
|
|
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
switch ($BlockTypeText) { |
|
88
|
|
|
case 'STREAMINFO': // 0x00 |
|
89
|
|
|
if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) { |
|
90
|
|
|
return false; |
|
91
|
|
|
} |
|
92
|
|
|
break; |
|
93
|
|
|
|
|
94
|
|
|
case 'PADDING': // 0x01 |
|
95
|
|
|
unset($info['flac']['PADDING']); // ignore |
|
96
|
|
|
break; |
|
97
|
|
|
|
|
98
|
|
|
case 'APPLICATION': // 0x02 |
|
99
|
|
|
if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) { |
|
100
|
|
|
return false; |
|
101
|
|
|
} |
|
102
|
|
|
break; |
|
103
|
|
|
|
|
104
|
|
|
case 'SEEKTABLE': // 0x03 |
|
105
|
|
|
if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) { |
|
106
|
|
|
return false; |
|
107
|
|
|
} |
|
108
|
|
|
break; |
|
109
|
|
|
|
|
110
|
|
|
case 'VORBIS_COMMENT': // 0x04 |
|
111
|
|
|
if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) { |
|
112
|
|
|
return false; |
|
113
|
|
|
} |
|
114
|
|
|
break; |
|
115
|
|
|
|
|
116
|
|
|
case 'CUESHEET': // 0x05 |
|
117
|
|
|
if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) { |
|
118
|
|
|
return false; |
|
119
|
|
|
} |
|
120
|
|
|
break; |
|
121
|
|
|
|
|
122
|
|
|
case 'PICTURE': // 0x06 |
|
123
|
|
|
if (!$this->parsePICTURE()) { |
|
124
|
|
|
return false; |
|
125
|
|
|
} |
|
126
|
|
|
break; |
|
127
|
|
|
|
|
128
|
|
|
default: |
|
129
|
|
|
$this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset); |
|
130
|
|
|
} |
|
131
|
|
|
|
|
132
|
|
|
unset($info['flac'][$BlockTypeText]['raw']); |
|
133
|
|
|
$info['avdataoffset'] = $this->ftell(); |
|
134
|
|
|
} |
|
135
|
|
|
while ($LastBlockFlag === false); |
|
136
|
|
|
|
|
137
|
|
|
// handle tags |
|
138
|
|
|
if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) { |
|
139
|
|
|
$info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments']; |
|
140
|
|
|
} |
|
141
|
|
|
if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) { |
|
142
|
|
|
$info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
// copy attachments to 'comments' array if nesesary |
|
146
|
|
|
if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) { |
|
147
|
|
|
foreach ($info['flac']['PICTURE'] as $entry) { |
|
148
|
|
View Code Duplication |
if (!empty($entry['data'])) { |
|
|
|
|
|
|
149
|
|
|
if (!isset($info['flac']['comments']['picture'])) { |
|
150
|
|
|
$info['flac']['comments']['picture'] = array(); |
|
151
|
|
|
} |
|
152
|
|
|
$comments_picture_data = array(); |
|
153
|
|
|
foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) { |
|
154
|
|
|
if (isset($entry[$picture_key])) { |
|
155
|
|
|
$comments_picture_data[$picture_key] = $entry[$picture_key]; |
|
156
|
|
|
} |
|
157
|
|
|
} |
|
158
|
|
|
$info['flac']['comments']['picture'][] = $comments_picture_data; |
|
159
|
|
|
unset($comments_picture_data); |
|
160
|
|
|
} |
|
161
|
|
|
} |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
if (isset($info['flac']['STREAMINFO'])) { |
|
165
|
|
|
if (!$this->isDependencyFor('matroska')) { |
|
166
|
|
|
$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset']; |
|
167
|
|
|
} |
|
168
|
|
|
$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8); |
|
169
|
|
|
if ($info['flac']['uncompressed_audio_bytes'] == 0) { |
|
170
|
|
|
return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero'); |
|
171
|
|
|
} |
|
172
|
|
|
if (!empty($info['flac']['compressed_audio_bytes'])) { |
|
173
|
|
|
$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes']; |
|
174
|
|
|
} |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
// set md5_data_source - built into flac 0.5+ |
|
178
|
|
|
if (isset($info['flac']['STREAMINFO']['audio_signature'])) { |
|
179
|
|
|
|
|
180
|
|
|
if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { |
|
181
|
|
|
$this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); |
|
182
|
|
|
} |
|
183
|
|
View Code Duplication |
else { |
|
|
|
|
|
|
184
|
|
|
$info['md5_data_source'] = ''; |
|
185
|
|
|
$md5 = $info['flac']['STREAMINFO']['audio_signature']; |
|
186
|
|
|
for ($i = 0; $i < strlen($md5); $i++) { |
|
187
|
|
|
$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT); |
|
188
|
|
|
} |
|
189
|
|
|
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) { |
|
190
|
|
|
unset($info['md5_data_source']); |
|
191
|
|
|
} |
|
192
|
|
|
} |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) { |
|
196
|
|
|
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; |
|
197
|
|
|
if ($info['audio']['bits_per_sample'] == 8) { |
|
198
|
|
|
// special case |
|
199
|
|
|
// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value |
|
200
|
|
|
// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed |
|
201
|
|
|
$this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'); |
|
202
|
|
|
} |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
return true; |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
|
|
209
|
|
|
/** |
|
210
|
|
|
* @param string $BlockData |
|
211
|
|
|
* |
|
212
|
|
|
* @return array |
|
213
|
|
|
*/ |
|
214
|
|
|
public static function parseSTREAMINFOdata($BlockData) { |
|
215
|
|
|
$streaminfo = array(); |
|
216
|
|
|
$streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2)); |
|
217
|
|
|
$streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2)); |
|
218
|
|
|
$streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3)); |
|
219
|
|
|
$streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3)); |
|
220
|
|
|
|
|
221
|
|
|
$SRCSBSS = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8)); |
|
222
|
|
|
$streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20)); |
|
223
|
|
|
$streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1; |
|
224
|
|
|
$streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1; |
|
225
|
|
|
$streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36)); |
|
226
|
|
|
|
|
227
|
|
|
$streaminfo['audio_signature'] = substr($BlockData, 18, 16); |
|
228
|
|
|
|
|
229
|
|
|
return $streaminfo; |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
/** |
|
233
|
|
|
* @param string $BlockData |
|
234
|
|
|
* |
|
235
|
|
|
* @return bool |
|
236
|
|
|
*/ |
|
237
|
|
|
private function parseSTREAMINFO($BlockData) { |
|
238
|
|
|
$info = &$this->getid3->info; |
|
239
|
|
|
|
|
240
|
|
|
$info['flac']['STREAMINFO'] = self::parseSTREAMINFOdata($BlockData); |
|
241
|
|
|
|
|
242
|
|
|
if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { |
|
243
|
|
|
|
|
244
|
|
|
$info['audio']['bitrate_mode'] = 'vbr'; |
|
245
|
|
|
$info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; |
|
246
|
|
|
$info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; |
|
247
|
|
|
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; |
|
248
|
|
|
$info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; |
|
249
|
|
|
if ($info['playtime_seconds'] > 0) { |
|
250
|
|
|
if (!$this->isDependencyFor('matroska')) { |
|
251
|
|
|
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; |
|
252
|
|
|
} |
|
253
|
|
|
else { |
|
254
|
|
|
$this->warning('Cannot determine audio bitrate because total stream size is unknown'); |
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
} else { |
|
259
|
|
|
return $this->error('Corrupt METAdata block: STREAMINFO'); |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
return true; |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
/** |
|
266
|
|
|
* @param string $BlockData |
|
267
|
|
|
* |
|
268
|
|
|
* @return bool |
|
269
|
|
|
*/ |
|
270
|
|
|
private function parseAPPLICATION($BlockData) { |
|
271
|
|
|
$info = &$this->getid3->info; |
|
272
|
|
|
|
|
273
|
|
|
$ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4)); |
|
274
|
|
|
$info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID); |
|
|
|
|
|
|
275
|
|
|
$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4); |
|
276
|
|
|
|
|
277
|
|
|
return true; |
|
278
|
|
|
} |
|
279
|
|
|
|
|
280
|
|
|
/** |
|
281
|
|
|
* @param string $BlockData |
|
282
|
|
|
* |
|
283
|
|
|
* @return bool |
|
284
|
|
|
*/ |
|
285
|
|
|
private function parseSEEKTABLE($BlockData) { |
|
286
|
|
|
$info = &$this->getid3->info; |
|
287
|
|
|
|
|
288
|
|
|
$offset = 0; |
|
289
|
|
|
$BlockLength = strlen($BlockData); |
|
290
|
|
|
$placeholderpattern = str_repeat("\xFF", 8); |
|
291
|
|
|
while ($offset < $BlockLength) { |
|
292
|
|
|
$SampleNumberString = substr($BlockData, $offset, 8); |
|
293
|
|
|
$offset += 8; |
|
294
|
|
|
if ($SampleNumberString == $placeholderpattern) { |
|
295
|
|
|
|
|
296
|
|
|
// placeholder point |
|
297
|
|
|
getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1); |
|
298
|
|
|
$offset += 10; |
|
299
|
|
|
|
|
300
|
|
|
} else { |
|
301
|
|
|
|
|
302
|
|
|
$SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString); |
|
303
|
|
|
$info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); |
|
304
|
|
|
$offset += 8; |
|
305
|
|
|
$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2)); |
|
306
|
|
|
$offset += 2; |
|
307
|
|
|
|
|
308
|
|
|
} |
|
309
|
|
|
} |
|
310
|
|
|
|
|
311
|
|
|
return true; |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
/** |
|
315
|
|
|
* @param string $BlockData |
|
316
|
|
|
* |
|
317
|
|
|
* @return bool |
|
318
|
|
|
*/ |
|
319
|
|
|
private function parseVORBIS_COMMENT($BlockData) { |
|
|
|
|
|
|
320
|
|
|
$info = &$this->getid3->info; |
|
321
|
|
|
|
|
322
|
|
|
$getid3_ogg = new getid3_ogg($this->getid3); |
|
323
|
|
|
if ($this->isDependencyFor('matroska')) { |
|
324
|
|
|
$getid3_ogg->setStringMode($this->data_string); |
|
325
|
|
|
} |
|
326
|
|
|
$getid3_ogg->ParseVorbisComments(); |
|
327
|
|
|
if (isset($info['ogg'])) { |
|
328
|
|
|
unset($info['ogg']['comments_raw']); |
|
329
|
|
|
$info['flac']['VORBIS_COMMENT'] = $info['ogg']; |
|
330
|
|
|
unset($info['ogg']); |
|
331
|
|
|
} |
|
332
|
|
|
|
|
333
|
|
|
unset($getid3_ogg); |
|
334
|
|
|
|
|
335
|
|
|
return true; |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
/** |
|
339
|
|
|
* @param string $BlockData |
|
340
|
|
|
* |
|
341
|
|
|
* @return bool |
|
342
|
|
|
*/ |
|
343
|
|
|
private function parseCUESHEET($BlockData) { |
|
344
|
|
|
$info = &$this->getid3->info; |
|
345
|
|
|
$offset = 0; |
|
346
|
|
|
$info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0"); |
|
347
|
|
|
$offset += 128; |
|
348
|
|
|
$info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); |
|
349
|
|
|
$offset += 8; |
|
350
|
|
|
$info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80); |
|
351
|
|
|
$offset += 1; |
|
352
|
|
|
|
|
353
|
|
|
$offset += 258; // reserved |
|
354
|
|
|
|
|
355
|
|
|
$info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); |
|
356
|
|
|
$offset += 1; |
|
357
|
|
|
|
|
358
|
|
|
for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) { |
|
359
|
|
|
$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); |
|
360
|
|
|
$offset += 8; |
|
361
|
|
|
$TrackNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); |
|
362
|
|
|
$offset += 1; |
|
363
|
|
|
|
|
364
|
|
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; |
|
365
|
|
|
|
|
366
|
|
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12); |
|
367
|
|
|
$offset += 12; |
|
368
|
|
|
|
|
369
|
|
|
$TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); |
|
370
|
|
|
$offset += 1; |
|
371
|
|
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80); |
|
372
|
|
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40); |
|
373
|
|
|
|
|
374
|
|
|
$offset += 13; // reserved |
|
375
|
|
|
|
|
376
|
|
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); |
|
377
|
|
|
$offset += 1; |
|
378
|
|
|
|
|
379
|
|
View Code Duplication |
for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { |
|
|
|
|
|
|
380
|
|
|
$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8)); |
|
381
|
|
|
$offset += 8; |
|
382
|
|
|
$IndexNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)); |
|
383
|
|
|
$offset += 1; |
|
384
|
|
|
|
|
385
|
|
|
$offset += 3; // reserved |
|
386
|
|
|
|
|
387
|
|
|
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; |
|
388
|
|
|
} |
|
389
|
|
|
} |
|
390
|
|
|
|
|
391
|
|
|
return true; |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
/** |
|
395
|
|
|
* Parse METADATA_BLOCK_PICTURE flac structure and extract attachment |
|
396
|
|
|
* External usage: audio.ogg |
|
397
|
|
|
* |
|
398
|
|
|
* @return bool |
|
399
|
|
|
*/ |
|
400
|
|
|
public function parsePICTURE() { |
|
401
|
|
|
$info = &$this->getid3->info; |
|
402
|
|
|
|
|
403
|
|
|
$picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4)); |
|
|
|
|
|
|
404
|
|
|
$picture['picturetype'] = self::pictureTypeLookup($picture['typeid']); |
|
|
|
|
|
|
405
|
|
|
$picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4))); |
|
|
|
|
|
|
406
|
|
|
$descr_length = getid3_lib::BigEndian2Int($this->fread(4)); |
|
|
|
|
|
|
407
|
|
|
if ($descr_length) { |
|
408
|
|
|
$picture['description'] = $this->fread($descr_length); |
|
409
|
|
|
} |
|
410
|
|
|
$picture['image_width'] = getid3_lib::BigEndian2Int($this->fread(4)); |
|
|
|
|
|
|
411
|
|
|
$picture['image_height'] = getid3_lib::BigEndian2Int($this->fread(4)); |
|
|
|
|
|
|
412
|
|
|
$picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4)); |
|
|
|
|
|
|
413
|
|
|
$picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4)); |
|
|
|
|
|
|
414
|
|
|
$picture['datalength'] = getid3_lib::BigEndian2Int($this->fread(4)); |
|
|
|
|
|
|
415
|
|
|
|
|
416
|
|
|
if ($picture['image_mime'] == '-->') { |
|
417
|
|
|
$picture['data'] = $this->fread($picture['datalength']); |
|
|
|
|
|
|
418
|
|
|
} else { |
|
419
|
|
|
$picture['data'] = $this->saveAttachment( |
|
420
|
|
|
str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(), |
|
421
|
|
|
$this->ftell(), |
|
|
|
|
|
|
422
|
|
|
$picture['datalength'], |
|
|
|
|
|
|
423
|
|
|
$picture['image_mime']); |
|
424
|
|
|
} |
|
425
|
|
|
|
|
426
|
|
|
$info['flac']['PICTURE'][] = $picture; |
|
427
|
|
|
|
|
428
|
|
|
return true; |
|
429
|
|
|
} |
|
430
|
|
|
|
|
431
|
|
|
/** |
|
432
|
|
|
* @param int $blocktype |
|
433
|
|
|
* |
|
434
|
|
|
* @return string |
|
435
|
|
|
*/ |
|
436
|
|
|
public static function metaBlockTypeLookup($blocktype) { |
|
437
|
|
|
static $lookup = array( |
|
438
|
|
|
0 => 'STREAMINFO', |
|
439
|
|
|
1 => 'PADDING', |
|
440
|
|
|
2 => 'APPLICATION', |
|
441
|
|
|
3 => 'SEEKTABLE', |
|
442
|
|
|
4 => 'VORBIS_COMMENT', |
|
443
|
|
|
5 => 'CUESHEET', |
|
444
|
|
|
6 => 'PICTURE', |
|
445
|
|
|
); |
|
446
|
|
|
return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved'); |
|
447
|
|
|
} |
|
448
|
|
|
|
|
449
|
|
|
/** |
|
450
|
|
|
* @param int $applicationid |
|
451
|
|
|
* |
|
452
|
|
|
* @return string |
|
453
|
|
|
*/ |
|
454
|
|
|
public static function applicationIDLookup($applicationid) { |
|
455
|
|
|
// http://flac.sourceforge.net/id.html |
|
456
|
|
|
static $lookup = array( |
|
457
|
|
|
0x41544348 => 'FlacFile', // "ATCH" |
|
458
|
|
|
0x42534F4C => 'beSolo', // "BSOL" |
|
459
|
|
|
0x42554753 => 'Bugs Player', // "BUGS" |
|
460
|
|
|
0x43756573 => 'GoldWave cue points (specification)', // "Cues" |
|
461
|
|
|
0x46696361 => 'CUE Splitter', // "Fica" |
|
462
|
|
|
0x46746F6C => 'flac-tools', // "Ftol" |
|
463
|
|
|
0x4D4F5442 => 'MOTB MetaCzar', // "MOTB" |
|
464
|
|
|
0x4D505345 => 'MP3 Stream Editor', // "MPSE" |
|
465
|
|
|
0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML" |
|
466
|
|
|
0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF" |
|
467
|
|
|
0x5346464C => 'Sound Font FLAC', // "SFFL" |
|
468
|
|
|
0x534F4E59 => 'Sony Creative Software', // "SONY" |
|
469
|
|
|
0x5351455A => 'flacsqueeze', // "SQEZ" |
|
470
|
|
|
0x54745776 => 'TwistedWave', // "TtWv" |
|
471
|
|
|
0x55495453 => 'UITS Embedding tools', // "UITS" |
|
472
|
|
|
0x61696666 => 'FLAC AIFF chunk storage', // "aiff" |
|
473
|
|
|
0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag" |
|
474
|
|
|
0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem" |
|
475
|
|
|
0x71667374 => 'QFLAC Studio', // "qfst" |
|
476
|
|
|
0x72696666 => 'FLAC RIFF chunk storage', // "riff" |
|
477
|
|
|
0x74756E65 => 'TagTuner', // "tune" |
|
478
|
|
|
0x78626174 => 'XBAT', // "xbat" |
|
479
|
|
|
0x786D6364 => 'xmcd', // "xmcd" |
|
480
|
|
|
); |
|
481
|
|
|
return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved'); |
|
482
|
|
|
} |
|
483
|
|
|
|
|
484
|
|
|
/** |
|
485
|
|
|
* @param int $type_id |
|
486
|
|
|
* |
|
487
|
|
|
* @return string |
|
488
|
|
|
*/ |
|
489
|
|
View Code Duplication |
public static function pictureTypeLookup($type_id) { |
|
|
|
|
|
|
490
|
|
|
static $lookup = array ( |
|
491
|
|
|
0 => 'Other', |
|
492
|
|
|
1 => '32x32 pixels \'file icon\' (PNG only)', |
|
493
|
|
|
2 => 'Other file icon', |
|
494
|
|
|
3 => 'Cover (front)', |
|
495
|
|
|
4 => 'Cover (back)', |
|
496
|
|
|
5 => 'Leaflet page', |
|
497
|
|
|
6 => 'Media (e.g. label side of CD)', |
|
498
|
|
|
7 => 'Lead artist/lead performer/soloist', |
|
499
|
|
|
8 => 'Artist/performer', |
|
500
|
|
|
9 => 'Conductor', |
|
501
|
|
|
10 => 'Band/Orchestra', |
|
502
|
|
|
11 => 'Composer', |
|
503
|
|
|
12 => 'Lyricist/text writer', |
|
504
|
|
|
13 => 'Recording Location', |
|
505
|
|
|
14 => 'During recording', |
|
506
|
|
|
15 => 'During performance', |
|
507
|
|
|
16 => 'Movie/video screen capture', |
|
508
|
|
|
17 => 'A bright coloured fish', |
|
509
|
|
|
18 => 'Illustration', |
|
510
|
|
|
19 => 'Band/artist logotype', |
|
511
|
|
|
20 => 'Publisher/Studio logotype', |
|
512
|
|
|
); |
|
513
|
|
|
return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved'); |
|
514
|
|
|
} |
|
515
|
|
|
|
|
516
|
|
|
} |
|
517
|
|
|
|
This check looks for type mismatches where the missing type is
false. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTimeobject or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalsebefore passing on the value to another function or method that may not be able to handle afalse.