File indexing completed on 2024-12-22 05:33:13
0001 <?php 0002 ///////////////////////////////////////////////////////////////// 0003 /// getID3() by James Heinrich <info@getid3.org> // 0004 // available at http://getid3.sourceforge.net // 0005 // or http://www.getid3.org // 0006 // also https://github.com/JamesHeinrich/getID3 // 0007 ///////////////////////////////////////////////////////////////// 0008 // See readme.txt for more details // 0009 ///////////////////////////////////////////////////////////////// 0010 // // 0011 // module.audio.ogg.php // 0012 // module for analyzing Ogg Vorbis, OggFLAC and Speex files // 0013 // dependencies: module.audio.flac.php // 0014 // /// 0015 ///////////////////////////////////////////////////////////////// 0016 0017 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true); 0018 0019 class getid3_ogg extends getid3_handler 0020 { 0021 // http://xiph.org/vorbis/doc/Vorbis_I_spec.html 0022 public function Analyze() { 0023 $info = &$this->getid3->info; 0024 0025 $info['fileformat'] = 'ogg'; 0026 0027 // Warn about illegal tags - only vorbiscomments are allowed 0028 if (isset($info['id3v2'])) { 0029 $info['warning'][] = 'Illegal ID3v2 tag present.'; 0030 } 0031 if (isset($info['id3v1'])) { 0032 $info['warning'][] = 'Illegal ID3v1 tag present.'; 0033 } 0034 if (isset($info['ape'])) { 0035 $info['warning'][] = 'Illegal APE tag present.'; 0036 } 0037 0038 0039 // Page 1 - Stream Header 0040 0041 $this->fseek($info['avdataoffset']); 0042 0043 $oggpageinfo = $this->ParseOggPageHeader(); 0044 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 0045 0046 if ($this->ftell() >= $this->getid3->fread_buffer_size()) { 0047 $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?)'; 0048 unset($info['fileformat']); 0049 unset($info['ogg']); 0050 return false; 0051 } 0052 0053 $filedata = $this->fread($oggpageinfo['page_length']); 0054 $filedataoffset = 0; 0055 0056 if (substr($filedata, 0, 4) == 'fLaC') { 0057 0058 $info['audio']['dataformat'] = 'flac'; 0059 $info['audio']['bitrate_mode'] = 'vbr'; 0060 $info['audio']['lossless'] = true; 0061 0062 } elseif (substr($filedata, 1, 6) == 'vorbis') { 0063 0064 $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); 0065 0066 } elseif (substr($filedata, 0, 8) == 'Speex ') { 0067 0068 // http://www.speex.org/manual/node10.html 0069 0070 $info['audio']['dataformat'] = 'speex'; 0071 $info['mime_type'] = 'audio/speex'; 0072 $info['audio']['bitrate_mode'] = 'abr'; 0073 $info['audio']['lossless'] = false; 0074 0075 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' 0076 $filedataoffset += 8; 0077 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); 0078 $filedataoffset += 20; 0079 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0080 $filedataoffset += 4; 0081 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0082 $filedataoffset += 4; 0083 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0084 $filedataoffset += 4; 0085 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0086 $filedataoffset += 4; 0087 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0088 $filedataoffset += 4; 0089 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0090 $filedataoffset += 4; 0091 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0092 $filedataoffset += 4; 0093 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0094 $filedataoffset += 4; 0095 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0096 $filedataoffset += 4; 0097 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0098 $filedataoffset += 4; 0099 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0100 $filedataoffset += 4; 0101 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0102 $filedataoffset += 4; 0103 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0104 $filedataoffset += 4; 0105 0106 $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); 0107 $info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; 0108 $info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; 0109 $info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; 0110 $info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); 0111 0112 $info['audio']['sample_rate'] = $info['speex']['sample_rate']; 0113 $info['audio']['channels'] = $info['speex']['channels']; 0114 if ($info['speex']['vbr']) { 0115 $info['audio']['bitrate_mode'] = 'vbr'; 0116 } 0117 0118 } elseif (substr($filedata, 0, 7) == "\x80".'theora') { 0119 0120 // http://www.theora.org/doc/Theora.pdf (section 6.2) 0121 0122 $info['ogg']['pageheader']['theora']['theora_magic'] = substr($filedata, $filedataoffset, 7); // hard-coded to "\x80.'theora' 0123 $filedataoffset += 7; 0124 $info['ogg']['pageheader']['theora']['version_major'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 0125 $filedataoffset += 1; 0126 $info['ogg']['pageheader']['theora']['version_minor'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 0127 $filedataoffset += 1; 0128 $info['ogg']['pageheader']['theora']['version_revision'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 0129 $filedataoffset += 1; 0130 $info['ogg']['pageheader']['theora']['frame_width_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); 0131 $filedataoffset += 2; 0132 $info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); 0133 $filedataoffset += 2; 0134 $info['ogg']['pageheader']['theora']['resolution_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 0135 $filedataoffset += 3; 0136 $info['ogg']['pageheader']['theora']['resolution_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 0137 $filedataoffset += 3; 0138 $info['ogg']['pageheader']['theora']['picture_offset_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 0139 $filedataoffset += 1; 0140 $info['ogg']['pageheader']['theora']['picture_offset_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 0141 $filedataoffset += 1; 0142 $info['ogg']['pageheader']['theora']['frame_rate_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); 0143 $filedataoffset += 4; 0144 $info['ogg']['pageheader']['theora']['frame_rate_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); 0145 $filedataoffset += 4; 0146 $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 0147 $filedataoffset += 3; 0148 $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 0149 $filedataoffset += 3; 0150 $info['ogg']['pageheader']['theora']['color_space_id'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 0151 $filedataoffset += 1; 0152 $info['ogg']['pageheader']['theora']['nominal_bitrate'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 0153 $filedataoffset += 3; 0154 $info['ogg']['pageheader']['theora']['flags'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); 0155 $filedataoffset += 2; 0156 0157 $info['ogg']['pageheader']['theora']['quality'] = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10; 0158 $info['ogg']['pageheader']['theora']['kfg_shift'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >> 5; 0159 $info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >> 3; 0160 $info['ogg']['pageheader']['theora']['reserved'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >> 0; // should be 0 0161 $info['ogg']['pageheader']['theora']['color_space'] = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']); 0162 $info['ogg']['pageheader']['theora']['pixel_format'] = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']); 0163 0164 $info['video']['dataformat'] = 'theora'; 0165 $info['mime_type'] = 'video/ogg'; 0166 //$info['audio']['bitrate_mode'] = 'abr'; 0167 //$info['audio']['lossless'] = false; 0168 $info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x']; 0169 $info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y']; 0170 if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) { 0171 $info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator']; 0172 } 0173 if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) { 0174 $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator']; 0175 } 0176 $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable'; 0177 0178 0179 } elseif (substr($filedata, 0, 8) == "fishead\x00") { 0180 0181 // Ogg Skeleton version 3.0 Format Specification 0182 // http://xiph.org/ogg/doc/skeleton.html 0183 $filedataoffset += 8; 0184 $info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 0185 $filedataoffset += 2; 0186 $info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 0187 $filedataoffset += 2; 0188 $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0189 $filedataoffset += 8; 0190 $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0191 $filedataoffset += 8; 0192 $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0193 $filedataoffset += 8; 0194 $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0195 $filedataoffset += 8; 0196 $info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20)); 0197 $filedataoffset += 20; 0198 0199 $info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor']; 0200 $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']; 0201 $info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']; 0202 $info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc']; 0203 0204 0205 $counter = 0; 0206 do { 0207 $oggpageinfo = $this->ParseOggPageHeader(); 0208 $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo; 0209 $filedata = $this->fread($oggpageinfo['page_length']); 0210 $this->fseek($oggpageinfo['page_end_offset']); 0211 0212 if (substr($filedata, 0, 8) == "fisbone\x00") { 0213 0214 $filedataoffset = 8; 0215 $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0216 $filedataoffset += 4; 0217 $info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0218 $filedataoffset += 4; 0219 $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0220 $filedataoffset += 4; 0221 $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0222 $filedataoffset += 8; 0223 $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0224 $filedataoffset += 8; 0225 $info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0226 $filedataoffset += 8; 0227 $info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0228 $filedataoffset += 4; 0229 $info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 0230 $filedataoffset += 1; 0231 $info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3); 0232 $filedataoffset += 3; 0233 0234 } elseif (substr($filedata, 1, 6) == 'theora') { 0235 0236 $info['video']['dataformat'] = 'theora1'; 0237 $info['error'][] = 'Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']'; 0238 //break; 0239 0240 } elseif (substr($filedata, 1, 6) == 'vorbis') { 0241 0242 $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); 0243 0244 } else { 0245 $info['error'][] = 'unexpected'; 0246 //break; 0247 } 0248 //} while ($oggpageinfo['page_seqno'] == 0); 0249 } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00")); 0250 0251 $this->fseek($oggpageinfo['page_start_offset']); 0252 0253 $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'; 0254 //return false; 0255 0256 } else { 0257 0258 $info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; 0259 unset($info['ogg']); 0260 unset($info['mime_type']); 0261 return false; 0262 0263 } 0264 0265 // Page 2 - Comment Header 0266 $oggpageinfo = $this->ParseOggPageHeader(); 0267 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 0268 0269 switch ($info['audio']['dataformat']) { 0270 case 'vorbis': 0271 $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 0272 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); 0273 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' 0274 0275 $this->ParseVorbisComments(); 0276 break; 0277 0278 case 'flac': 0279 $flac = new getid3_flac($this->getid3); 0280 if (!$flac->parseMETAdata()) { 0281 $info['error'][] = 'Failed to parse FLAC headers'; 0282 return false; 0283 } 0284 unset($flac); 0285 break; 0286 0287 case 'speex': 0288 $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); 0289 $this->ParseVorbisComments(); 0290 break; 0291 } 0292 0293 0294 // Last Page - Number of Samples 0295 if (!getid3_lib::intValueSupported($info['avdataend'])) { 0296 0297 $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; 0298 0299 } else { 0300 0301 $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0)); 0302 $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size())); 0303 if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { 0304 $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO'))); 0305 $info['avdataend'] = $this->ftell(); 0306 $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader(); 0307 $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position']; 0308 if ($info['ogg']['samples'] == 0) { 0309 $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero'; 0310 return false; 0311 } 0312 if (!empty($info['audio']['sample_rate'])) { 0313 $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']); 0314 } 0315 } 0316 0317 } 0318 0319 if (!empty($info['ogg']['bitrate_average'])) { 0320 $info['audio']['bitrate'] = $info['ogg']['bitrate_average']; 0321 } elseif (!empty($info['ogg']['bitrate_nominal'])) { 0322 $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal']; 0323 } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) { 0324 $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2; 0325 } 0326 if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) { 0327 if ($info['audio']['bitrate'] == 0) { 0328 $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero'; 0329 return false; 0330 } 0331 $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']); 0332 } 0333 0334 if (isset($info['ogg']['vendor'])) { 0335 $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']); 0336 0337 // Vorbis only 0338 if ($info['audio']['dataformat'] == 'vorbis') { 0339 0340 // Vorbis 1.0 starts with Xiph.Org 0341 if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) { 0342 0343 if ($info['audio']['bitrate_mode'] == 'abr') { 0344 0345 // Set -b 128 on abr files 0346 $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000); 0347 0348 } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) { 0349 // Set -q N on vbr files 0350 $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']); 0351 0352 } 0353 } 0354 0355 if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) { 0356 $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps'; 0357 } 0358 } 0359 } 0360 0361 return true; 0362 } 0363 0364 public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { 0365 $info = &$this->getid3->info; 0366 $info['audio']['dataformat'] = 'vorbis'; 0367 $info['audio']['lossless'] = false; 0368 0369 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 0370 $filedataoffset += 1; 0371 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' 0372 $filedataoffset += 6; 0373 $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0374 $filedataoffset += 4; 0375 $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 0376 $filedataoffset += 1; 0377 $info['audio']['channels'] = $info['ogg']['numberofchannels']; 0378 $info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0379 $filedataoffset += 4; 0380 if ($info['ogg']['samplerate'] == 0) { 0381 $info['error'][] = 'Corrupt Ogg file: sample rate == zero'; 0382 return false; 0383 } 0384 $info['audio']['sample_rate'] = $info['ogg']['samplerate']; 0385 $info['ogg']['samples'] = 0; // filled in later 0386 $info['ogg']['bitrate_average'] = 0; // filled in later 0387 $info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0388 $filedataoffset += 4; 0389 $info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0390 $filedataoffset += 4; 0391 $info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0392 $filedataoffset += 4; 0393 $info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); 0394 $info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); 0395 $info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet 0396 0397 $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr 0398 if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) { 0399 unset($info['ogg']['bitrate_max']); 0400 $info['audio']['bitrate_mode'] = 'abr'; 0401 } 0402 if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { 0403 unset($info['ogg']['bitrate_nominal']); 0404 } 0405 if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) { 0406 unset($info['ogg']['bitrate_min']); 0407 $info['audio']['bitrate_mode'] = 'abr'; 0408 } 0409 return true; 0410 } 0411 0412 public function ParseOggPageHeader() { 0413 // http://xiph.org/ogg/vorbis/doc/framing.html 0414 $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file 0415 0416 $filedata = $this->fread($this->getid3->fread_buffer_size()); 0417 $filedataoffset = 0; 0418 while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { 0419 if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { 0420 // should be found before here 0421 return false; 0422 } 0423 if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { 0424 if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) { 0425 // get some more data, unless eof, in which case fail 0426 return false; 0427 } 0428 } 0429 } 0430 $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' 0431 0432 $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 0433 $filedataoffset += 1; 0434 $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 0435 $filedataoffset += 1; 0436 $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet 0437 $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) 0438 $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) 0439 0440 $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 0441 $filedataoffset += 8; 0442 $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0443 $filedataoffset += 4; 0444 $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0445 $filedataoffset += 4; 0446 $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 0447 $filedataoffset += 4; 0448 $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 0449 $filedataoffset += 1; 0450 $oggheader['page_length'] = 0; 0451 for ($i = 0; $i < $oggheader['page_segments']; $i++) { 0452 $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 0453 $filedataoffset += 1; 0454 $oggheader['page_length'] += $oggheader['segment_table'][$i]; 0455 } 0456 $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; 0457 $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; 0458 $this->fseek($oggheader['header_end_offset']); 0459 0460 return $oggheader; 0461 } 0462 0463 // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 0464 public function ParseVorbisComments() { 0465 $info = &$this->getid3->info; 0466 0467 $OriginalOffset = $this->ftell(); 0468 $commentdataoffset = 0; 0469 $VorbisCommentPage = 1; 0470 0471 switch ($info['audio']['dataformat']) { 0472 case 'vorbis': 0473 case 'speex': 0474 $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block 0475 $this->fseek($CommentStartOffset); 0476 $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; 0477 $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); 0478 0479 if ($info['audio']['dataformat'] == 'vorbis') { 0480 $commentdataoffset += (strlen('vorbis') + 1); 0481 } 0482 break; 0483 0484 case 'flac': 0485 $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; 0486 $this->fseek($CommentStartOffset); 0487 $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']); 0488 break; 0489 0490 default: 0491 return false; 0492 } 0493 0494 $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 0495 $commentdataoffset += 4; 0496 0497 $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); 0498 $commentdataoffset += $VendorSize; 0499 0500 $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 0501 $commentdataoffset += 4; 0502 $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset; 0503 0504 $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); 0505 $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; 0506 for ($i = 0; $i < $CommentsCount; $i++) { 0507 0508 $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; 0509 0510 if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { 0511 if ($oggpageinfo = $this->ParseOggPageHeader()) { 0512 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 0513 0514 $VorbisCommentPage++; 0515 0516 // First, save what we haven't read yet 0517 $AsYetUnusedData = substr($commentdata, $commentdataoffset); 0518 0519 // Then take that data off the end 0520 $commentdata = substr($commentdata, 0, $commentdataoffset); 0521 0522 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 0523 $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 0524 $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 0525 0526 // Finally, stick the unused data back on the end 0527 $commentdata .= $AsYetUnusedData; 0528 0529 //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 0530 $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); 0531 } 0532 0533 } 0534 $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 0535 0536 // replace avdataoffset with position just after the last vorbiscomment 0537 $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4; 0538 0539 $commentdataoffset += 4; 0540 while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) { 0541 if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) { 0542 $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments'; 0543 break 2; 0544 } 0545 0546 $VorbisCommentPage++; 0547 0548 $oggpageinfo = $this->ParseOggPageHeader(); 0549 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 0550 0551 // First, save what we haven't read yet 0552 $AsYetUnusedData = substr($commentdata, $commentdataoffset); 0553 0554 // Then take that data off the end 0555 $commentdata = substr($commentdata, 0, $commentdataoffset); 0556 0557 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 0558 $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 0559 $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 0560 0561 // Finally, stick the unused data back on the end 0562 $commentdata .= $AsYetUnusedData; 0563 0564 //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 0565 if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { 0566 $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); 0567 break; 0568 } 0569 $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); 0570 if ($readlength <= 0) { 0571 $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); 0572 break; 0573 } 0574 $commentdata .= $this->fread($readlength); 0575 0576 //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; 0577 } 0578 $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; 0579 $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); 0580 $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size']; 0581 0582 if (!$commentstring) { 0583 0584 // no comment? 0585 $info['warning'][] = 'Blank Ogg comment ['.$i.']'; 0586 0587 } elseif (strstr($commentstring, '=')) { 0588 0589 $commentexploded = explode('=', $commentstring, 2); 0590 $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); 0591 $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); 0592 0593 if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') { 0594 0595 // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE 0596 // 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. 0597 // http://flac.sourceforge.net/format.html#metadata_block_picture 0598 $flac = new getid3_flac($this->getid3); 0599 $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value'])); 0600 $flac->parsePICTURE(); 0601 $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0]; 0602 unset($flac); 0603 0604 } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') { 0605 0606 $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); 0607 $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure'); 0608 /** @todo use 'coverartmime' where available */ 0609 $imageinfo = getid3_lib::GetDataImageSize($data); 0610 if ($imageinfo === false || !isset($imageinfo['mime'])) { 0611 $this->warning('COVERART vorbiscomment tag contains invalid image'); 0612 continue; 0613 } 0614 0615 $ogg = new self($this->getid3); 0616 $ogg->setStringMode($data); 0617 $info['ogg']['comments']['picture'][] = array( 0618 'image_mime' => $imageinfo['mime'], 0619 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), 0620 ); 0621 unset($ogg); 0622 0623 } else { 0624 0625 $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; 0626 0627 } 0628 0629 } else { 0630 0631 $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; 0632 0633 } 0634 unset($ThisFileInfo_ogg_comments_raw[$i]); 0635 } 0636 unset($ThisFileInfo_ogg_comments_raw); 0637 0638 0639 // Replay Gain Adjustment 0640 // http://privatewww.essex.ac.uk/~djmrob/replaygain/ 0641 if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) { 0642 foreach ($info['ogg']['comments'] as $index => $commentvalue) { 0643 switch ($index) { 0644 case 'rg_audiophile': 0645 case 'replaygain_album_gain': 0646 $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; 0647 unset($info['ogg']['comments'][$index]); 0648 break; 0649 0650 case 'rg_radio': 0651 case 'replaygain_track_gain': 0652 $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; 0653 unset($info['ogg']['comments'][$index]); 0654 break; 0655 0656 case 'replaygain_album_peak': 0657 $info['replay_gain']['album']['peak'] = (double) $commentvalue[0]; 0658 unset($info['ogg']['comments'][$index]); 0659 break; 0660 0661 case 'rg_peak': 0662 case 'replaygain_track_peak': 0663 $info['replay_gain']['track']['peak'] = (double) $commentvalue[0]; 0664 unset($info['ogg']['comments'][$index]); 0665 break; 0666 0667 case 'replaygain_reference_loudness': 0668 $info['replay_gain']['reference_volume'] = (double) $commentvalue[0]; 0669 unset($info['ogg']['comments'][$index]); 0670 break; 0671 0672 default: 0673 // do nothing 0674 break; 0675 } 0676 } 0677 } 0678 0679 $this->fseek($OriginalOffset); 0680 0681 return true; 0682 } 0683 0684 public static function SpeexBandModeLookup($mode) { 0685 static $SpeexBandModeLookup = array(); 0686 if (empty($SpeexBandModeLookup)) { 0687 $SpeexBandModeLookup[0] = 'narrow'; 0688 $SpeexBandModeLookup[1] = 'wide'; 0689 $SpeexBandModeLookup[2] = 'ultra-wide'; 0690 } 0691 return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); 0692 } 0693 0694 0695 public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { 0696 for ($i = 0; $i < $SegmentNumber; $i++) { 0697 $segmentlength = 0; 0698 foreach ($OggInfoArray['segment_table'] as $key => $value) { 0699 $segmentlength += $value; 0700 if ($value < 255) { 0701 break; 0702 } 0703 } 0704 } 0705 return $segmentlength; 0706 } 0707 0708 0709 public static function get_quality_from_nominal_bitrate($nominal_bitrate) { 0710 0711 // decrease precision 0712 $nominal_bitrate = $nominal_bitrate / 1000; 0713 0714 if ($nominal_bitrate < 128) { 0715 // q-1 to q4 0716 $qval = ($nominal_bitrate - 64) / 16; 0717 } elseif ($nominal_bitrate < 256) { 0718 // q4 to q8 0719 $qval = $nominal_bitrate / 32; 0720 } elseif ($nominal_bitrate < 320) { 0721 // q8 to q9 0722 $qval = ($nominal_bitrate + 256) / 64; 0723 } else { 0724 // q9 to q10 0725 $qval = ($nominal_bitrate + 1300) / 180; 0726 } 0727 //return $qval; // 5.031324 0728 //return intval($qval); // 5 0729 return round($qval, 1); // 5 or 4.9 0730 } 0731 0732 public static function TheoraColorSpace($colorspace_id) { 0733 // http://www.theora.org/doc/Theora.pdf (table 6.3) 0734 static $TheoraColorSpaceLookup = array(); 0735 if (empty($TheoraColorSpaceLookup)) { 0736 $TheoraColorSpaceLookup[0] = 'Undefined'; 0737 $TheoraColorSpaceLookup[1] = 'Rec. 470M'; 0738 $TheoraColorSpaceLookup[2] = 'Rec. 470BG'; 0739 $TheoraColorSpaceLookup[3] = 'Reserved'; 0740 } 0741 return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null); 0742 } 0743 0744 public static function TheoraPixelFormat($pixelformat_id) { 0745 // http://www.theora.org/doc/Theora.pdf (table 6.4) 0746 static $TheoraPixelFormatLookup = array(); 0747 if (empty($TheoraPixelFormatLookup)) { 0748 $TheoraPixelFormatLookup[0] = '4:2:0'; 0749 $TheoraPixelFormatLookup[1] = 'Reserved'; 0750 $TheoraPixelFormatLookup[2] = '4:2:2'; 0751 $TheoraPixelFormatLookup[3] = '4:4:4'; 0752 } 0753 return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null); 0754 } 0755 0756 }