1 <?php |
1 <?php |
|
2 |
2 ///////////////////////////////////////////////////////////////// |
3 ///////////////////////////////////////////////////////////////// |
3 /// getID3() by James Heinrich <info@getid3.org> // |
4 /// getID3() by James Heinrich <info@getid3.org> // |
4 // available at http://getid3.sourceforge.net // |
5 // available at https://github.com/JamesHeinrich/getID3 // |
5 // or http://www.getid3.org // |
6 // or https://www.getid3.org // |
6 // also https://github.com/JamesHeinrich/getID3 // |
7 // or http://getid3.sourceforge.net // |
7 ///////////////////////////////////////////////////////////////// |
8 // see readme.txt for more details // |
8 // See readme.txt for more details // |
|
9 ///////////////////////////////////////////////////////////////// |
9 ///////////////////////////////////////////////////////////////// |
10 // // |
10 // // |
11 // module.audio.flac.php // |
11 // module.audio.flac.php // |
12 // module for analyzing FLAC and OggFLAC audio files // |
12 // module for analyzing FLAC and OggFLAC audio files // |
13 // dependencies: module.audio.ogg.php // |
13 // dependencies: module.audio.ogg.php // |
14 // /// |
14 // /// |
15 ///////////////////////////////////////////////////////////////// |
15 ///////////////////////////////////////////////////////////////// |
16 |
16 |
17 |
17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers |
|
18 exit; |
|
19 } |
18 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); |
20 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); |
19 |
21 |
20 /** |
22 /** |
21 * @tutorial http://flac.sourceforge.net/format.html |
23 * @tutorial http://flac.sourceforge.net/format.html |
22 */ |
24 */ |
23 class getid3_flac extends getid3_handler |
25 class getid3_flac extends getid3_handler |
24 { |
26 { |
25 const syncword = 'fLaC'; |
27 const syncword = 'fLaC'; |
26 |
28 |
|
29 /** |
|
30 * @return bool |
|
31 */ |
27 public function Analyze() { |
32 public function Analyze() { |
28 $info = &$this->getid3->info; |
33 $info = &$this->getid3->info; |
29 |
34 |
30 $this->fseek($info['avdataoffset']); |
35 $this->fseek($info['avdataoffset']); |
31 $StreamMarker = $this->fread(4); |
36 $StreamMarker = $this->fread(4); |
39 |
44 |
40 // parse flac container |
45 // parse flac container |
41 return $this->parseMETAdata(); |
46 return $this->parseMETAdata(); |
42 } |
47 } |
43 |
48 |
|
49 /** |
|
50 * @return bool |
|
51 */ |
44 public function parseMETAdata() { |
52 public function parseMETAdata() { |
45 $info = &$this->getid3->info; |
53 $info = &$this->getid3->info; |
46 do { |
54 do { |
47 $BlockOffset = $this->ftell(); |
55 $BlockOffset = $this->ftell(); |
48 $BlockHeader = $this->fread(4); |
56 $BlockHeader = $this->fread(4); |
49 $LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); |
57 $LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); // LBFBT = LastBlockFlag + BlockType |
50 $LastBlockFlag = (bool) ($LBFBT & 0x80); |
58 $LastBlockFlag = (bool) ($LBFBT & 0x80); |
51 $BlockType = ($LBFBT & 0x7F); |
59 $BlockType = ($LBFBT & 0x7F); |
52 $BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3)); |
60 $BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3)); |
53 $BlockTypeText = self::metaBlockTypeLookup($BlockType); |
61 $BlockTypeText = self::metaBlockTypeLookup($BlockType); |
54 |
62 |
55 if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) { |
63 if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) { |
56 $this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file'); |
64 $this->warning('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file'); |
57 break; |
65 break; |
58 } |
66 } |
59 if ($BlockLength < 1) { |
67 if ($BlockLength < 1) { |
|
68 if ($BlockTypeText != 'reserved') { |
|
69 // probably supposed to be zero-length |
|
70 $this->warning('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockTypeText.') at offset '.$BlockOffset.' is zero bytes'); |
|
71 continue; |
|
72 } |
60 $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid'); |
73 $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid'); |
61 break; |
74 break; |
62 } |
75 } |
63 |
76 |
64 $info['flac'][$BlockTypeText]['raw'] = array(); |
77 $info['flac'][$BlockTypeText]['raw'] = array(); |
165 |
178 |
166 // set md5_data_source - built into flac 0.5+ |
179 // set md5_data_source - built into flac 0.5+ |
167 if (isset($info['flac']['STREAMINFO']['audio_signature'])) { |
180 if (isset($info['flac']['STREAMINFO']['audio_signature'])) { |
168 |
181 |
169 if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { |
182 if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { |
170 $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); |
183 $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); |
171 } |
184 } |
172 else { |
185 else { |
173 $info['md5_data_source'] = ''; |
186 $info['md5_data_source'] = ''; |
174 $md5 = $info['flac']['STREAMINFO']['audio_signature']; |
187 $md5 = $info['flac']['STREAMINFO']['audio_signature']; |
175 for ($i = 0; $i < strlen($md5); $i++) { |
188 for ($i = 0; $i < strlen($md5); $i++) { |
192 } |
205 } |
193 |
206 |
194 return true; |
207 return true; |
195 } |
208 } |
196 |
209 |
197 private function parseSTREAMINFO($BlockData) { |
210 |
198 $info = &$this->getid3->info; |
211 /** |
199 |
212 * @param string $BlockData |
200 $info['flac']['STREAMINFO'] = array(); |
213 * |
201 $streaminfo = &$info['flac']['STREAMINFO']; |
214 * @return array |
202 |
215 */ |
|
216 public static function parseSTREAMINFOdata($BlockData) { |
|
217 $streaminfo = array(); |
203 $streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2)); |
218 $streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2)); |
204 $streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2)); |
219 $streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2)); |
205 $streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3)); |
220 $streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3)); |
206 $streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3)); |
221 $streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3)); |
207 |
222 |
209 $streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20)); |
224 $streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20)); |
210 $streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1; |
225 $streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1; |
211 $streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1; |
226 $streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1; |
212 $streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36)); |
227 $streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36)); |
213 |
228 |
214 $streaminfo['audio_signature'] = substr($BlockData, 18, 16); |
229 $streaminfo['audio_signature'] = substr($BlockData, 18, 16); |
215 |
230 |
216 if (!empty($streaminfo['sample_rate'])) { |
231 return $streaminfo; |
|
232 } |
|
233 |
|
234 /** |
|
235 * @param string $BlockData |
|
236 * |
|
237 * @return bool |
|
238 */ |
|
239 private function parseSTREAMINFO($BlockData) { |
|
240 $info = &$this->getid3->info; |
|
241 |
|
242 $info['flac']['STREAMINFO'] = self::parseSTREAMINFOdata($BlockData); |
|
243 |
|
244 if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { |
217 |
245 |
218 $info['audio']['bitrate_mode'] = 'vbr'; |
246 $info['audio']['bitrate_mode'] = 'vbr'; |
219 $info['audio']['sample_rate'] = $streaminfo['sample_rate']; |
247 $info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; |
220 $info['audio']['channels'] = $streaminfo['channels']; |
248 $info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; |
221 $info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample']; |
249 $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; |
222 $info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate']; |
250 $info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; |
223 if ($info['playtime_seconds'] > 0) { |
251 if ($info['playtime_seconds'] > 0) { |
224 if (!$this->isDependencyFor('matroska')) { |
252 if (!$this->isDependencyFor('matroska')) { |
225 $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; |
253 $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; |
226 } |
254 } |
227 else { |
255 else { |
234 } |
262 } |
235 |
263 |
236 return true; |
264 return true; |
237 } |
265 } |
238 |
266 |
|
267 /** |
|
268 * @param string $BlockData |
|
269 * |
|
270 * @return bool |
|
271 */ |
239 private function parseAPPLICATION($BlockData) { |
272 private function parseAPPLICATION($BlockData) { |
240 $info = &$this->getid3->info; |
273 $info = &$this->getid3->info; |
241 |
274 |
242 $ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4)); |
275 $ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4)); |
243 $info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID); |
276 $info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID); |
244 $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4); |
277 $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4); |
245 |
278 |
246 return true; |
279 return true; |
247 } |
280 } |
248 |
281 |
|
282 /** |
|
283 * @param string $BlockData |
|
284 * |
|
285 * @return bool |
|
286 */ |
249 private function parseSEEKTABLE($BlockData) { |
287 private function parseSEEKTABLE($BlockData) { |
250 $info = &$this->getid3->info; |
288 $info = &$this->getid3->info; |
251 |
289 |
252 $offset = 0; |
290 $offset = 0; |
253 $BlockLength = strlen($BlockData); |
291 $BlockLength = strlen($BlockData); |
292 unset($getid3_ogg); |
335 unset($getid3_ogg); |
293 |
336 |
294 return true; |
337 return true; |
295 } |
338 } |
296 |
339 |
|
340 /** |
|
341 * @param string $BlockData |
|
342 * |
|
343 * @return bool |
|
344 */ |
297 private function parseCUESHEET($BlockData) { |
345 private function parseCUESHEET($BlockData) { |
298 $info = &$this->getid3->info; |
346 $info = &$this->getid3->info; |
299 $offset = 0; |
347 $offset = 0; |
300 $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0"); |
348 $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0"); |
301 $offset += 128; |
349 $offset += 128; |
344 |
392 |
345 return true; |
393 return true; |
346 } |
394 } |
347 |
395 |
348 /** |
396 /** |
349 * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment |
397 * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment |
350 * External usage: audio.ogg |
398 * External usage: audio.ogg |
351 */ |
399 * |
|
400 * @return bool |
|
401 */ |
352 public function parsePICTURE() { |
402 public function parsePICTURE() { |
353 $info = &$this->getid3->info; |
403 $info = &$this->getid3->info; |
354 |
404 |
355 $picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4)); |
405 $picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4)); |
356 $picture['picturetype'] = self::pictureTypeLookup($picture['typeid']); |
406 $picture['picturetype'] = self::pictureTypeLookup($picture['typeid']); |
391 6 => 'PICTURE', |
446 6 => 'PICTURE', |
392 ); |
447 ); |
393 return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved'); |
448 return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved'); |
394 } |
449 } |
395 |
450 |
|
451 /** |
|
452 * @param int $applicationid |
|
453 * |
|
454 * @return string |
|
455 */ |
396 public static function applicationIDLookup($applicationid) { |
456 public static function applicationIDLookup($applicationid) { |
397 // http://flac.sourceforge.net/id.html |
457 // http://flac.sourceforge.net/id.html |
398 static $lookup = array( |
458 static $lookup = array( |
399 0x41544348 => 'FlacFile', // "ATCH" |
459 0x41544348 => 'FlacFile', // "ATCH" |
400 0x42534F4C => 'beSolo', // "BSOL" |
460 0x42534F4C => 'beSolo', // "BSOL" |
421 0x786D6364 => 'xmcd', // "xmcd" |
481 0x786D6364 => 'xmcd', // "xmcd" |
422 ); |
482 ); |
423 return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved'); |
483 return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved'); |
424 } |
484 } |
425 |
485 |
|
486 /** |
|
487 * @param int $type_id |
|
488 * |
|
489 * @return string |
|
490 */ |
426 public static function pictureTypeLookup($type_id) { |
491 public static function pictureTypeLookup($type_id) { |
427 static $lookup = array ( |
492 static $lookup = array ( |
428 0 => 'Other', |
493 0 => 'Other', |
429 1 => '32x32 pixels \'file icon\' (PNG only)', |
494 1 => '32x32 pixels \'file icon\' (PNG only)', |
430 2 => 'Other file icon', |
495 2 => 'Other file icon', |