home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress2 / wp-includes / ID3 / module.audio.ogg.php < prev    next >
Encoding:
PHP Script  |  2017-07-31  |  38.9 KB  |  841 lines

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