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