ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
Flac.php
Go to the documentation of this file.
1 <?php
2 
3 namespace 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 
30 class 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 }
static BigEndian2Bin($byteword)
Definition: Helper.php:423
static PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8')
Definition: Helper.php:36
GetId3() by James Heinrich info@getid3.org //.
Definition: BaseHandler.php:25
parseAPPLICATION($BlockData)
Definition: Flac.php:255
static pictureTypeLookup($type_id)
array $lookup
Definition: Flac.php:466
parsePICTURE($Block='')
Definition: Flac.php:389
fseek($bytes, $whence=SEEK_SET)
$info
Definition: example_052.php:80
GetId3() by James Heinrich info@getid3.org //.
Definition: Ogg.php:30
parseSTREAMINFO($BlockData)
Definition: Flac.php:208
static metaBlockTypeLookup($blocktype)
array $metaBlockTypeLookup
Definition: Flac.php:426
GetId3() by James Heinrich info@getid3.org //.
Definition: Flac.php:30
parseVORBIS_COMMENT($BlockData)
Definition: Flac.php:306
Create styles array
The data for the language used.
parseCUESHEET($BlockData)
Definition: Flac.php:332
static BigEndian2Int($byteword, $synchsafe=false, $signed=false)
Definition: Helper.php:374
parseSEEKTABLE($BlockData)
Definition: Flac.php:271
static safe_inc(&$variable, $increment=1)
Definition: Helper.php:91
static Bin2Dec($binstring, $signed=false)
Definition: Helper.php:496
static applicationIDLookup($applicationid)
array $applicationIDLookup
Definition: Flac.php:448
saveAttachment(&$ThisFileInfoIndex, $filename, $offset, $length)
const ATTACHMENTS_INLINE
Definition: GetId3Core.php:65