[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

title

Body

[close]

/wp-includes/ID3/ -> module.audio.flac.php (source)

   1  <?php
   2  
   3  /////////////////////////////////////////////////////////////////
   4  /// getID3() by James Heinrich <info@getid3.org>               //
   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                  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              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              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  	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  }


Generated: Sat Nov 23 20:47:33 2019 Cross-referenced by PHPXref 0.7