ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
Flac.php
Go to the documentation of this file.
1<?php
2
3namespace GetId3\Module\Audio;
4
8
11// available at http://getid3.sourceforge.net //
12// or http://www.getid3.org //
14// See readme.txt for more details //
16// //
17// module.audio.flac.php //
18// module for analyzing FLAC and OggFLAC audio files //
19// dependencies: module.audio.ogg.php //
20// ///
22
30class Flac extends BaseHandler
31{
32
37 public function analyze()
38 {
39 $info = &$this->getid3->info;
40
41 // http://flac.sourceforge.net/format.html
42
43 $this->fseek($info['avdataoffset']);
44 $StreamMarker = $this->fread(4);
45 $magic = 'fLaC';
46 if ($StreamMarker != $magic) {
47 return $this->error('Expecting "'.Helper::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.Helper::PrintHexBytes($StreamMarker).'"');
48 }
49 $info['fileformat'] = 'flac';
50 $info['audio']['dataformat'] = 'flac';
51 $info['audio']['bitrate_mode'] = 'vbr';
52 $info['audio']['lossless'] = true;
53
54 return $this->parseMETAdata();
55 }
56
61 public function parseMETAdata()
62 {
63 $info = &$this->getid3->info;
64 do {
65 $BlockOffset = $this->ftell();
66 $BlockHeader = $this->fread(4);
67 $LastBlockFlag = (bool) (Helper::BigEndian2Int(substr($BlockHeader, 0, 1)) & 0x80);
68 $BlockType = Helper::BigEndian2Int(substr($BlockHeader, 0, 1)) & 0x7F;
69 $BlockLength = Helper::BigEndian2Int(substr($BlockHeader, 1, 3));
70 $BlockTypeText = self::metaBlockTypeLookup($BlockType);
71
72 if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
73 $this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset.' extends beyond end of file');
74 break;
75 }
76 if ($BlockLength < 1) {
77 $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
78 break;
79 }
80
81 $info['flac'][$BlockTypeText]['raw'] = array();
82 $BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
83
84 $BlockTypeText_raw['offset'] = $BlockOffset;
85 $BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
86 $BlockTypeText_raw['block_type'] = $BlockType;
87 $BlockTypeText_raw['block_type_text'] = $BlockTypeText;
88 $BlockTypeText_raw['block_length'] = $BlockLength;
89 if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
90 $BlockTypeText_raw['block_data'] = $this->fread($BlockLength);
91 }
92
93 switch ($BlockTypeText) {
94 case 'STREAMINFO': // 0x00
95 if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
96 return false;
97 }
98 break;
99
100 case 'PADDING': // 0x01
101 // ignore
102 break;
103
104 case 'APPLICATION': // 0x02
105 if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
106 return false;
107 }
108 break;
109
110 case 'SEEKTABLE': // 0x03
111 if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
112 return false;
113 }
114 break;
115
116 case 'VORBIS_COMMENT': // 0x04
117 if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
118 return false;
119 }
120 break;
121
122 case 'CUESHEET': // 0x05
123 if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
124 return false;
125 }
126 break;
127
128 case 'PICTURE': // 0x06
129 if (!$this->parsePICTURE($BlockTypeText_raw)) {
130 return false;
131 }
132 break;
133
134 default:
135 $this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
136 }
137
138 unset($info['flac'][$BlockTypeText]['raw']);
139 $info['avdataoffset'] = $this->ftell();
140 } while ($LastBlockFlag === false);
141
142 // handle tags
143 if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
144 $info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
145 }
146 if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
147 $info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
148 }
149
150 // copy attachments to 'comments' array if nesesary
151 if (isset($info['flac']['PICTURE']) && $this->getid3->option_save_attachments === GetId3Core::ATTACHMENTS_INLINE) {
152 foreach ($info['flac']['PICTURE'] as $key => $valuearray) {
153 if (!empty($valuearray['image_mime']) && !empty($valuearray['data'])) {
154 $info['flac']['comments']['picture'][] = array('image_mime' => $valuearray['image_mime'], 'data' => $valuearray['data']);
155 unset($info['flac']['PICTURE'][$key]);
156 }
157 }
158 }
159
160 if (isset($info['flac']['STREAMINFO'])) {
161 if (!$this->isDependencyFor('matroska')) {
162 $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
163 }
164 $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
165 if ($info['flac']['uncompressed_audio_bytes'] == 0) {
166 return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
167 }
168 if (!$this->isDependencyFor('matroska')) {
169 $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
170 }
171 }
172
173 // set md5_data_source - built into flac 0.5+
174 if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
175
176 if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
177 $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
178 } else {
179 $info['md5_data_source'] = '';
180 $md5 = $info['flac']['STREAMINFO']['audio_signature'];
181 for ($i = 0; $i < strlen($md5); $i++) {
182 $info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
183 }
184 if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
185 unset($info['md5_data_source']);
186 }
187 }
188 }
189
190 if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
191 $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
192 if ($info['audio']['bits_per_sample'] == 8) {
193 // special case
194 // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
195 // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
196 $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');
197 }
198 }
199
200 return true;
201 }
202
208 private function parseSTREAMINFO($BlockData)
209 {
210 $info = &$this->getid3->info;
211
212 $info['flac']['STREAMINFO'] = array();
213 $streaminfo = &$info['flac']['STREAMINFO'];
214
215 $streaminfo['min_block_size'] = Helper::BigEndian2Int(substr($BlockData, 0, 2));
216 $streaminfo['max_block_size'] = Helper::BigEndian2Int(substr($BlockData, 2, 2));
217 $streaminfo['min_frame_size'] = Helper::BigEndian2Int(substr($BlockData, 4, 3));
218 $streaminfo['max_frame_size'] = Helper::BigEndian2Int(substr($BlockData, 7, 3));
219
220 $SRCSBSS = Helper::BigEndian2Bin(substr($BlockData, 10, 8));
221 $streaminfo['sample_rate'] = Helper::Bin2Dec(substr($SRCSBSS, 0, 20));
222 $streaminfo['channels'] = Helper::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1;
223 $streaminfo['bits_per_sample'] = Helper::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1;
224 $streaminfo['samples_stream'] = Helper::Bin2Dec(substr($SRCSBSS, 28, 36));
225
226 $streaminfo['audio_signature'] = substr($BlockData, 18, 16);
227
228 if (!empty($streaminfo['sample_rate'])) {
229
230 $info['audio']['bitrate_mode'] = 'vbr';
231 $info['audio']['sample_rate'] = $streaminfo['sample_rate'];
232 $info['audio']['channels'] = $streaminfo['channels'];
233 $info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample'];
234 $info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate'];
235 if ($info['playtime_seconds'] > 0) {
236 if (!$this->isDependencyFor('matroska')) {
237 $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
238 } else {
239 $this->warning('Cannot determine audio bitrate because total stream size is unknown');
240 }
241 }
242
243 } else {
244 return $this->error('Corrupt METAdata block: STREAMINFO');
245 }
246
247 return true;
248 }
249
255 private function parseAPPLICATION($BlockData)
256 {
257 $info = &$this->getid3->info;
258
259 $ApplicationID = Helper::BigEndian2Int(substr($BlockData, 0, 4));
260 $info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
261 $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
262
263 return true;
264 }
265
271 private function parseSEEKTABLE($BlockData)
272 {
273 $info = &$this->getid3->info;
274
275 $offset = 0;
276 $BlockLength = strlen($BlockData);
277 $placeholderpattern = str_repeat("\xFF", 8);
278 while ($offset < $BlockLength) {
279 $SampleNumberString = substr($BlockData, $offset, 8);
280 $offset += 8;
281 if ($SampleNumberString == $placeholderpattern) {
282
283 // placeholder point
284 Helper::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
285 $offset += 10;
286
287 } else {
288
289 $SampleNumber = Helper::BigEndian2Int($SampleNumberString);
290 $info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = Helper::BigEndian2Int(substr($BlockData, $offset, 8));
291 $offset += 8;
292 $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = Helper::BigEndian2Int(substr($BlockData, $offset, 2));
293 $offset += 2;
294
295 }
296 }
297
298 return true;
299 }
300
306 private function parseVORBIS_COMMENT($BlockData)
307 {
308 $info = &$this->getid3->info;
309
310 $getid3_ogg = new Ogg($this->getid3);
311 if ($this->isDependencyFor('matroska')) {
312 $getid3_ogg->data_string_flag = true;
313 $getid3_ogg->data_string = $this->data_string;
314 }
315 $getid3_ogg->ParseVorbisComments();
316 if (isset($info['ogg'])) {
317 unset($info['ogg']['comments_raw']);
318 $info['flac']['VORBIS_COMMENT'] = $info['ogg'];
319 unset($info['ogg']);
320 }
321
322 unset($getid3_ogg);
323
324 return true;
325 }
326
332 private function parseCUESHEET($BlockData)
333 {
334 $info = &$this->getid3->info;
335 $offset = 0;
336 $info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0");
337 $offset += 128;
338 $info['flac']['CUESHEET']['lead_in_samples'] = Helper::BigEndian2Int(substr($BlockData, $offset, 8));
339 $offset += 8;
340 $info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (Helper::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
341 $offset += 1;
342
343 $offset += 258; // reserved
344
345 $info['flac']['CUESHEET']['number_tracks'] = Helper::BigEndian2Int(substr($BlockData, $offset, 1));
346 $offset += 1;
347
348 for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
349 $TrackSampleOffset = Helper::BigEndian2Int(substr($BlockData, $offset, 8));
350 $offset += 8;
351 $TrackNumber = Helper::BigEndian2Int(substr($BlockData, $offset, 1));
352 $offset += 1;
353
354 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset;
355
356 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12);
357 $offset += 12;
358
359 $TrackFlagsRaw = Helper::BigEndian2Int(substr($BlockData, $offset, 1));
360 $offset += 1;
361 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80);
362 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
363
364 $offset += 13; // reserved
365
366 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = Helper::BigEndian2Int(substr($BlockData, $offset, 1));
367 $offset += 1;
368
369 for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
370 $IndexSampleOffset = Helper::BigEndian2Int(substr($BlockData, $offset, 8));
371 $offset += 8;
372 $IndexNumber = Helper::BigEndian2Int(substr($BlockData, $offset, 1));
373 $offset += 1;
374
375 $offset += 3; // reserved
376
377 $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
378 }
379 }
380
381 return true;
382 }
383
389 public function parsePICTURE($Block='')
390 {
391 $info = &$this->getid3->info;
392
393 $picture['typeid'] = Helper::BigEndian2Int($this->fread(4));
394 $picture['type'] = self::pictureTypeLookup($picture['typeid']);
395 $picture['image_mime'] = $this->fread(Helper::BigEndian2Int($this->fread(4)));
396 $descr_length = Helper::BigEndian2Int($this->fread(4));
397 if ($descr_length) {
398 $picture['description'] = $this->fread($descr_length);
399 }
400 $picture['width'] = Helper::BigEndian2Int($this->fread(4));
401 $picture['height'] = Helper::BigEndian2Int($this->fread(4));
402 $picture['color_depth'] = Helper::BigEndian2Int($this->fread(4));
403 $picture['colors_indexed'] = Helper::BigEndian2Int($this->fread(4));
404 $data_length = Helper::BigEndian2Int($this->fread(4));
405
406 if ($picture['image_mime'] == '-->') {
407 $picture['data'] = $this->fread($data_length);
408 } else {
409 $this->saveAttachment(
410 $picture['data'],
411 $picture['type'].'_'.$this->ftell().'.'.substr($picture['image_mime'], 6),
412 $this->ftell(), $data_length);
413 }
414
415 $info['flac']['PICTURE'][] = $picture;
416
417 return true;
418 }
419
426 public static function metaBlockTypeLookup($blocktype)
427 {
428 static $metaBlockTypeLookup = array();
429 if (empty($metaBlockTypeLookup)) {
430 $metaBlockTypeLookup[0] = 'STREAMINFO';
431 $metaBlockTypeLookup[1] = 'PADDING';
432 $metaBlockTypeLookup[2] = 'APPLICATION';
433 $metaBlockTypeLookup[3] = 'SEEKTABLE';
434 $metaBlockTypeLookup[4] = 'VORBIS_COMMENT';
435 $metaBlockTypeLookup[5] = 'CUESHEET';
436 $metaBlockTypeLookup[6] = 'PICTURE';
437 }
438
439 return (isset($metaBlockTypeLookup[$blocktype]) ? $metaBlockTypeLookup[$blocktype] : 'reserved');
440 }
441
448 public static function applicationIDLookup($applicationid)
449 {
450 static $applicationIDLookup = array();
451 if (empty($applicationIDLookup)) {
452 // http://flac.sourceforge.net/id.html
453 $applicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol'
454 $applicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL'
455 }
456
457 return (isset($applicationIDLookup[$applicationid]) ? $applicationIDLookup[$applicationid] : 'reserved');
458 }
459
466 public static function pictureTypeLookup($type_id)
467 {
468 static $lookup = array (
469 0 => 'Other',
470 1 => '32x32 pixels \'file icon\' (PNG only)',
471 2 => 'Other file icon',
472 3 => 'Cover (front)',
473 4 => 'Cover (back)',
474 5 => 'Leaflet page',
475 6 => 'Media (e.g. label side of CD)',
476 7 => 'Lead artist/lead performer/soloist',
477 8 => 'Artist/performer',
478 9 => 'Conductor',
479 10 => 'Band/Orchestra',
480 11 => 'Composer',
481 12 => 'Lyricist/text writer',
482 13 => 'Recording Location',
483 14 => 'During recording',
484 15 => 'During performance',
485 16 => 'Movie/video screen capture',
486 17 => 'A bright coloured fish',
487 18 => 'Illustration',
488 19 => 'Band/artist logotype',
489 20 => 'Publisher/Studio logotype',
490 );
491
492 return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
493 }
494}
An exception for terminatinating execution or to throw for unit testing.
GetId3() by James Heinrich info@getid3.org //.
Definition: GetId3Core.php:26
const ATTACHMENTS_INLINE
Definition: GetId3Core.php:65
GetId3() by James Heinrich info@getid3.org //.
Definition: BaseHandler.php:26
saveAttachment(&$ThisFileInfoIndex, $filename, $offset, $length)
fseek($bytes, $whence=SEEK_SET)
GetId3() by James Heinrich info@getid3.org //.
Definition: Helper.php:27
static safe_inc(&$variable, $increment=1)
Definition: Helper.php:91
static Bin2Dec($binstring, $signed=false)
Definition: Helper.php:496
static BigEndian2Int($byteword, $synchsafe=false, $signed=false)
Definition: Helper.php:374
static PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8')
Definition: Helper.php:36
static BigEndian2Bin($byteword)
Definition: Helper.php:423
GetId3() by James Heinrich info@getid3.org //.
Definition: Flac.php:31
static metaBlockTypeLookup($blocktype)
@staticvar array $metaBlockTypeLookup
Definition: Flac.php:426
parseCUESHEET($BlockData)
Definition: Flac.php:332
parseSEEKTABLE($BlockData)
Definition: Flac.php:271
parseAPPLICATION($BlockData)
Definition: Flac.php:255
parseSTREAMINFO($BlockData)
Definition: Flac.php:208
static applicationIDLookup($applicationid)
@staticvar array $applicationIDLookup
Definition: Flac.php:448
parseVORBIS_COMMENT($BlockData)
Definition: Flac.php:306
static pictureTypeLookup($type_id)
@staticvar array $lookup
Definition: Flac.php:466
parsePICTURE($Block='')
Definition: Flac.php:389
GetId3() by James Heinrich info@getid3.org //.
Definition: Ogg.php:31
$info
Definition: example_052.php:80