wp/wp-includes/ID3/module.audio.ogg.php
changeset 0 d970ebf37754
child 5 5e2f62d02dcd
equal deleted inserted replaced
-1:000000000000 0:d970ebf37754
       
     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 /////////////////////////////////////////////////////////////////
       
     7 // See readme.txt for more details                             //
       
     8 /////////////////////////////////////////////////////////////////
       
     9 //                                                             //
       
    10 // module.audio.ogg.php                                        //
       
    11 // module for analyzing Ogg Vorbis, OggFLAC and Speex files    //
       
    12 // dependencies: module.audio.flac.php                         //
       
    13 //                                                            ///
       
    14 /////////////////////////////////////////////////////////////////
       
    15 
       
    16 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
       
    17 
       
    18 class getid3_ogg extends getid3_handler
       
    19 {
       
    20 	// http://xiph.org/vorbis/doc/Vorbis_I_spec.html
       
    21 	public function Analyze() {
       
    22 		$info = &$this->getid3->info;
       
    23 
       
    24 		$info['fileformat'] = 'ogg';
       
    25 
       
    26 		// Warn about illegal tags - only vorbiscomments are allowed
       
    27 		if (isset($info['id3v2'])) {
       
    28 			$info['warning'][] = 'Illegal ID3v2 tag present.';
       
    29 		}
       
    30 		if (isset($info['id3v1'])) {
       
    31 			$info['warning'][] = 'Illegal ID3v1 tag present.';
       
    32 		}
       
    33 		if (isset($info['ape'])) {
       
    34 			$info['warning'][] = 'Illegal APE tag present.';
       
    35 		}
       
    36 
       
    37 
       
    38 		// Page 1 - Stream Header
       
    39 
       
    40 		$this->fseek($info['avdataoffset']);
       
    41 
       
    42 		$oggpageinfo = $this->ParseOggPageHeader();
       
    43 		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
       
    44 
       
    45 		if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
       
    46 			$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?)';
       
    47 			unset($info['fileformat']);
       
    48 			unset($info['ogg']);
       
    49 			return false;
       
    50 		}
       
    51 
       
    52 		$filedata = $this->fread($oggpageinfo['page_length']);
       
    53 		$filedataoffset = 0;
       
    54 
       
    55 		if (substr($filedata, 0, 4) == 'fLaC') {
       
    56 
       
    57 			$info['audio']['dataformat']   = 'flac';
       
    58 			$info['audio']['bitrate_mode'] = 'vbr';
       
    59 			$info['audio']['lossless']     = true;
       
    60 
       
    61 		} elseif (substr($filedata, 1, 6) == 'vorbis') {
       
    62 
       
    63 			$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
       
    64 
       
    65 		} elseif (substr($filedata, 0, 8) == 'Speex   ') {
       
    66 
       
    67 			// http://www.speex.org/manual/node10.html
       
    68 
       
    69 			$info['audio']['dataformat']   = 'speex';
       
    70 			$info['mime_type']             = 'audio/speex';
       
    71 			$info['audio']['bitrate_mode'] = 'abr';
       
    72 			$info['audio']['lossless']     = false;
       
    73 
       
    74 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string']           =                              substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex   '
       
    75 			$filedataoffset += 8;
       
    76 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']          =                              substr($filedata, $filedataoffset, 20);
       
    77 			$filedataoffset += 20;
       
    78 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    79 			$filedataoffset += 4;
       
    80 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    81 			$filedataoffset += 4;
       
    82 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    83 			$filedataoffset += 4;
       
    84 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']                   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    85 			$filedataoffset += 4;
       
    86 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    87 			$filedataoffset += 4;
       
    88 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    89 			$filedataoffset += 4;
       
    90 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    91 			$filedataoffset += 4;
       
    92 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    93 			$filedataoffset += 4;
       
    94 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']                    = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    95 			$filedataoffset += 4;
       
    96 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    97 			$filedataoffset += 4;
       
    98 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers']          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
    99 			$filedataoffset += 4;
       
   100 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   101 			$filedataoffset += 4;
       
   102 			$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2']              = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   103 			$filedataoffset += 4;
       
   104 
       
   105 			$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
       
   106 			$info['speex']['sample_rate']   = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
       
   107 			$info['speex']['channels']      = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
       
   108 			$info['speex']['vbr']           = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
       
   109 			$info['speex']['band_type']     = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
       
   110 
       
   111 			$info['audio']['sample_rate']   = $info['speex']['sample_rate'];
       
   112 			$info['audio']['channels']      = $info['speex']['channels'];
       
   113 			if ($info['speex']['vbr']) {
       
   114 				$info['audio']['bitrate_mode'] = 'vbr';
       
   115 			}
       
   116 
       
   117 
       
   118 		} elseif (substr($filedata, 0, 8) == "fishead\x00") {
       
   119 
       
   120 			// Ogg Skeleton version 3.0 Format Specification
       
   121 			// http://xiph.org/ogg/doc/skeleton.html
       
   122 			$filedataoffset += 8;
       
   123 			$info['ogg']['skeleton']['fishead']['raw']['version_major']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
       
   124 			$filedataoffset += 2;
       
   125 			$info['ogg']['skeleton']['fishead']['raw']['version_minor']                = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  2));
       
   126 			$filedataoffset += 2;
       
   127 			$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
       
   128 			$filedataoffset += 8;
       
   129 			$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
       
   130 			$filedataoffset += 8;
       
   131 			$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
       
   132 			$filedataoffset += 8;
       
   133 			$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
       
   134 			$filedataoffset += 8;
       
   135 			$info['ogg']['skeleton']['fishead']['raw']['utc']                          = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
       
   136 			$filedataoffset += 20;
       
   137 
       
   138 			$info['ogg']['skeleton']['fishead']['version']          = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
       
   139 			$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
       
   140 			$info['ogg']['skeleton']['fishead']['basetime']         = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator']         / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
       
   141 			$info['ogg']['skeleton']['fishead']['utc']              = $info['ogg']['skeleton']['fishead']['raw']['utc'];
       
   142 
       
   143 
       
   144 			$counter = 0;
       
   145 			do {
       
   146 				$oggpageinfo = $this->ParseOggPageHeader();
       
   147 				$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
       
   148 				$filedata = $this->fread($oggpageinfo['page_length']);
       
   149 				$this->fseek($oggpageinfo['page_end_offset']);
       
   150 
       
   151 				if (substr($filedata, 0, 8) == "fisbone\x00") {
       
   152 
       
   153 					$filedataoffset = 8;
       
   154 					$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
       
   155 					$filedataoffset += 4;
       
   156 					$info['ogg']['skeleton']['fisbone']['raw']['serial_number']           = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
       
   157 					$filedataoffset += 4;
       
   158 					$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
       
   159 					$filedataoffset += 4;
       
   160 					$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
       
   161 					$filedataoffset += 8;
       
   162 					$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
       
   163 					$filedataoffset += 8;
       
   164 					$info['ogg']['skeleton']['fisbone']['raw']['basegranule']             = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  8));
       
   165 					$filedataoffset += 8;
       
   166 					$info['ogg']['skeleton']['fisbone']['raw']['preroll']                 = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  4));
       
   167 					$filedataoffset += 4;
       
   168 					$info['ogg']['skeleton']['fisbone']['raw']['granuleshift']            = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset,  1));
       
   169 					$filedataoffset += 1;
       
   170 					$info['ogg']['skeleton']['fisbone']['raw']['padding']                 =                              substr($filedata, $filedataoffset,  3);
       
   171 					$filedataoffset += 3;
       
   172 
       
   173 				} elseif (substr($filedata, 1, 6) == 'theora') {
       
   174 
       
   175 					$info['video']['dataformat'] = 'theora';
       
   176 					$info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
       
   177 					//break;
       
   178 
       
   179 				} elseif (substr($filedata, 1, 6) == 'vorbis') {
       
   180 
       
   181 					$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
       
   182 
       
   183 				} else {
       
   184 					$info['error'][] = 'unexpected';
       
   185 					//break;
       
   186 				}
       
   187 			//} while ($oggpageinfo['page_seqno'] == 0);
       
   188 			} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
       
   189 
       
   190 			$this->fseek($oggpageinfo['page_start_offset']);
       
   191 
       
   192 			$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
       
   193 			//return false;
       
   194 
       
   195 		} else {
       
   196 
       
   197 			$info['error'][] = 'Expecting either "Speex   " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
       
   198 			unset($info['ogg']);
       
   199 			unset($info['mime_type']);
       
   200 			return false;
       
   201 
       
   202 		}
       
   203 
       
   204 		// Page 2 - Comment Header
       
   205 		$oggpageinfo = $this->ParseOggPageHeader();
       
   206 		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
       
   207 
       
   208 		switch ($info['audio']['dataformat']) {
       
   209 			case 'vorbis':
       
   210 				$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
       
   211 				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
       
   212 				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] =                              substr($filedata, 1, 6); // hard-coded to 'vorbis'
       
   213 
       
   214 				$this->ParseVorbisComments();
       
   215 				break;
       
   216 
       
   217 			case 'flac':
       
   218 				$flac = new getid3_flac($this->getid3);
       
   219 				if (!$flac->parseMETAdata()) {
       
   220 					$info['error'][] = 'Failed to parse FLAC headers';
       
   221 					return false;
       
   222 				}
       
   223 				unset($flac);
       
   224 				break;
       
   225 
       
   226 			case 'speex':
       
   227 				$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
       
   228 				$this->ParseVorbisComments();
       
   229 				break;
       
   230 		}
       
   231 
       
   232 
       
   233 		// Last Page - Number of Samples
       
   234 		if (!getid3_lib::intValueSupported($info['avdataend'])) {
       
   235 
       
   236 			$info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
       
   237 
       
   238 		} else {
       
   239 
       
   240 			$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
       
   241 			$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
       
   242 			if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
       
   243 				$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
       
   244 				$info['avdataend'] = $this->ftell();
       
   245 				$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
       
   246 				$info['ogg']['samples']   = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
       
   247 				if ($info['ogg']['samples'] == 0) {
       
   248 					$info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
       
   249 					return false;
       
   250 				}
       
   251 				if (!empty($info['audio']['sample_rate'])) {
       
   252 					$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
       
   253 				}
       
   254 			}
       
   255 
       
   256 		}
       
   257 
       
   258 		if (!empty($info['ogg']['bitrate_average'])) {
       
   259 			$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
       
   260 		} elseif (!empty($info['ogg']['bitrate_nominal'])) {
       
   261 			$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
       
   262 		} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
       
   263 			$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
       
   264 		}
       
   265 		if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
       
   266 			if ($info['audio']['bitrate'] == 0) {
       
   267 				$info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
       
   268 				return false;
       
   269 			}
       
   270 			$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
       
   271 		}
       
   272 
       
   273 		if (isset($info['ogg']['vendor'])) {
       
   274 			$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
       
   275 
       
   276 			// Vorbis only
       
   277 			if ($info['audio']['dataformat'] == 'vorbis') {
       
   278 
       
   279 				// Vorbis 1.0 starts with Xiph.Org
       
   280 				if  (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
       
   281 
       
   282 					if ($info['audio']['bitrate_mode'] == 'abr') {
       
   283 
       
   284 						// Set -b 128 on abr files
       
   285 						$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
       
   286 
       
   287 					} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
       
   288 						// Set -q N on vbr files
       
   289 						$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
       
   290 
       
   291 					}
       
   292 				}
       
   293 
       
   294 				if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
       
   295 					$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
       
   296 				}
       
   297 			}
       
   298 		}
       
   299 
       
   300 		return true;
       
   301 	}
       
   302 
       
   303 	public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
       
   304 		$info = &$this->getid3->info;
       
   305 		$info['audio']['dataformat'] = 'vorbis';
       
   306 		$info['audio']['lossless']   = false;
       
   307 
       
   308 		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
       
   309 		$filedataoffset += 1;
       
   310 		$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
       
   311 		$filedataoffset += 6;
       
   312 		$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   313 		$filedataoffset += 4;
       
   314 		$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
       
   315 		$filedataoffset += 1;
       
   316 		$info['audio']['channels']       = $info['ogg']['numberofchannels'];
       
   317 		$info['ogg']['samplerate']       = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   318 		$filedataoffset += 4;
       
   319 		if ($info['ogg']['samplerate'] == 0) {
       
   320 			$info['error'][] = 'Corrupt Ogg file: sample rate == zero';
       
   321 			return false;
       
   322 		}
       
   323 		$info['audio']['sample_rate']    = $info['ogg']['samplerate'];
       
   324 		$info['ogg']['samples']          = 0; // filled in later
       
   325 		$info['ogg']['bitrate_average']  = 0; // filled in later
       
   326 		$info['ogg']['bitrate_max']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   327 		$filedataoffset += 4;
       
   328 		$info['ogg']['bitrate_nominal']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   329 		$filedataoffset += 4;
       
   330 		$info['ogg']['bitrate_min']      = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   331 		$filedataoffset += 4;
       
   332 		$info['ogg']['blocksize_small']  = pow(2,  getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
       
   333 		$info['ogg']['blocksize_large']  = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
       
   334 		$info['ogg']['stop_bit']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
       
   335 
       
   336 		$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
       
   337 		if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
       
   338 			unset($info['ogg']['bitrate_max']);
       
   339 			$info['audio']['bitrate_mode'] = 'abr';
       
   340 		}
       
   341 		if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
       
   342 			unset($info['ogg']['bitrate_nominal']);
       
   343 		}
       
   344 		if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
       
   345 			unset($info['ogg']['bitrate_min']);
       
   346 			$info['audio']['bitrate_mode'] = 'abr';
       
   347 		}
       
   348 		return true;
       
   349 	}
       
   350 
       
   351 	public function ParseOggPageHeader() {
       
   352 		// http://xiph.org/ogg/vorbis/doc/framing.html
       
   353 		$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
       
   354 
       
   355 		$filedata = $this->fread($this->getid3->fread_buffer_size());
       
   356 		$filedataoffset = 0;
       
   357 		while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
       
   358 			if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
       
   359 				// should be found before here
       
   360 				return false;
       
   361 			}
       
   362 			if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
       
   363 				if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
       
   364 					// get some more data, unless eof, in which case fail
       
   365 					return false;
       
   366 				}
       
   367 			}
       
   368 		}
       
   369 		$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
       
   370 
       
   371 		$oggheader['stream_structver']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
       
   372 		$filedataoffset += 1;
       
   373 		$oggheader['flags_raw']         = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
       
   374 		$filedataoffset += 1;
       
   375 		$oggheader['flags']['fresh']    = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
       
   376 		$oggheader['flags']['bos']      = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
       
   377 		$oggheader['flags']['eos']      = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
       
   378 
       
   379 		$oggheader['pcm_abs_position']  = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
       
   380 		$filedataoffset += 8;
       
   381 		$oggheader['stream_serialno']   = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   382 		$filedataoffset += 4;
       
   383 		$oggheader['page_seqno']        = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   384 		$filedataoffset += 4;
       
   385 		$oggheader['page_checksum']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
       
   386 		$filedataoffset += 4;
       
   387 		$oggheader['page_segments']     = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
       
   388 		$filedataoffset += 1;
       
   389 		$oggheader['page_length'] = 0;
       
   390 		for ($i = 0; $i < $oggheader['page_segments']; $i++) {
       
   391 			$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
       
   392 			$filedataoffset += 1;
       
   393 			$oggheader['page_length'] += $oggheader['segment_table'][$i];
       
   394 		}
       
   395 		$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
       
   396 		$oggheader['page_end_offset']   = $oggheader['header_end_offset'] + $oggheader['page_length'];
       
   397 		$this->fseek($oggheader['header_end_offset']);
       
   398 
       
   399 		return $oggheader;
       
   400 	}
       
   401 
       
   402     // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
       
   403 	public function ParseVorbisComments() {
       
   404 		$info = &$this->getid3->info;
       
   405 
       
   406 		$OriginalOffset = $this->ftell();
       
   407 		$commentdataoffset = 0;
       
   408 		$VorbisCommentPage = 1;
       
   409 
       
   410 		switch ($info['audio']['dataformat']) {
       
   411 			case 'vorbis':
       
   412 			case 'speex':
       
   413 				$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset'];  // Second Ogg page, after header block
       
   414 				$this->fseek($CommentStartOffset);
       
   415 				$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
       
   416 				$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
       
   417 
       
   418 				if ($info['audio']['dataformat'] == 'vorbis') {
       
   419 					$commentdataoffset += (strlen('vorbis') + 1);
       
   420 				}
       
   421 				break;
       
   422 
       
   423 			case 'flac':
       
   424 				$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
       
   425 				$this->fseek($CommentStartOffset);
       
   426 				$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
       
   427 				break;
       
   428 
       
   429 			default:
       
   430 				return false;
       
   431 		}
       
   432 
       
   433 		$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
       
   434 		$commentdataoffset += 4;
       
   435 
       
   436 		$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
       
   437 		$commentdataoffset += $VendorSize;
       
   438 
       
   439 		$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
       
   440 		$commentdataoffset += 4;
       
   441 		$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
       
   442 
       
   443 		$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
       
   444 		$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
       
   445 		for ($i = 0; $i < $CommentsCount; $i++) {
       
   446 
       
   447 			$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
       
   448 
       
   449 			if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
       
   450 				if ($oggpageinfo = $this->ParseOggPageHeader()) {
       
   451 					$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
       
   452 
       
   453 					$VorbisCommentPage++;
       
   454 
       
   455 					// First, save what we haven't read yet
       
   456 					$AsYetUnusedData = substr($commentdata, $commentdataoffset);
       
   457 
       
   458 					// Then take that data off the end
       
   459 					$commentdata     = substr($commentdata, 0, $commentdataoffset);
       
   460 
       
   461 					// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
       
   462 					$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
       
   463 					$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
       
   464 
       
   465 					// Finally, stick the unused data back on the end
       
   466 					$commentdata .= $AsYetUnusedData;
       
   467 
       
   468 					//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
       
   469 					$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
       
   470 				}
       
   471 
       
   472 			}
       
   473 			$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
       
   474 
       
   475 			// replace avdataoffset with position just after the last vorbiscomment
       
   476 			$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
       
   477 
       
   478 			$commentdataoffset += 4;
       
   479 			while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
       
   480 				if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
       
   481 					$info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
       
   482 					break 2;
       
   483 				}
       
   484 
       
   485 				$VorbisCommentPage++;
       
   486 
       
   487 				$oggpageinfo = $this->ParseOggPageHeader();
       
   488 				$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
       
   489 
       
   490 				// First, save what we haven't read yet
       
   491 				$AsYetUnusedData = substr($commentdata, $commentdataoffset);
       
   492 
       
   493 				// Then take that data off the end
       
   494 				$commentdata     = substr($commentdata, 0, $commentdataoffset);
       
   495 
       
   496 				// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
       
   497 				$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
       
   498 				$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
       
   499 
       
   500 				// Finally, stick the unused data back on the end
       
   501 				$commentdata .= $AsYetUnusedData;
       
   502 
       
   503 				//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
       
   504 				if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
       
   505 					$info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
       
   506 					break;
       
   507 				}
       
   508 				$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
       
   509 				if ($readlength <= 0) {
       
   510 					$info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
       
   511 					break;
       
   512 				}
       
   513 				$commentdata .= $this->fread($readlength);
       
   514 
       
   515 				//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
       
   516 			}
       
   517 			$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
       
   518 			$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
       
   519 			$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
       
   520 
       
   521 			if (!$commentstring) {
       
   522 
       
   523 				// no comment?
       
   524 				$info['warning'][] = 'Blank Ogg comment ['.$i.']';
       
   525 
       
   526 			} elseif (strstr($commentstring, '=')) {
       
   527 
       
   528 				$commentexploded = explode('=', $commentstring, 2);
       
   529 				$ThisFileInfo_ogg_comments_raw[$i]['key']   = strtoupper($commentexploded[0]);
       
   530 				$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
       
   531 
       
   532 				if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
       
   533 
       
   534 					// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
       
   535 					// 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.
       
   536 					// http://flac.sourceforge.net/format.html#metadata_block_picture
       
   537 					$flac = new getid3_flac($this->getid3);
       
   538 					$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
       
   539 					$flac->parsePICTURE();
       
   540 					$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
       
   541 					unset($flac);
       
   542 
       
   543 				} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
       
   544 
       
   545 					$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
       
   546 					$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
       
   547 					/** @todo use 'coverartmime' where available */
       
   548 					$imageinfo = getid3_lib::GetDataImageSize($data);
       
   549 					if ($imageinfo === false || !isset($imageinfo['mime'])) {
       
   550 						$this->warning('COVERART vorbiscomment tag contains invalid image');
       
   551 						continue;
       
   552 					}
       
   553 
       
   554 					$ogg = new self($this->getid3);
       
   555 					$ogg->setStringMode($data);
       
   556 					$info['ogg']['comments']['picture'][] = array(
       
   557 						'image_mime' => $imageinfo['mime'],
       
   558 						'data'       => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
       
   559 					);
       
   560 					unset($ogg);
       
   561 
       
   562 				} else {
       
   563 
       
   564 					$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
       
   565 
       
   566 				}
       
   567 
       
   568 			} else {
       
   569 
       
   570 				$info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
       
   571 
       
   572 			}
       
   573 			unset($ThisFileInfo_ogg_comments_raw[$i]);
       
   574 		}
       
   575 		unset($ThisFileInfo_ogg_comments_raw);
       
   576 
       
   577 
       
   578 		// Replay Gain Adjustment
       
   579 		// http://privatewww.essex.ac.uk/~djmrob/replaygain/
       
   580 		if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
       
   581 			foreach ($info['ogg']['comments'] as $index => $commentvalue) {
       
   582 				switch ($index) {
       
   583 					case 'rg_audiophile':
       
   584 					case 'replaygain_album_gain':
       
   585 						$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
       
   586 						unset($info['ogg']['comments'][$index]);
       
   587 						break;
       
   588 
       
   589 					case 'rg_radio':
       
   590 					case 'replaygain_track_gain':
       
   591 						$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
       
   592 						unset($info['ogg']['comments'][$index]);
       
   593 						break;
       
   594 
       
   595 					case 'replaygain_album_peak':
       
   596 						$info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
       
   597 						unset($info['ogg']['comments'][$index]);
       
   598 						break;
       
   599 
       
   600 					case 'rg_peak':
       
   601 					case 'replaygain_track_peak':
       
   602 						$info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
       
   603 						unset($info['ogg']['comments'][$index]);
       
   604 						break;
       
   605 
       
   606 					case 'replaygain_reference_loudness':
       
   607 						$info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
       
   608 						unset($info['ogg']['comments'][$index]);
       
   609 						break;
       
   610 
       
   611 					default:
       
   612 						// do nothing
       
   613 						break;
       
   614 				}
       
   615 			}
       
   616 		}
       
   617 
       
   618 		$this->fseek($OriginalOffset);
       
   619 
       
   620 		return true;
       
   621 	}
       
   622 
       
   623 	public static function SpeexBandModeLookup($mode) {
       
   624 		static $SpeexBandModeLookup = array();
       
   625 		if (empty($SpeexBandModeLookup)) {
       
   626 			$SpeexBandModeLookup[0] = 'narrow';
       
   627 			$SpeexBandModeLookup[1] = 'wide';
       
   628 			$SpeexBandModeLookup[2] = 'ultra-wide';
       
   629 		}
       
   630 		return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
       
   631 	}
       
   632 
       
   633 
       
   634 	public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
       
   635 		for ($i = 0; $i < $SegmentNumber; $i++) {
       
   636 			$segmentlength = 0;
       
   637 			foreach ($OggInfoArray['segment_table'] as $key => $value) {
       
   638 				$segmentlength += $value;
       
   639 				if ($value < 255) {
       
   640 					break;
       
   641 				}
       
   642 			}
       
   643 		}
       
   644 		return $segmentlength;
       
   645 	}
       
   646 
       
   647 
       
   648 	public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
       
   649 
       
   650 		// decrease precision
       
   651 		$nominal_bitrate = $nominal_bitrate / 1000;
       
   652 
       
   653 		if ($nominal_bitrate < 128) {
       
   654 			// q-1 to q4
       
   655 			$qval = ($nominal_bitrate - 64) / 16;
       
   656 		} elseif ($nominal_bitrate < 256) {
       
   657 			// q4 to q8
       
   658 			$qval = $nominal_bitrate / 32;
       
   659 		} elseif ($nominal_bitrate < 320) {
       
   660 			// q8 to q9
       
   661 			$qval = ($nominal_bitrate + 256) / 64;
       
   662 		} else {
       
   663 			// q9 to q10
       
   664 			$qval = ($nominal_bitrate + 1300) / 180;
       
   665 		}
       
   666 		//return $qval; // 5.031324
       
   667 		//return intval($qval); // 5
       
   668 		return round($qval, 1); // 5 or 4.9
       
   669 	}
       
   670 
       
   671 }