getID3() PHP Cross Reference Developer Tools

Source: /getid3/module.audio.ogg.php - 756 lines - 37243 bytes - Summary - Text - Print

   1  <?php
   2  /////////////////////////////////////////////////////////////////

   3  /// getID3() by James Heinrich <info@getid3.org>               //

   4  //  available at http://getid3.sourceforge.net                 //

   5  //            or http://www.getid3.org                         //

   6  //          also https://github.com/JamesHeinrich/getID3       //

   7  /////////////////////////////////////////////////////////////////

   8  // See readme.txt for more details                             //

   9  /////////////////////////////////////////////////////////////////

  10  //                                                             //

  11  // module.audio.ogg.php                                        //

  12  // module for analyzing Ogg Vorbis, OggFLAC and Speex files    //

  13  // dependencies: module.audio.flac.php                         //

  14  //                                                            ///

  15  /////////////////////////////////////////////////////////////////

  16  
  17  getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
  18  
  19  class getid3_ogg extends getid3_handler
  20  {
  21      // http://xiph.org/vorbis/doc/Vorbis_I_spec.html

  22  	public function Analyze() {
  23          $info = &$this->getid3->info;
  24  
  25          $info['fileformat'] = 'ogg';
  26  
  27          // Warn about illegal tags - only vorbiscomments are allowed

  28          if (isset($info['id3v2'])) {
  29              $info['warning'][] = 'Illegal ID3v2 tag present.';
  30          }
  31          if (isset($info['id3v1'])) {
  32              $info['warning'][] = 'Illegal ID3v1 tag present.';
  33          }
  34          if (isset($info['ape'])) {
  35              $info['warning'][] = 'Illegal APE tag present.';
  36          }
  37  
  38  
  39          // Page 1 - Stream Header

  40  
  41          $this->fseek($info['avdataoffset']);
  42  
  43          $oggpageinfo = $this->ParseOggPageHeader();
  44          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  45  
  46          if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
  47              $info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)';
  48              unset($info['fileformat']);
  49              unset($info['ogg']);
  50              return false;
  51          }
  52  
  53          $filedata = $this->fread($oggpageinfo['page_length']);
  54          $filedataoffset = 0;
  55  
  56          if (substr($filedata, 0, 4) == 'fLaC') {
  57  
  58              $info['audio']['dataformat']   = 'flac';
  59              $info['audio']['bitrate_mode'] = 'vbr';
  60              $info['audio']['lossless']     = true;
  61  
  62          } elseif (substr($filedata, 1, 6) == 'vorbis') {
  63  
  64              $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
  65  
  66          } elseif (substr($filedata, 0, 8) == 'Speex   ') {
  67  
  68              // http://www.speex.org/manual/node10.html

  69  
  70              $info['audio']['dataformat']   = 'speex';
  71              $info['mime_type']             = 'audio/speex';
  72              $info['audio']['bitrate_mode'] = 'abr';
  73              $info['audio']['lossless']     = false;
  74  
  75              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '

  76              $filedataoffset += 8;
  77              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
  78              $filedataoffset += 20;
  79              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  80              $filedataoffset += 4;
  81              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  82              $filedataoffset += 4;
  83              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  84              $filedataoffset += 4;
  85              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  86              $filedataoffset += 4;
  87              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  88              $filedataoffset += 4;
  89              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  90              $filedataoffset += 4;
  91              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  92              $filedataoffset += 4;
  93              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  94              $filedataoffset += 4;
  95              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  96              $filedataoffset += 4;
  97              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  98              $filedataoffset += 4;
  99              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 100              $filedataoffset += 4;
 101              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 102              $filedataoffset += 4;
 103              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 104              $filedataoffset += 4;
 105  
 106              $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
 107              $info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
 108              $info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
 109              $info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
 110              $info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
 111  
 112              $info['audio']['sample_rate']   = $info['speex']['sample_rate'];
 113              $info['audio']['channels']      = $info['speex']['channels'];
 114              if ($info['speex']['vbr']) {
 115                  $info['audio']['bitrate_mode'] = 'vbr';
 116              }
 117  
 118          } elseif (substr($filedata, 0, 7) == "\x80".'theora') {
 119  
 120              // http://www.theora.org/doc/Theora.pdf (section 6.2)

 121  
 122              $info['ogg']['pageheader']['theora']['theora_magic']             =                           substr($filedata, $filedataoffset,  7); // hard-coded to "\x80.'theora'

 123              $filedataoffset += 7;
 124              $info['ogg']['pageheader']['theora']['version_major']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 125              $filedataoffset += 1;
 126              $info['ogg']['pageheader']['theora']['version_minor']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 127              $filedataoffset += 1;
 128              $info['ogg']['pageheader']['theora']['version_revision']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 129              $filedataoffset += 1;
 130              $info['ogg']['pageheader']['theora']['frame_width_macroblocks']  = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
 131              $filedataoffset += 2;
 132              $info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
 133              $filedataoffset += 2;
 134              $info['ogg']['pageheader']['theora']['resolution_x']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 135              $filedataoffset += 3;
 136              $info['ogg']['pageheader']['theora']['resolution_y']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 137              $filedataoffset += 3;
 138              $info['ogg']['pageheader']['theora']['picture_offset_x']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 139              $filedataoffset += 1;
 140              $info['ogg']['pageheader']['theora']['picture_offset_y']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 141              $filedataoffset += 1;
 142              $info['ogg']['pageheader']['theora']['frame_rate_numerator']     = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
 143              $filedataoffset += 4;
 144              $info['ogg']['pageheader']['theora']['frame_rate_denominator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
 145              $filedataoffset += 4;
 146              $info['ogg']['pageheader']['theora']['pixel_aspect_numerator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 147              $filedataoffset += 3;
 148              $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 149              $filedataoffset += 3;
 150              $info['ogg']['pageheader']['theora']['color_space_id']           = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 151              $filedataoffset += 1;
 152              $info['ogg']['pageheader']['theora']['nominal_bitrate']          = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 153              $filedataoffset += 3;
 154              $info['ogg']['pageheader']['theora']['flags']                    = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
 155              $filedataoffset += 2;
 156  
 157              $info['ogg']['pageheader']['theora']['quality']         = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10;
 158              $info['ogg']['pageheader']['theora']['kfg_shift']       = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >>  5;
 159              $info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >>  3;
 160              $info['ogg']['pageheader']['theora']['reserved']        = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >>  0; // should be 0

 161              $info['ogg']['pageheader']['theora']['color_space']     = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']);
 162              $info['ogg']['pageheader']['theora']['pixel_format']    = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']);
 163  
 164              $info['video']['dataformat']   = 'theora';
 165              $info['mime_type']             = 'video/ogg';
 166              //$info['audio']['bitrate_mode'] = 'abr';

 167              //$info['audio']['lossless']     = false;

 168              $info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x'];
 169              $info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y'];
 170              if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) {
 171                  $info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator'];
 172              }
 173              if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) {
 174                  $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'];
 175              }
 176  $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable';
 177  
 178  
 179          } elseif (substr($filedata, 0, 8) == "fishead\x00") {
 180  
 181              // Ogg Skeleton version 3.0 Format Specification

 182              // http://xiph.org/ogg/doc/skeleton.html

 183              $filedataoffset += 8;
 184              $info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 185              $filedataoffset += 2;
 186              $info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 187              $filedataoffset += 2;
 188              $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 189              $filedataoffset += 8;
 190              $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 191              $filedataoffset += 8;
 192              $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 193              $filedataoffset += 8;
 194              $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 195              $filedataoffset += 8;
 196              $info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
 197              $filedataoffset += 20;
 198  
 199              $info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
 200              $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
 201              $info['ogg']['skeleton']['fishead']['basetime']         = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']         / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
 202              $info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];
 203  
 204  
 205              $counter = 0;
 206              do {
 207                  $oggpageinfo = $this->ParseOggPageHeader();
 208                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
 209                  $filedata = $this->fread($oggpageinfo['page_length']);
 210                  $this->fseek($oggpageinfo['page_end_offset']);
 211  
 212                  if (substr($filedata, 0, 8) == "fisbone\x00") {
 213  
 214                      $filedataoffset = 8;
 215                      $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 216                      $filedataoffset += 4;
 217                      $info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 218                      $filedataoffset += 4;
 219                      $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 220                      $filedataoffset += 4;
 221                      $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 222                      $filedataoffset += 8;
 223                      $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 224                      $filedataoffset += 8;
 225                      $info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 226                      $filedataoffset += 8;
 227                      $info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 228                      $filedataoffset += 4;
 229                      $info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
 230                      $filedataoffset += 1;
 231                      $info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
 232                      $filedataoffset += 3;
 233  
 234                  } elseif (substr($filedata, 1, 6) == 'theora') {
 235  
 236                      $info['video']['dataformat'] = 'theora1';
 237                      $info['error'][] = 'Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']';
 238                      //break;

 239  
 240                  } elseif (substr($filedata, 1, 6) == 'vorbis') {
 241  
 242                      $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
 243  
 244                  } else {
 245                      $info['error'][] = 'unexpected';
 246                      //break;

 247                  }
 248              //} while ($oggpageinfo['page_seqno'] == 0);

 249              } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
 250  
 251              $this->fseek($oggpageinfo['page_start_offset']);
 252  
 253              $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
 254              //return false;

 255  
 256          } else {
 257  
 258              $info['error'][] = 'Expecting either "Speex   " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
 259              unset($info['ogg']);
 260              unset($info['mime_type']);
 261              return false;
 262  
 263          }
 264  
 265          // Page 2 - Comment Header

 266          $oggpageinfo = $this->ParseOggPageHeader();
 267          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 268  
 269          switch ($info['audio']['dataformat']) {
 270              case 'vorbis':
 271                  $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 272                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
 273                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'

 274  
 275                  $this->ParseVorbisComments();
 276                  break;
 277  
 278              case 'flac':
 279                  $flac = new getid3_flac($this->getid3);
 280                  if (!$flac->parseMETAdata()) {
 281                      $info['error'][] = 'Failed to parse FLAC headers';
 282                      return false;
 283                  }
 284                  unset($flac);
 285                  break;
 286  
 287              case 'speex':
 288                  $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
 289                  $this->ParseVorbisComments();
 290                  break;
 291          }
 292  
 293  
 294          // Last Page - Number of Samples

 295          if (!getid3_lib::intValueSupported($info['avdataend'])) {
 296  
 297              $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
 298  
 299          } else {
 300  
 301              $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
 302              $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
 303              if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
 304                  $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
 305                  $info['avdataend'] = $this->ftell();
 306                  $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
 307                  $info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
 308                  if ($info['ogg']['samples'] == 0) {
 309                      $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
 310                      return false;
 311                  }
 312                  if (!empty($info['audio']['sample_rate'])) {
 313                      $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
 314                  }
 315              }
 316  
 317          }
 318  
 319          if (!empty($info['ogg']['bitrate_average'])) {
 320              $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
 321          } elseif (!empty($info['ogg']['bitrate_nominal'])) {
 322              $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
 323          } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
 324              $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
 325          }
 326          if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
 327              if ($info['audio']['bitrate'] == 0) {
 328                  $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
 329                  return false;
 330              }
 331              $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
 332          }
 333  
 334          if (isset($info['ogg']['vendor'])) {
 335              $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
 336  
 337              // Vorbis only

 338              if ($info['audio']['dataformat'] == 'vorbis') {
 339  
 340                  // Vorbis 1.0 starts with Xiph.Org

 341                  if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
 342  
 343                      if ($info['audio']['bitrate_mode'] == 'abr') {
 344  
 345                          // Set -b 128 on abr files

 346                          $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
 347  
 348                      } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
 349                          // Set -q N on vbr files

 350                          $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
 351  
 352                      }
 353                  }
 354  
 355                  if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
 356                      $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
 357                  }
 358              }
 359          }
 360  
 361          return true;
 362      }
 363  
 364  	public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
 365          $info = &$this->getid3->info;
 366          $info['audio']['dataformat'] = 'vorbis';
 367          $info['audio']['lossless']   = false;
 368  
 369          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 370          $filedataoffset += 1;
 371          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'

 372          $filedataoffset += 6;
 373          $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 374          $filedataoffset += 4;
 375          $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 376          $filedataoffset += 1;
 377          $info['audio']['channels']       = $info['ogg']['numberofchannels'];
 378          $info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 379          $filedataoffset += 4;
 380          if ($info['ogg']['samplerate'] == 0) {
 381              $info['error'][] = 'Corrupt Ogg file: sample rate == zero';
 382              return false;
 383          }
 384          $info['audio']['sample_rate']    = $info['ogg']['samplerate'];
 385          $info['ogg']['samples']          = 0; // filled in later

 386          $info['ogg']['bitrate_average']  = 0; // filled in later

 387          $info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 388          $filedataoffset += 4;
 389          $info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 390          $filedataoffset += 4;
 391          $info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 392          $filedataoffset += 4;
 393          $info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
 394          $info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
 395          $info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet

 396  
 397          $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr

 398          if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
 399              unset($info['ogg']['bitrate_max']);
 400              $info['audio']['bitrate_mode'] = 'abr';
 401          }
 402          if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
 403              unset($info['ogg']['bitrate_nominal']);
 404          }
 405          if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
 406              unset($info['ogg']['bitrate_min']);
 407              $info['audio']['bitrate_mode'] = 'abr';
 408          }
 409          return true;
 410      }
 411  
 412  	public function ParseOggPageHeader() {
 413          // http://xiph.org/ogg/vorbis/doc/framing.html

 414          $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file

 415  
 416          $filedata = $this->fread($this->getid3->fread_buffer_size());
 417          $filedataoffset = 0;
 418          while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
 419              if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
 420                  // should be found before here

 421                  return false;
 422              }
 423              if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
 424                  if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
 425                      // get some more data, unless eof, in which case fail

 426                      return false;
 427                  }
 428              }
 429          }
 430          $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'

 431  
 432          $oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 433          $filedataoffset += 1;
 434          $oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 435          $filedataoffset += 1;
 436          $oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet

 437          $oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)

 438          $oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)

 439  
 440          $oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
 441          $filedataoffset += 8;
 442          $oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 443          $filedataoffset += 4;
 444          $oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 445          $filedataoffset += 4;
 446          $oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 447          $filedataoffset += 4;
 448          $oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 449          $filedataoffset += 1;
 450          $oggheader['page_length'] = 0;
 451          for ($i = 0; $i < $oggheader['page_segments']; $i++) {
 452              $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 453              $filedataoffset += 1;
 454              $oggheader['page_length'] += $oggheader['segment_table'][$i];
 455          }
 456          $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
 457          $oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
 458          $this->fseek($oggheader['header_end_offset']);
 459  
 460          return $oggheader;
 461      }
 462  
 463      // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005

 464  	public function ParseVorbisComments() {
 465          $info = &$this->getid3->info;
 466  
 467          $OriginalOffset = $this->ftell();
 468          $commentdataoffset = 0;
 469          $VorbisCommentPage = 1;
 470  
 471          switch ($info['audio']['dataformat']) {
 472              case 'vorbis':
 473              case 'speex':
 474                  $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block

 475                  $this->fseek($CommentStartOffset);
 476                  $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
 477                  $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
 478  
 479                  if ($info['audio']['dataformat'] == 'vorbis') {
 480                      $commentdataoffset += (strlen('vorbis') + 1);
 481                  }
 482                  break;
 483  
 484              case 'flac':
 485                  $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
 486                  $this->fseek($CommentStartOffset);
 487                  $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
 488                  break;
 489  
 490              default:
 491                  return false;
 492          }
 493  
 494          $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 495          $commentdataoffset += 4;
 496  
 497          $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
 498          $commentdataoffset += $VendorSize;
 499  
 500          $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 501          $commentdataoffset += 4;
 502          $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
 503  
 504          $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
 505          $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
 506          for ($i = 0; $i < $CommentsCount; $i++) {
 507  
 508              $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
 509  
 510              if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
 511                  if ($oggpageinfo = $this->ParseOggPageHeader()) {
 512                      $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 513  
 514                      $VorbisCommentPage++;
 515  
 516                      // First, save what we haven't read yet

 517                      $AsYetUnusedData = substr($commentdata, $commentdataoffset);
 518  
 519                      // Then take that data off the end

 520                      $commentdata     = substr($commentdata, 0, $commentdataoffset);
 521  
 522                      // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct

 523                      $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 524                      $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 525  
 526                      // Finally, stick the unused data back on the end

 527                      $commentdata .= $AsYetUnusedData;
 528  
 529                      //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);

 530                      $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
 531                  }
 532  
 533              }
 534              $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 535  
 536              // replace avdataoffset with position just after the last vorbiscomment

 537              $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
 538  
 539              $commentdataoffset += 4;
 540              while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
 541                  if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
 542                      $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
 543                      break 2;
 544                  }
 545  
 546                  $VorbisCommentPage++;
 547  
 548                  $oggpageinfo = $this->ParseOggPageHeader();
 549                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 550  
 551                  // First, save what we haven't read yet

 552                  $AsYetUnusedData = substr($commentdata, $commentdataoffset);
 553  
 554                  // Then take that data off the end

 555                  $commentdata     = substr($commentdata, 0, $commentdataoffset);
 556  
 557                  // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct

 558                  $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 559                  $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 560  
 561                  // Finally, stick the unused data back on the end

 562                  $commentdata .= $AsYetUnusedData;
 563  
 564                  //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);

 565                  if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
 566                      $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
 567                      break;
 568                  }
 569                  $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
 570                  if ($readlength <= 0) {
 571                      $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
 572                      break;
 573                  }
 574                  $commentdata .= $this->fread($readlength);
 575  
 576                  //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];

 577              }
 578              $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
 579              $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
 580              $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
 581  
 582              if (!$commentstring) {
 583  
 584                  // no comment?

 585                  $info['warning'][] = 'Blank Ogg comment ['.$i.']';
 586  
 587              } elseif (strstr($commentstring, '=')) {
 588  
 589                  $commentexploded = explode('=', $commentstring, 2);
 590                  $ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
 591                  $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
 592  
 593                  if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
 594  
 595                      // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE

 596                      // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.

 597                      // http://flac.sourceforge.net/format.html#metadata_block_picture

 598                      $flac = new getid3_flac($this->getid3);
 599                      $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
 600                      $flac->parsePICTURE();
 601                      $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
 602                      unset($flac);
 603  
 604                  } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
 605  
 606                      $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
 607                      $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
 608                      /** @todo use 'coverartmime' where available */

 609                      $imageinfo = getid3_lib::GetDataImageSize($data);
 610                      if ($imageinfo === false || !isset($imageinfo['mime'])) {
 611                          $this->warning('COVERART vorbiscomment tag contains invalid image');
 612                          continue;
 613                      }
 614  
 615                      $ogg = new self($this->getid3);
 616                      $ogg->setStringMode($data);
 617                      $info['ogg']['comments']['picture'][] = array(
 618                          'image_mime' => $imageinfo['mime'],
 619                          'data'       => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
 620                      );
 621                      unset($ogg);
 622  
 623                  } else {
 624  
 625                      $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
 626  
 627                  }
 628  
 629              } else {
 630  
 631                  $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
 632  
 633              }
 634              unset($ThisFileInfo_ogg_comments_raw[$i]);
 635          }
 636          unset($ThisFileInfo_ogg_comments_raw);
 637  
 638  
 639          // Replay Gain Adjustment

 640          // http://privatewww.essex.ac.uk/~djmrob/replaygain/

 641          if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
 642              foreach ($info['ogg']['comments'] as $index => $commentvalue) {
 643                  switch ($index) {
 644                      case 'rg_audiophile':
 645                      case 'replaygain_album_gain':
 646                          $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
 647                          unset($info['ogg']['comments'][$index]);
 648                          break;
 649  
 650                      case 'rg_radio':
 651                      case 'replaygain_track_gain':
 652                          $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
 653                          unset($info['ogg']['comments'][$index]);
 654                          break;
 655  
 656                      case 'replaygain_album_peak':
 657                          $info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
 658                          unset($info['ogg']['comments'][$index]);
 659                          break;
 660  
 661                      case 'rg_peak':
 662                      case 'replaygain_track_peak':
 663                          $info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
 664                          unset($info['ogg']['comments'][$index]);
 665                          break;
 666  
 667                      case 'replaygain_reference_loudness':
 668                          $info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
 669                          unset($info['ogg']['comments'][$index]);
 670                          break;
 671  
 672                      default:
 673                          // do nothing

 674                          break;
 675                  }
 676              }
 677          }
 678  
 679          $this->fseek($OriginalOffset);
 680  
 681          return true;
 682      }
 683  
 684  	public static function SpeexBandModeLookup($mode) {
 685          static $SpeexBandModeLookup = array();
 686          if (empty($SpeexBandModeLookup)) {
 687              $SpeexBandModeLookup[0] = 'narrow';
 688              $SpeexBandModeLookup[1] = 'wide';
 689              $SpeexBandModeLookup[2] = 'ultra-wide';
 690          }
 691          return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
 692      }
 693  
 694  
 695  	public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
 696          for ($i = 0; $i < $SegmentNumber; $i++) {
 697              $segmentlength = 0;
 698              foreach ($OggInfoArray['segment_table'] as $key => $value) {
 699                  $segmentlength += $value;
 700                  if ($value < 255) {
 701                      break;
 702                  }
 703              }
 704          }
 705          return $segmentlength;
 706      }
 707  
 708  
 709  	public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
 710  
 711          // decrease precision

 712          $nominal_bitrate = $nominal_bitrate / 1000;
 713  
 714          if ($nominal_bitrate < 128) {
 715              // q-1 to q4

 716              $qval = ($nominal_bitrate - 64) / 16;
 717          } elseif ($nominal_bitrate < 256) {
 718              // q4 to q8

 719              $qval = $nominal_bitrate / 32;
 720          } elseif ($nominal_bitrate < 320) {
 721              // q8 to q9

 722              $qval = ($nominal_bitrate + 256) / 64;
 723          } else {
 724              // q9 to q10

 725              $qval = ($nominal_bitrate + 1300) / 180;
 726          }
 727          //return $qval; // 5.031324

 728          //return intval($qval); // 5

 729          return round($qval, 1); // 5 or 4.9

 730      }
 731  
 732  	public static function TheoraColorSpace($colorspace_id) {
 733          // http://www.theora.org/doc/Theora.pdf (table 6.3)

 734          static $TheoraColorSpaceLookup = array();
 735          if (empty($TheoraColorSpaceLookup)) {
 736              $TheoraColorSpaceLookup[0] = 'Undefined';
 737              $TheoraColorSpaceLookup[1] = 'Rec. 470M';
 738              $TheoraColorSpaceLookup[2] = 'Rec. 470BG';
 739              $TheoraColorSpaceLookup[3] = 'Reserved';
 740          }
 741          return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null);
 742      }
 743  
 744  	public static function TheoraPixelFormat($pixelformat_id) {
 745          // http://www.theora.org/doc/Theora.pdf (table 6.4)

 746          static $TheoraPixelFormatLookup = array();
 747          if (empty($TheoraPixelFormatLookup)) {
 748              $TheoraPixelFormatLookup[0] = '4:2:0';
 749              $TheoraPixelFormatLookup[1] = 'Reserved';
 750              $TheoraPixelFormatLookup[2] = '4:2:2';
 751              $TheoraPixelFormatLookup[3] = '4:4:4';
 752          }
 753          return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null);
 754      }
 755  
 756  }

title

Description

title

Description

title

Description

title

title

Body