[ Index ] |
PHP Cross Reference of WordPress |
[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'] = $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 = array(); 533 $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file 534 535 $filedata = $this->fread($this->getid3->fread_buffer_size()); 536 $filedataoffset = 0; 537 while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { 538 if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { 539 // should be found before here 540 return false; 541 } 542 if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { 543 if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) { 544 // get some more data, unless eof, in which case fail 545 return false; 546 } 547 } 548 } 549 $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' 550 551 $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 552 $filedataoffset += 1; 553 $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 554 $filedataoffset += 1; 555 $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet 556 $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) 557 $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) 558 559 $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 560 $filedataoffset += 8; 561 $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 562 $filedataoffset += 4; 563 $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 564 $filedataoffset += 4; 565 $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 566 $filedataoffset += 4; 567 $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 568 $filedataoffset += 1; 569 $oggheader['page_length'] = 0; 570 for ($i = 0; $i < $oggheader['page_segments']; $i++) { 571 $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 572 $filedataoffset += 1; 573 $oggheader['page_length'] += $oggheader['segment_table'][$i]; 574 } 575 $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; 576 $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; 577 $this->fseek($oggheader['header_end_offset']); 578 579 return $oggheader; 580 } 581 582 /** 583 * @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 584 * 585 * @return bool 586 */ 587 public function ParseVorbisComments() { 588 $info = &$this->getid3->info; 589 590 $OriginalOffset = $this->ftell(); 591 $commentdata = null; 592 $commentdataoffset = 0; 593 $VorbisCommentPage = 1; 594 $CommentStartOffset = 0; 595 596 switch ($info['audio']['dataformat']) { 597 case 'vorbis': 598 case 'speex': 599 case 'opus': 600 $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block 601 $this->fseek($CommentStartOffset); 602 $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; 603 $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); 604 605 if ($info['audio']['dataformat'] == 'vorbis') { 606 $commentdataoffset += (strlen('vorbis') + 1); 607 } 608 else if ($info['audio']['dataformat'] == 'opus') { 609 $commentdataoffset += strlen('OpusTags'); 610 } 611 612 break; 613 614 case 'flac': 615 $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; 616 $this->fseek($CommentStartOffset); 617 $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']); 618 break; 619 620 default: 621 return false; 622 } 623 624 $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 625 $commentdataoffset += 4; 626 627 $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); 628 $commentdataoffset += $VendorSize; 629 630 $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 631 $commentdataoffset += 4; 632 $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset; 633 634 $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); 635 $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; 636 for ($i = 0; $i < $CommentsCount; $i++) { 637 638 if ($i >= 10000) { 639 // https://github.com/owncloud/music/issues/212#issuecomment-43082336 640 $this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments'); 641 break; 642 } 643 644 $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; 645 646 if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { 647 if ($oggpageinfo = $this->ParseOggPageHeader()) { 648 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 649 650 $VorbisCommentPage++; 651 652 // First, save what we haven't read yet 653 $AsYetUnusedData = substr($commentdata, $commentdataoffset); 654 655 // Then take that data off the end 656 $commentdata = substr($commentdata, 0, $commentdataoffset); 657 658 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 659 $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 660 $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 661 662 // Finally, stick the unused data back on the end 663 $commentdata .= $AsYetUnusedData; 664 665 //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 666 $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); 667 } 668 669 } 670 $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 671 672 // replace avdataoffset with position just after the last vorbiscomment 673 $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4; 674 675 $commentdataoffset += 4; 676 while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) { 677 if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) { 678 $this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments'); 679 break 2; 680 } 681 682 $VorbisCommentPage++; 683 684 if ($oggpageinfo = $this->ParseOggPageHeader()) { 685 $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 686 687 // First, save what we haven't read yet 688 $AsYetUnusedData = substr($commentdata, $commentdataoffset); 689 690 // Then take that data off the end 691 $commentdata = substr($commentdata, 0, $commentdataoffset); 692 693 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 694 $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 695 $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 696 697 // Finally, stick the unused data back on the end 698 $commentdata .= $AsYetUnusedData; 699 700 //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 701 if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { 702 $this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell()); 703 break; 704 } 705 $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); 706 if ($readlength <= 0) { 707 $this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell()); 708 break; 709 } 710 $commentdata .= $this->fread($readlength); 711 712 //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; 713 } else { 714 $this->warning('failed to ParseOggPageHeader() at offset '.$this->ftell()); 715 break; 716 } 717 } 718 $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; 719 $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); 720 $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size']; 721 722 if (!$commentstring) { 723 724 // no comment? 725 $this->warning('Blank Ogg comment ['.$i.']'); 726 727 } elseif (strstr($commentstring, '=')) { 728 729 $commentexploded = explode('=', $commentstring, 2); 730 $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); 731 $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); 732 733 if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') { 734 735 // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE 736 // 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. 737 // http://flac.sourceforge.net/format.html#metadata_block_picture 738 $flac = new getid3_flac($this->getid3); 739 $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value'])); 740 $flac->parsePICTURE(); 741 $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0]; 742 unset($flac); 743 744 } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') { 745 746 $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); 747 $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure'); 748 /** @todo use 'coverartmime' where available */ 749 $imageinfo = getid3_lib::GetDataImageSize($data); 750 if ($imageinfo === false || !isset($imageinfo['mime'])) { 751 $this->warning('COVERART vorbiscomment tag contains invalid image'); 752 continue; 753 } 754 755 $ogg = new self($this->getid3); 756 $ogg->setStringMode($data); 757 $info['ogg']['comments']['picture'][] = array( 758 'image_mime' => $imageinfo['mime'], 759 'datalength' => strlen($data), 760 'picturetype' => 'cover art', 761 'image_height' => $imageinfo['height'], 762 'image_width' => $imageinfo['width'], 763 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), 764 ); 765 unset($ogg); 766 767 } else { 768 769 $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; 770 771 } 772 773 } else { 774 775 $this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring); 776 777 } 778 unset($ThisFileInfo_ogg_comments_raw[$i]); 779 } 780 unset($ThisFileInfo_ogg_comments_raw); 781 782 783 // Replay Gain Adjustment 784 // http://privatewww.essex.ac.uk/~djmrob/replaygain/ 785 if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) { 786 foreach ($info['ogg']['comments'] as $index => $commentvalue) { 787 switch ($index) { 788 case 'rg_audiophile': 789 case 'replaygain_album_gain': 790 $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; 791 unset($info['ogg']['comments'][$index]); 792 break; 793 794 case 'rg_radio': 795 case 'replaygain_track_gain': 796 $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; 797 unset($info['ogg']['comments'][$index]); 798 break; 799 800 case 'replaygain_album_peak': 801 $info['replay_gain']['album']['peak'] = (double) $commentvalue[0]; 802 unset($info['ogg']['comments'][$index]); 803 break; 804 805 case 'rg_peak': 806 case 'replaygain_track_peak': 807 $info['replay_gain']['track']['peak'] = (double) $commentvalue[0]; 808 unset($info['ogg']['comments'][$index]); 809 break; 810 811 case 'replaygain_reference_loudness': 812 $info['replay_gain']['reference_volume'] = (double) $commentvalue[0]; 813 unset($info['ogg']['comments'][$index]); 814 break; 815 816 default: 817 // do nothing 818 break; 819 } 820 } 821 } 822 823 $this->fseek($OriginalOffset); 824 825 return true; 826 } 827 828 /** 829 * @param int $mode 830 * 831 * @return string|null 832 */ 833 public static function SpeexBandModeLookup($mode) { 834 static $SpeexBandModeLookup = array(); 835 if (empty($SpeexBandModeLookup)) { 836 $SpeexBandModeLookup[0] = 'narrow'; 837 $SpeexBandModeLookup[1] = 'wide'; 838 $SpeexBandModeLookup[2] = 'ultra-wide'; 839 } 840 return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); 841 } 842 843 /** 844 * @param array $OggInfoArray 845 * @param int $SegmentNumber 846 * 847 * @return int 848 */ 849 public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { 850 $segmentlength = 0; 851 for ($i = 0; $i < $SegmentNumber; $i++) { 852 $segmentlength = 0; 853 foreach ($OggInfoArray['segment_table'] as $key => $value) { 854 $segmentlength += $value; 855 if ($value < 255) { 856 break; 857 } 858 } 859 } 860 return $segmentlength; 861 } 862 863 /** 864 * @param int $nominal_bitrate 865 * 866 * @return float 867 */ 868 public static function get_quality_from_nominal_bitrate($nominal_bitrate) { 869 870 // decrease precision 871 $nominal_bitrate = $nominal_bitrate / 1000; 872 873 if ($nominal_bitrate < 128) { 874 // q-1 to q4 875 $qval = ($nominal_bitrate - 64) / 16; 876 } elseif ($nominal_bitrate < 256) { 877 // q4 to q8 878 $qval = $nominal_bitrate / 32; 879 } elseif ($nominal_bitrate < 320) { 880 // q8 to q9 881 $qval = ($nominal_bitrate + 256) / 64; 882 } else { 883 // q9 to q10 884 $qval = ($nominal_bitrate + 1300) / 180; 885 } 886 //return $qval; // 5.031324 887 //return intval($qval); // 5 888 return round($qval, 1); // 5 or 4.9 889 } 890 891 /** 892 * @param int $colorspace_id 893 * 894 * @return string|null 895 */ 896 public static function TheoraColorSpace($colorspace_id) { 897 // http://www.theora.org/doc/Theora.pdf (table 6.3) 898 static $TheoraColorSpaceLookup = array(); 899 if (empty($TheoraColorSpaceLookup)) { 900 $TheoraColorSpaceLookup[0] = 'Undefined'; 901 $TheoraColorSpaceLookup[1] = 'Rec. 470M'; 902 $TheoraColorSpaceLookup[2] = 'Rec. 470BG'; 903 $TheoraColorSpaceLookup[3] = 'Reserved'; 904 } 905 return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null); 906 } 907 908 /** 909 * @param int $pixelformat_id 910 * 911 * @return string|null 912 */ 913 public static function TheoraPixelFormat($pixelformat_id) { 914 // http://www.theora.org/doc/Theora.pdf (table 6.4) 915 static $TheoraPixelFormatLookup = array(); 916 if (empty($TheoraPixelFormatLookup)) { 917 $TheoraPixelFormatLookup[0] = '4:2:0'; 918 $TheoraPixelFormatLookup[1] = 'Reserved'; 919 $TheoraPixelFormatLookup[2] = '4:2:2'; 920 $TheoraPixelFormatLookup[3] = '4:4:4'; 921 } 922 return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null); 923 } 924 925 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Dec 6 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |