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