[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ID3/ -> module.audio.ogg.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.ogg.php                                        //
  12  // module for analyzing Ogg Vorbis, OggFLAC and Speex files    //
  13  // dependencies: module.audio.flac.php                         //
  14  //                                                            ///
  15  /////////////////////////////////////////////////////////////////
  16  
  17  if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
  18      exit;
  19  }
  20  getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
  21  
  22  class getid3_ogg extends getid3_handler
  23  {
  24      /**
  25       * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html
  26       *
  27       * @return bool
  28       */
  29  	public function Analyze() {
  30          $info = &$this->getid3->info;
  31  
  32          $info['fileformat'] = 'ogg';
  33  
  34          // Warn about illegal tags - only vorbiscomments are allowed
  35          if (isset($info['id3v2'])) {
  36              $this->warning('Illegal ID3v2 tag present.');
  37          }
  38          if (isset($info['id3v1'])) {
  39              $this->warning('Illegal ID3v1 tag present.');
  40          }
  41          if (isset($info['ape'])) {
  42              $this->warning('Illegal APE tag present.');
  43          }
  44  
  45  
  46          // Page 1 - Stream Header
  47  
  48          $this->fseek($info['avdataoffset']);
  49  
  50          $oggpageinfo = $this->ParseOggPageHeader();
  51          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  52  
  53          if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
  54              $this->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?)');
  55              unset($info['fileformat']);
  56              unset($info['ogg']);
  57              return false;
  58          }
  59  
  60          $filedata = $this->fread($oggpageinfo['page_length']);
  61          $filedataoffset = 0;
  62  
  63          if (substr($filedata, 0, 4) == 'fLaC') {
  64  
  65              $info['audio']['dataformat']   = 'flac';
  66              $info['audio']['bitrate_mode'] = 'vbr';
  67              $info['audio']['lossless']     = true;
  68  
  69          } elseif (substr($filedata, 1, 6) == 'vorbis') {
  70  
  71              $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
  72  
  73          } elseif (substr($filedata, 0, 8) == 'OpusHead') {
  74  
  75              if ($this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) === false) {
  76                  return false;
  77              }
  78  
  79          } elseif (substr($filedata, 0, 8) == 'Speex   ') {
  80  
  81              // http://www.speex.org/manual/node10.html
  82  
  83              $info['audio']['dataformat']   = 'speex';
  84              $info['mime_type']             = 'audio/speex';
  85              $info['audio']['bitrate_mode'] = 'abr';
  86              $info['audio']['lossless']     = false;
  87  
  88              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '
  89              $filedataoffset += 8;
  90              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
  91              $filedataoffset += 20;
  92              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  93              $filedataoffset += 4;
  94              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  95              $filedataoffset += 4;
  96              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  97              $filedataoffset += 4;
  98              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  99              $filedataoffset += 4;
 100              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 101              $filedataoffset += 4;
 102              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 103              $filedataoffset += 4;
 104              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 105              $filedataoffset += 4;
 106              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 107              $filedataoffset += 4;
 108              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 109              $filedataoffset += 4;
 110              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 111              $filedataoffset += 4;
 112              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 113              $filedataoffset += 4;
 114              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 115              $filedataoffset += 4;
 116              $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 117              $filedataoffset += 4;
 118  
 119              $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
 120              $info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
 121              $info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
 122              $info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
 123              $info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
 124  
 125              $info['audio']['sample_rate']   = $info['speex']['sample_rate'];
 126              $info['audio']['channels']      = $info['speex']['channels'];
 127              if ($info['speex']['vbr']) {
 128                  $info['audio']['bitrate_mode'] = 'vbr';
 129              }
 130  
 131          } elseif (substr($filedata, 0, 7) == "\x80".'theora') {
 132  
 133              // http://www.theora.org/doc/Theora.pdf (section 6.2)
 134  
 135              $info['ogg']['pageheader']['theora']['theora_magic']             =                           substr($filedata, $filedataoffset,  7); // hard-coded to "\x80.'theora'
 136              $filedataoffset += 7;
 137              $info['ogg']['pageheader']['theora']['version_major']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 138              $filedataoffset += 1;
 139              $info['ogg']['pageheader']['theora']['version_minor']            = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 140              $filedataoffset += 1;
 141              $info['ogg']['pageheader']['theora']['version_revision']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 142              $filedataoffset += 1;
 143              $info['ogg']['pageheader']['theora']['frame_width_macroblocks']  = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
 144              $filedataoffset += 2;
 145              $info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
 146              $filedataoffset += 2;
 147              $info['ogg']['pageheader']['theora']['resolution_x']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 148              $filedataoffset += 3;
 149              $info['ogg']['pageheader']['theora']['resolution_y']             = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 150              $filedataoffset += 3;
 151              $info['ogg']['pageheader']['theora']['picture_offset_x']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 152              $filedataoffset += 1;
 153              $info['ogg']['pageheader']['theora']['picture_offset_y']         = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 154              $filedataoffset += 1;
 155              $info['ogg']['pageheader']['theora']['frame_rate_numerator']     = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
 156              $filedataoffset += 4;
 157              $info['ogg']['pageheader']['theora']['frame_rate_denominator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  4));
 158              $filedataoffset += 4;
 159              $info['ogg']['pageheader']['theora']['pixel_aspect_numerator']   = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 160              $filedataoffset += 3;
 161              $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 162              $filedataoffset += 3;
 163              $info['ogg']['pageheader']['theora']['color_space_id']           = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  1));
 164              $filedataoffset += 1;
 165              $info['ogg']['pageheader']['theora']['nominal_bitrate']          = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  3));
 166              $filedataoffset += 3;
 167              $info['ogg']['pageheader']['theora']['flags']                    = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset,  2));
 168              $filedataoffset += 2;
 169  
 170              $info['ogg']['pageheader']['theora']['quality']         = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10;
 171              $info['ogg']['pageheader']['theora']['kfg_shift']       = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >>  5;
 172              $info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >>  3;
 173              $info['ogg']['pageheader']['theora']['reserved']        = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >>  0; // should be 0
 174              $info['ogg']['pageheader']['theora']['color_space']     = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']);
 175              $info['ogg']['pageheader']['theora']['pixel_format']    = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']);
 176  
 177              $info['video']['dataformat']   = 'theora';
 178              $info['mime_type']             = 'video/ogg';
 179              //$info['audio']['bitrate_mode'] = 'abr';
 180              //$info['audio']['lossless']     = false;
 181              $info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x'];
 182              $info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y'];
 183              if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) {
 184                  $info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator'];
 185              }
 186              if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) {
 187                  $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'];
 188              }
 189  $this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable');
 190  
 191  
 192          } elseif (substr($filedata, 0, 8) == "fishead\x00") {
 193  
 194              // Ogg Skeleton version 3.0 Format Specification
 195              // http://xiph.org/ogg/doc/skeleton.html
 196              $filedataoffset += 8;
 197              $info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 198              $filedataoffset += 2;
 199              $info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 200              $filedataoffset += 2;
 201              $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 202              $filedataoffset += 8;
 203              $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 204              $filedataoffset += 8;
 205              $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 206              $filedataoffset += 8;
 207              $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 208              $filedataoffset += 8;
 209              $info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
 210              $filedataoffset += 20;
 211  
 212              $info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
 213              $info['ogg']['skeleton']['fishead']['presentationtime'] = getid3_lib::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'], $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']);
 214              $info['ogg']['skeleton']['fishead']['basetime']         = getid3_lib::SafeDiv($info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'],         $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']);
 215              $info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];
 216  
 217  
 218              $counter = 0;
 219              do {
 220                  $oggpageinfo = $this->ParseOggPageHeader();
 221                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
 222                  $filedata = $this->fread($oggpageinfo['page_length']);
 223                  $this->fseek($oggpageinfo['page_end_offset']);
 224  
 225                  if (substr($filedata, 0, 8) == "fisbone\x00") {
 226  
 227                      $filedataoffset = 8;
 228                      $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 229                      $filedataoffset += 4;
 230                      $info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 231                      $filedataoffset += 4;
 232                      $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 233                      $filedataoffset += 4;
 234                      $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 235                      $filedataoffset += 8;
 236                      $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 237                      $filedataoffset += 8;
 238                      $info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
 239                      $filedataoffset += 8;
 240                      $info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 241                      $filedataoffset += 4;
 242                      $info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
 243                      $filedataoffset += 1;
 244                      $info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
 245                      $filedataoffset += 3;
 246  
 247                  } elseif (substr($filedata, 1, 6) == 'theora') {
 248  
 249                      $info['video']['dataformat'] = 'theora1';
 250                      $this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']');
 251                      //break;
 252  
 253                  } elseif (substr($filedata, 1, 6) == 'vorbis') {
 254  
 255                      $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
 256  
 257                  } else {
 258                      $this->error('unexpected');
 259                      //break;
 260                  }
 261              //} while ($oggpageinfo['page_seqno'] == 0);
 262              } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
 263  
 264              $this->fseek($oggpageinfo['page_start_offset']);
 265  
 266              $this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']');
 267              //return false;
 268  
 269          } elseif (substr($filedata, 0, 5) == "\x7F".'FLAC') {
 270              // https://xiph.org/flac/ogg_mapping.html
 271  
 272              $info['audio']['dataformat']   = 'flac';
 273              $info['audio']['bitrate_mode'] = 'vbr';
 274              $info['audio']['lossless']     = true;
 275  
 276              $info['ogg']['flac']['header']['version_major']  =                         ord(substr($filedata,  5, 1));
 277              $info['ogg']['flac']['header']['version_minor']  =                         ord(substr($filedata,  6, 1));
 278              $info['ogg']['flac']['header']['header_packets'] =   getid3_lib::BigEndian2Int(substr($filedata,  7, 2)) + 1; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams."
 279              $info['ogg']['flac']['header']['magic']          =                             substr($filedata,  9, 4);
 280              if ($info['ogg']['flac']['header']['magic'] != 'fLaC') {
 281                  $this->error('Ogg-FLAC expecting "fLaC", found "'.$info['ogg']['flac']['header']['magic'].'" ('.trim(getid3_lib::PrintHexBytes($info['ogg']['flac']['header']['magic'])).')');
 282                  return false;
 283              }
 284              $info['ogg']['flac']['header']['STREAMINFO_bytes'] = getid3_lib::BigEndian2Int(substr($filedata, 13, 4));
 285              $info['flac']['STREAMINFO'] = getid3_flac::parseSTREAMINFOdata(substr($filedata, 17, 34));
 286              if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {
 287                  $info['audio']['bitrate_mode']    = 'vbr';
 288                  $info['audio']['sample_rate']     = $info['flac']['STREAMINFO']['sample_rate'];
 289                  $info['audio']['channels']        = $info['flac']['STREAMINFO']['channels'];
 290                  $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
 291                  $info['playtime_seconds']         = getid3_lib::SafeDiv($info['flac']['STREAMINFO']['samples_stream'], $info['flac']['STREAMINFO']['sample_rate']);
 292              }
 293  
 294          } else {
 295  
 296              $this->error('Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "'.substr($filedata, 0, 8).'"');
 297              unset($info['ogg']);
 298              unset($info['mime_type']);
 299              return false;
 300  
 301          }
 302  
 303          // Page 2 - Comment Header
 304          $oggpageinfo = $this->ParseOggPageHeader();
 305          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 306  
 307          switch ($info['audio']['dataformat']) {
 308              case 'vorbis':
 309                  $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 310                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
 311                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'
 312  
 313                  $this->ParseVorbisComments();
 314                  break;
 315  
 316              case 'flac':
 317                  $flac = new getid3_flac($this->getid3);
 318                  if (!$flac->parseMETAdata()) {
 319                      $this->error('Failed to parse FLAC headers');
 320                      return false;
 321                  }
 322                  unset($flac);
 323                  break;
 324  
 325              case 'speex':
 326                  $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
 327                  $this->ParseVorbisComments();
 328                  break;
 329  
 330              case 'opus':
 331                  $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 332                  $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags'
 333                  if(substr($filedata, 0, 8)  != 'OpusTags') {
 334                      $this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"');
 335                      return false;
 336                  }
 337  
 338                  $this->ParseVorbisComments();
 339                  break;
 340  
 341          }
 342  
 343          // Last Page - Number of Samples
 344          if (!getid3_lib::intValueSupported($info['avdataend'])) {
 345  
 346              $this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)');
 347  
 348          } else {
 349  
 350              $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
 351              $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
 352              if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
 353                  if (substr($LastChunkOfOgg, 13, 8) === "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {
 354                      // https://github.com/JamesHeinrich/getID3/issues/450
 355                      // "Sometimes, Opus encoders (WhatsApp voice registrations and others) add a special last header with a granule duration of 0xFFFFFFFFFFFFFF.
 356                      // This value indicates "this is the end," but must be ignored; otherwise, it makes calculations wrong."
 357                      $LastOggSpostion = strpos($LastChunkOfOgg, 'SggO', $LastOggSpostion + 1);
 358                  }
 359                  $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
 360                  $info['avdataend'] = $this->ftell();
 361                  $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
 362                  $info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
 363                  if ($info['ogg']['samples'] == 0) {
 364                      $this->error('Corrupt Ogg file: eos.number of samples == zero');
 365                      return false;
 366                  }
 367                  if (!empty($info['audio']['sample_rate'])) {
 368                      $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) * $info['audio']['sample_rate'] / $info['ogg']['samples'];
 369                  }
 370              }
 371  
 372          }
 373  
 374          if (!empty($info['ogg']['bitrate_average'])) {
 375              $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
 376          } elseif (!empty($info['ogg']['bitrate_nominal'])) {
 377              $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
 378          } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
 379              $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
 380          }
 381          if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
 382              if ($info['audio']['bitrate'] == 0) {
 383                  $this->error('Corrupt Ogg file: bitrate_audio == zero');
 384                  return false;
 385              }
 386              $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
 387          }
 388  
 389          if (isset($info['ogg']['vendor'])) {
 390              $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
 391  
 392              // Vorbis only
 393              if ($info['audio']['dataformat'] == 'vorbis') {
 394  
 395                  // Vorbis 1.0 starts with Xiph.Org
 396                  if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
 397  
 398                      if ($info['audio']['bitrate_mode'] == 'abr') {
 399  
 400                          // Set -b 128 on abr files
 401                          $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
 402  
 403                      } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
 404                          // Set -q N on vbr files
 405                          $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
 406  
 407                      }
 408                  }
 409  
 410                  if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
 411                      $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
 412                  }
 413              }
 414          }
 415  
 416          return true;
 417      }
 418  
 419      /**
 420       * @param string $filedata
 421       * @param int    $filedataoffset
 422       * @param array  $oggpageinfo
 423       *
 424       * @return bool
 425       */
 426  	public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
 427          $info = &$this->getid3->info;
 428          $info['audio']['dataformat'] = 'vorbis';
 429          $info['audio']['lossless']   = false;
 430  
 431          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 432          $filedataoffset += 1;
 433          $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
 434          $filedataoffset += 6;
 435          $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 436          $filedataoffset += 4;
 437          $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 438          $filedataoffset += 1;
 439          $info['audio']['channels']       = $info['ogg']['numberofchannels'];
 440          $info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 441          $filedataoffset += 4;
 442          if ($info['ogg']['samplerate'] == 0) {
 443              $this->error('Corrupt Ogg file: sample rate == zero');
 444              return false;
 445          }
 446          $info['audio']['sample_rate']    = $info['ogg']['samplerate'];
 447          $info['ogg']['samples']          = 0; // filled in later
 448          $info['ogg']['bitrate_average']  = 0; // filled in later
 449          $info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 450          $filedataoffset += 4;
 451          $info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 452          $filedataoffset += 4;
 453          $info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 454          $filedataoffset += 4;
 455          $info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
 456          $info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
 457          $info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
 458  
 459          $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
 460          if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
 461              unset($info['ogg']['bitrate_max']);
 462              $info['audio']['bitrate_mode'] = 'abr';
 463          }
 464          if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
 465              unset($info['ogg']['bitrate_nominal']);
 466          }
 467          if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
 468              unset($info['ogg']['bitrate_min']);
 469              $info['audio']['bitrate_mode'] = 'abr';
 470          }
 471          return true;
 472      }
 473  
 474      /**
 475       * @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03
 476       *
 477       * @param string $filedata
 478       * @param int    $filedataoffset
 479       * @param array  $oggpageinfo
 480       *
 481       * @return bool
 482       */
 483  	public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
 484          $info = &$this->getid3->info;
 485          $info['audio']['dataformat']   = 'opus';
 486          $info['mime_type']             = 'audio/ogg; codecs=opus';
 487  
 488          /** @todo find a usable way to detect abr (vbr that is padded to be abr) */
 489          $info['audio']['bitrate_mode'] = 'vbr';
 490  
 491          $info['audio']['lossless']     = false;
 492  
 493          $info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead'
 494          $filedataoffset += 8;
 495          $info['ogg']['pageheader']['opus']['version']    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
 496          $filedataoffset += 1;
 497  
 498          if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) {
 499              $this->error('Unknown opus version number (only accepting 1-15)');
 500              return false;
 501          }
 502  
 503          $info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
 504          $filedataoffset += 1;
 505  
 506          if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) {
 507              $this->error('Invalid channel count in opus header (must not be zero)');
 508              return false;
 509          }
 510  
 511          $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 512          $filedataoffset += 2;
 513  
 514          $info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
 515          $filedataoffset += 4;
 516  
 517          //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
 518          //$filedataoffset += 2;
 519  
 520          //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
 521          //$filedataoffset += 1;
 522  
 523          $info['opus']['opus_version']       = $info['ogg']['pageheader']['opus']['version'];
 524          $info['opus']['sample_rate_input']  = $info['ogg']['pageheader']['opus']['input_sample_rate'];
 525          $info['opus']['out_channel_count']  = $info['ogg']['pageheader']['opus']['out_channel_count'];
 526  
 527          $info['audio']['channels']          = $info['opus']['out_channel_count'];
 528          $info['audio']['sample_rate_input'] = $info['opus']['sample_rate_input'];
 529          $info['audio']['sample_rate']       = 48000; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html
 530          return true;
 531      }
 532  
 533      /**
 534       * @return array|false
 535       */
 536  	public function ParseOggPageHeader() {
 537          // http://xiph.org/ogg/vorbis/doc/framing.html
 538          $oggheader = array();
 539          $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
 540  
 541          $filedata = $this->fread($this->getid3->fread_buffer_size());
 542          $filedataoffset = 0;
 543          while (substr($filedata, $filedataoffset++, 4) != 'OggS') {
 544              if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
 545                  // should be found before here
 546                  return false;
 547              }
 548              if (($filedataoffset + 28) > strlen($filedata)) {
 549                  if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) {
 550                      // get some more data, unless eof, in which case fail
 551                      return false;
 552                  }
 553              }
 554          }
 555          $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
 556  
 557          $oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 558          $filedataoffset += 1;
 559          $oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 560          $filedataoffset += 1;
 561          $oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
 562          $oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
 563          $oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
 564  
 565          $oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
 566          $filedataoffset += 8;
 567          $oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 568          $filedataoffset += 4;
 569          $oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 570          $filedataoffset += 4;
 571          $oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
 572          $filedataoffset += 4;
 573          $oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 574          $filedataoffset += 1;
 575          $oggheader['page_length'] = 0;
 576          for ($i = 0; $i < $oggheader['page_segments']; $i++) {
 577              $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
 578              $filedataoffset += 1;
 579              $oggheader['page_length'] += $oggheader['segment_table'][$i];
 580          }
 581          $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
 582          $oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
 583          $this->fseek($oggheader['header_end_offset']);
 584  
 585          return $oggheader;
 586      }
 587  
 588      /**
 589       * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
 590       *
 591       * @return bool
 592       */
 593  	public function ParseVorbisComments() {
 594          $info = &$this->getid3->info;
 595  
 596          $OriginalOffset = $this->ftell();
 597          $commentdata = null;
 598          $commentdataoffset = 0;
 599          $VorbisCommentPage = 1;
 600          $CommentStartOffset = 0;
 601  
 602          switch ($info['audio']['dataformat']) {
 603              case 'vorbis':
 604              case 'speex':
 605              case 'opus':
 606                  $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block
 607                  $this->fseek($CommentStartOffset);
 608                  $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
 609                  $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
 610  
 611                  if ($info['audio']['dataformat'] == 'vorbis') {
 612                      $commentdataoffset += (strlen('vorbis') + 1);
 613                  }
 614                  else if ($info['audio']['dataformat'] == 'opus') {
 615                      $commentdataoffset += strlen('OpusTags');
 616                  }
 617  
 618                  break;
 619  
 620              case 'flac':
 621                  $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
 622                  $this->fseek($CommentStartOffset);
 623                  $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
 624                  break;
 625  
 626              default:
 627                  return false;
 628          }
 629  
 630          $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 631          $commentdataoffset += 4;
 632  
 633          $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
 634          $commentdataoffset += $VendorSize;
 635  
 636          $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 637          $commentdataoffset += 4;
 638          $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
 639  
 640          $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
 641          $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
 642          for ($i = 0; $i < $CommentsCount; $i++) {
 643  
 644              if ($i >= 10000) {
 645                  // https://github.com/owncloud/music/issues/212#issuecomment-43082336
 646                  $this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments');
 647                  break;
 648              }
 649  
 650              $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
 651  
 652              if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
 653                  if ($oggpageinfo = $this->ParseOggPageHeader()) {
 654                      $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 655  
 656                      $VorbisCommentPage++;
 657  
 658                      // First, save what we haven't read yet
 659                      $AsYetUnusedData = substr($commentdata, $commentdataoffset);
 660  
 661                      // Then take that data off the end
 662                      $commentdata     = substr($commentdata, 0, $commentdataoffset);
 663  
 664                      // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
 665                      $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 666                      $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 667  
 668                      // Finally, stick the unused data back on the end
 669                      $commentdata .= $AsYetUnusedData;
 670  
 671                      //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 672                      $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
 673                  }
 674  
 675              }
 676              $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
 677  
 678              // replace avdataoffset with position just after the last vorbiscomment
 679              $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
 680  
 681              $commentdataoffset += 4;
 682              while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
 683                  if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
 684                      $this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments');
 685                      break 2;
 686                  }
 687  
 688                  $VorbisCommentPage++;
 689  
 690                  if ($oggpageinfo = $this->ParseOggPageHeader()) {
 691                      $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
 692  
 693                      // First, save what we haven't read yet
 694                      $AsYetUnusedData = substr($commentdata, $commentdataoffset);
 695  
 696                      // Then take that data off the end
 697                      $commentdata     = substr($commentdata, 0, $commentdataoffset);
 698  
 699                      // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
 700                      $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 701                      $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
 702  
 703                      // Finally, stick the unused data back on the end
 704                      $commentdata .= $AsYetUnusedData;
 705  
 706                      //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
 707                      if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
 708                          $this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
 709                          break;
 710                      }
 711                      $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
 712                      if ($readlength <= 0) {
 713                          $this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell());
 714                          break;
 715                      }
 716                      $commentdata .= $this->fread($readlength);
 717  
 718                      //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
 719                  } else {
 720                      $this->warning('failed to ParseOggPageHeader() at offset '.$this->ftell());
 721                      break;
 722                  }
 723              }
 724              $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
 725              $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
 726              $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
 727  
 728              if (!$commentstring) {
 729  
 730                  // no comment?
 731                  $this->warning('Blank Ogg comment ['.$i.']');
 732  
 733              } elseif (strstr($commentstring, '=')) {
 734  
 735                  $commentexploded = explode('=', $commentstring, 2);
 736                  $ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
 737                  $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
 738  
 739                  if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
 740  
 741                      // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
 742                      // 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.
 743                      // http://flac.sourceforge.net/format.html#metadata_block_picture
 744                      $flac = new getid3_flac($this->getid3);
 745                      $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
 746                      $flac->parsePICTURE();
 747                      $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
 748                      unset($flac);
 749  
 750                  } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
 751  
 752                      $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
 753                      $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
 754                      /** @todo use 'coverartmime' where available */
 755                      $imageinfo = getid3_lib::GetDataImageSize($data);
 756                      if ($imageinfo === false || !isset($imageinfo['mime'])) {
 757                          $this->warning('COVERART vorbiscomment tag contains invalid image');
 758                          continue;
 759                      }
 760  
 761                      $ogg = new self($this->getid3);
 762                      $ogg->setStringMode($data);
 763                      $info['ogg']['comments']['picture'][] = array(
 764                          'image_mime'   => $imageinfo['mime'],
 765                          'datalength'   => strlen($data),
 766                          'picturetype'  => 'cover art',
 767                          'image_height' => $imageinfo['height'],
 768                          'image_width'  => $imageinfo['width'],
 769                          'data'         => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
 770                      );
 771                      unset($ogg);
 772  
 773                  } else {
 774  
 775                      $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
 776  
 777                  }
 778  
 779              } else {
 780  
 781                  $this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring);
 782  
 783              }
 784              unset($ThisFileInfo_ogg_comments_raw[$i]);
 785          }
 786          unset($ThisFileInfo_ogg_comments_raw);
 787  
 788  
 789          // Replay Gain Adjustment
 790          // http://privatewww.essex.ac.uk/~djmrob/replaygain/
 791          if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
 792              foreach ($info['ogg']['comments'] as $index => $commentvalue) {
 793                  switch ($index) {
 794                      case 'rg_audiophile':
 795                      case 'replaygain_album_gain':
 796                          $info['replay_gain']['album']['adjustment'] = (float) $commentvalue[0];
 797                          unset($info['ogg']['comments'][$index]);
 798                          break;
 799  
 800                      case 'rg_radio':
 801                      case 'replaygain_track_gain':
 802                          $info['replay_gain']['track']['adjustment'] = (float) $commentvalue[0];
 803                          unset($info['ogg']['comments'][$index]);
 804                          break;
 805  
 806                      case 'replaygain_album_peak':
 807                          $info['replay_gain']['album']['peak'] = (float) $commentvalue[0];
 808                          unset($info['ogg']['comments'][$index]);
 809                          break;
 810  
 811                      case 'rg_peak':
 812                      case 'replaygain_track_peak':
 813                          $info['replay_gain']['track']['peak'] = (float) $commentvalue[0];
 814                          unset($info['ogg']['comments'][$index]);
 815                          break;
 816  
 817                      case 'replaygain_reference_loudness':
 818                          $info['replay_gain']['reference_volume'] = (float) $commentvalue[0];
 819                          unset($info['ogg']['comments'][$index]);
 820                          break;
 821  
 822                      default:
 823                          // do nothing
 824                          break;
 825                  }
 826              }
 827          }
 828  
 829          $this->fseek($OriginalOffset);
 830  
 831          return true;
 832      }
 833  
 834      /**
 835       * @param int $mode
 836       *
 837       * @return string|null
 838       */
 839  	public static function SpeexBandModeLookup($mode) {
 840          static $SpeexBandModeLookup = array();
 841          if (empty($SpeexBandModeLookup)) {
 842              $SpeexBandModeLookup[0] = 'narrow';
 843              $SpeexBandModeLookup[1] = 'wide';
 844              $SpeexBandModeLookup[2] = 'ultra-wide';
 845          }
 846          return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
 847      }
 848  
 849      /**
 850       * @param array $OggInfoArray
 851       * @param int   $SegmentNumber
 852       *
 853       * @return int
 854       */
 855  	public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
 856          $segmentlength = 0;
 857          for ($i = 0; $i < $SegmentNumber; $i++) {
 858              $segmentlength = 0;
 859              foreach ($OggInfoArray['segment_table'] as $key => $value) {
 860                  $segmentlength += $value;
 861                  if ($value < 255) {
 862                      break;
 863                  }
 864              }
 865          }
 866          return $segmentlength;
 867      }
 868  
 869      /**
 870       * @param int $nominal_bitrate
 871       *
 872       * @return float
 873       */
 874  	public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
 875  
 876          // decrease precision
 877          $nominal_bitrate = $nominal_bitrate / 1000;
 878  
 879          if ($nominal_bitrate < 128) {
 880              // q-1 to q4
 881              $qval = ($nominal_bitrate - 64) / 16;
 882          } elseif ($nominal_bitrate < 256) {
 883              // q4 to q8
 884              $qval = $nominal_bitrate / 32;
 885          } elseif ($nominal_bitrate < 320) {
 886              // q8 to q9
 887              $qval = ($nominal_bitrate + 256) / 64;
 888          } else {
 889              // q9 to q10
 890              $qval = ($nominal_bitrate + 1300) / 180;
 891          }
 892          //return $qval; // 5.031324
 893          //return intval($qval); // 5
 894          return round($qval, 1); // 5 or 4.9
 895      }
 896  
 897      /**
 898       * @param int $colorspace_id
 899       *
 900       * @return string|null
 901       */
 902  	public static function TheoraColorSpace($colorspace_id) {
 903          // http://www.theora.org/doc/Theora.pdf (table 6.3)
 904          static $TheoraColorSpaceLookup = array();
 905          if (empty($TheoraColorSpaceLookup)) {
 906              $TheoraColorSpaceLookup[0] = 'Undefined';
 907              $TheoraColorSpaceLookup[1] = 'Rec. 470M';
 908              $TheoraColorSpaceLookup[2] = 'Rec. 470BG';
 909              $TheoraColorSpaceLookup[3] = 'Reserved';
 910          }
 911          return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null);
 912      }
 913  
 914      /**
 915       * @param int $pixelformat_id
 916       *
 917       * @return string|null
 918       */
 919  	public static function TheoraPixelFormat($pixelformat_id) {
 920          // http://www.theora.org/doc/Theora.pdf (table 6.4)
 921          static $TheoraPixelFormatLookup = array();
 922          if (empty($TheoraPixelFormatLookup)) {
 923              $TheoraPixelFormatLookup[0] = '4:2:0';
 924              $TheoraPixelFormatLookup[1] = 'Reserved';
 925              $TheoraPixelFormatLookup[2] = '4:2:2';
 926              $TheoraPixelFormatLookup[3] = '4:4:4';
 927          }
 928          return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null);
 929      }
 930  
 931  }


Generated : Sat Jun 13 09:38:55 2026 Cross-referenced by PHPXref