[ Index ] |
WordPress Cross Reference |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Mar 25 01:41:18 2014 | WordPress honlapkészítés: online1.hu |