rlm@3: <?php
rlm@3: // +----------------------------------------------------------------------+
rlm@3: // | PHP version 5                                                        |
rlm@3: // +----------------------------------------------------------------------+
rlm@3: // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen                 |
rlm@3: // +----------------------------------------------------------------------+
rlm@3: // | This source file is subject to version 2 of the GPL license,         |
rlm@3: // | that is bundled with this package in the file license.txt and is     |
rlm@3: // | available through the world-wide-web at the following url:           |
rlm@3: // | http://www.gnu.org/copyleft/gpl.html                                 |
rlm@3: // +----------------------------------------------------------------------+
rlm@3: // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org    |
rlm@3: // +----------------------------------------------------------------------+
rlm@3: // | Authors: James Heinrich <infoØgetid3*org>                            |
rlm@3: // |          Allan Hansen <ahØartemis*dk>                                |
rlm@3: // +----------------------------------------------------------------------+
rlm@3: // | module.audio.xiph.php                                                |
rlm@3: // | Module for analyzing Xiph.org audio file formats:                    |
rlm@3: // | Ogg Vorbis, FLAC, OggFLAC and Speex - not Ogg Theora                 |
rlm@3: // | dependencies: module.lib.image_size.php (optional)                   |
rlm@3: // +----------------------------------------------------------------------+
rlm@3: //
rlm@3: // $Id: module.audio.xiph.php,v 1.5 2006/12/03 21:12:43 ah Exp $
rlm@3: 
rlm@3:         
rlm@3:         
rlm@3: class getid3_xiph extends getid3_handler
rlm@3: {
rlm@3:     
rlm@3:     public function Analyze() {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         if ($getid3->option_tags_images) {        
rlm@3:             $getid3->include_module('lib.image_size');
rlm@3:         }
rlm@3:         
rlm@3:         fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
rlm@3:         
rlm@3:         $magic = fread($getid3->fp, 4);
rlm@3:         
rlm@3:         if ($magic == 'OggS') {
rlm@3:             return $this->ParseOgg();
rlm@3:         }
rlm@3:         
rlm@3:         if ($magic == 'fLaC') {
rlm@3:             return $this->ParseFLAC();
rlm@3:         }
rlm@3:         
rlm@3:     }
rlm@3:     
rlm@3:     
rlm@3:     
rlm@3:     private function ParseOgg() {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
rlm@3:         
rlm@3:         $getid3->info['audio'] = $getid3->info['ogg'] = array ();
rlm@3:         $info_ogg   = &$getid3->info['ogg'];				
rlm@3:         $info_audio = &$getid3->info['audio'];
rlm@3:         
rlm@3:         $getid3->info['fileformat'] = 'ogg';
rlm@3: 
rlm@3: 
rlm@3:         //// Page 1 - Stream Header
rlm@3: 
rlm@3:         $ogg_page_info = $this->ParseOggPageHeader();
rlm@3:         $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
rlm@3: 
rlm@3:         if (ftell($getid3->fp) >= getid3::FREAD_BUFFER_SIZE) {
rlm@3:             throw new getid3_exception('Could not find start of Ogg page in the first '.getid3::FREAD_BUFFER_SIZE.' bytes (this might not be an Ogg file?)');
rlm@3:         }
rlm@3: 
rlm@3:         $file_data = fread($getid3->fp, $ogg_page_info['page_length']);
rlm@3:         $file_data_offset = 0;
rlm@3: 
rlm@3: 
rlm@3:         // OggFLAC
rlm@3:         if (substr($file_data, 0, 4) == 'fLaC') {
rlm@3: 
rlm@3:             $info_audio['dataformat']   = 'flac';
rlm@3:             $info_audio['bitrate_mode'] = 'vbr';
rlm@3:             $info_audio['lossless']     = true;
rlm@3: 
rlm@3:         } 
rlm@3:     
rlm@3:     
rlm@3:         // Ogg Vorbis
rlm@3:         elseif (substr($file_data, 1, 6) == 'vorbis') {
rlm@3: 
rlm@3:             $info_audio['dataformat'] = 'vorbis';
rlm@3:             $info_audio['lossless']   = false;
rlm@3: 
rlm@3:             $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int($file_data[0]);
rlm@3:             $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'
rlm@3:             
rlm@3:             getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg, $file_data, 7, 
rlm@3:                 array (
rlm@3:                     'bitstreamversion' => 4,
rlm@3:                     'numberofchannels' => 1,
rlm@3:                     'samplerate'       => 4,
rlm@3:                     'bitrate_max'      => 4,
rlm@3:                     'bitrate_nominal'  => 4,
rlm@3:                     'bitrate_min'      => 4
rlm@3:                 )
rlm@3:             );
rlm@3:                                                                                                                      
rlm@3:             $n28 = getid3_lib::LittleEndian2Int($file_data{28});
rlm@3:             $info_ogg['blocksize_small']  = pow(2, $n28 & 0x0F);
rlm@3:             $info_ogg['blocksize_large']  = pow(2, ($n28 & 0xF0) >> 4);
rlm@3:             $info_ogg['stop_bit']         = $n28;
rlm@3:             
rlm@3:             $info_audio['channels']       = $info_ogg['numberofchannels'];
rlm@3:             $info_audio['sample_rate']    = $info_ogg['samplerate'];
rlm@3: 
rlm@3:             $info_audio['bitrate_mode'] = 'vbr';     // overridden if actually abr
rlm@3: 
rlm@3:             if ($info_ogg['bitrate_max'] == 0xFFFFFFFF) {
rlm@3:                 unset($info_ogg['bitrate_max']);
rlm@3:                 $info_audio['bitrate_mode'] = 'abr';
rlm@3:             }
rlm@3:             
rlm@3:             if ($info_ogg['bitrate_nominal'] == 0xFFFFFFFF) {
rlm@3:                 unset($info_ogg['bitrate_nominal']);
rlm@3:             }
rlm@3:             
rlm@3:             if ($info_ogg['bitrate_min'] == 0xFFFFFFFF) {
rlm@3:                 unset($info_ogg['bitrate_min']);
rlm@3:                 $info_audio['bitrate_mode'] = 'abr';
rlm@3:             }
rlm@3:         }
rlm@3:     
rlm@3: 
rlm@3:         // Speex
rlm@3:         elseif (substr($file_data, 0, 8) == 'Speex   ') {
rlm@3: 
rlm@3:             // http://www.speex.org/manual/node10.html
rlm@3: 
rlm@3:             $info_audio['dataformat']   = 'speex';
rlm@3:             $getid3->info['mime_type']  = 'audio/speex';
rlm@3:             $info_audio['bitrate_mode'] = 'abr';
rlm@3:             $info_audio['lossless']     = false;
rlm@3: 
rlm@3:             getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg['pageheader'][$ogg_page_info['page_seqno']], $file_data, 0, 
rlm@3:                 array (
rlm@3:                     'speex_string'           => -8, 		// hard-coded to 'Speex   '
rlm@3:                     'speex_version'          => -20,      	// string                  
rlm@3:                     'speex_version_id'       => 4,
rlm@3:                     'header_size'            => 4,
rlm@3:                     'rate'                   => 4,
rlm@3:                     'mode'                   => 4,
rlm@3:                     'mode_bitstream_version' => 4,
rlm@3:                     'nb_channels'            => 4,
rlm@3:                     'bitrate'                => 4,
rlm@3:                     'framesize'              => 4,
rlm@3:                     'vbr'                    => 4,
rlm@3:                     'frames_per_packet'      => 4,
rlm@3:                     'extra_headers'          => 4,
rlm@3:                     'reserved1'              => 4,
rlm@3:                     'reserved2'              => 4
rlm@3:                 )
rlm@3:             );
rlm@3:                 
rlm@3:             $getid3->info['speex']['speex_version'] = trim($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['speex_version']);
rlm@3:             $getid3->info['speex']['sample_rate']   = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['rate'];
rlm@3:             $getid3->info['speex']['channels']      = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['nb_channels'];
rlm@3:             $getid3->info['speex']['vbr']           = (bool)$info_ogg['pageheader'][$ogg_page_info['page_seqno']]['vbr'];
rlm@3:             $getid3->info['speex']['band_type']     = getid3_xiph::SpeexBandModeLookup($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['mode']);
rlm@3: 
rlm@3:             $info_audio['sample_rate'] = $getid3->info['speex']['sample_rate'];
rlm@3:             $info_audio['channels']    = $getid3->info['speex']['channels'];
rlm@3:             
rlm@3:             if ($getid3->info['speex']['vbr']) {
rlm@3:                 $info_audio['bitrate_mode'] = 'vbr';
rlm@3:             }
rlm@3:         }
rlm@3: 
rlm@3:         // Unsupported Ogg file
rlm@3:         else {
rlm@3: 
rlm@3:             throw new getid3_exception('Expecting either "Speex   " or "vorbis" identifier strings, found neither');
rlm@3:         }
rlm@3: 
rlm@3: 
rlm@3:         //// Page 2 - Comment Header
rlm@3: 
rlm@3:         $ogg_page_info = $this->ParseOggPageHeader();
rlm@3:         $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
rlm@3: 
rlm@3:         switch ($info_audio['dataformat']) {
rlm@3: 
rlm@3:             case 'vorbis':
rlm@3:                 $file_data = fread($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length']);
rlm@3:                 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($file_data, 0, 1));
rlm@3:                 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'
rlm@3:                 $this->ParseVorbisCommentsFilepointer();
rlm@3:                 break;
rlm@3: 
rlm@3:             case 'flac':
rlm@3:                 if (!$this->FLACparseMETAdata()) {
rlm@3:                     throw new getid3_exception('Failed to parse FLAC headers');
rlm@3:                 }
rlm@3:                 break;
rlm@3: 
rlm@3:             case 'speex':
rlm@3:                 fseek($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length'], SEEK_CUR);
rlm@3:                 $this->ParseVorbisCommentsFilepointer();
rlm@3:                 break;
rlm@3:         }
rlm@3: 
rlm@3: 
rlm@3:         //// Last Page - Number of Samples
rlm@3: 
rlm@3:         fseek($getid3->fp, max($getid3->info['avdataend'] - getid3::FREAD_BUFFER_SIZE, 0), SEEK_SET);
rlm@3:         $last_chunk_of_ogg = strrev(fread($getid3->fp, getid3::FREAD_BUFFER_SIZE));
rlm@3:         
rlm@3:         if ($last_OggS_postion = strpos($last_chunk_of_ogg, 'SggO')) {
rlm@3:             fseek($getid3->fp, $getid3->info['avdataend'] - ($last_OggS_postion + strlen('SggO')), SEEK_SET);
rlm@3:             $getid3->info['avdataend'] = ftell($getid3->fp);
rlm@3:             $info_ogg['pageheader']['eos'] = $this->ParseOggPageHeader();
rlm@3:             $info_ogg['samples']           = $info_ogg['pageheader']['eos']['pcm_abs_position'];
rlm@3:             $info_ogg['bitrate_average']   = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / ($info_ogg['samples'] / $info_audio['sample_rate']);
rlm@3:         }
rlm@3: 
rlm@3:         if (!empty($info_ogg['bitrate_average'])) {
rlm@3:             $info_audio['bitrate'] = $info_ogg['bitrate_average'];
rlm@3:         } elseif (!empty($info_ogg['bitrate_nominal'])) {
rlm@3:             $info_audio['bitrate'] = $info_ogg['bitrate_nominal'];
rlm@3:         } elseif (!empty($info_ogg['bitrate_min']) && !empty($info_ogg['bitrate_max'])) {
rlm@3:             $info_audio['bitrate'] = ($info_ogg['bitrate_min'] + $info_ogg['bitrate_max']) / 2;
rlm@3:         }
rlm@3:         if (isset($info_audio['bitrate']) && !isset($getid3->info['playtime_seconds'])) {
rlm@3:             $getid3->info['playtime_seconds'] = (float)((($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $info_audio['bitrate']);
rlm@3:         }
rlm@3: 
rlm@3:         if (isset($info_ogg['vendor'])) {
rlm@3:             $info_audio['encoder'] = preg_replace('/^Encoded with /', '', $info_ogg['vendor']);
rlm@3: 
rlm@3:             // Vorbis only
rlm@3:             if ($info_audio['dataformat'] == 'vorbis') {
rlm@3: 
rlm@3:                 // Vorbis 1.0 starts with Xiph.Org
rlm@3:                 if  (preg_match('/^Xiph.Org/', $info_audio['encoder'])) {
rlm@3: 
rlm@3:                     if ($info_audio['bitrate_mode'] == 'abr') {
rlm@3: 
rlm@3:                         // Set -b 128 on abr files
rlm@3:                         $info_audio['encoder_options'] = '-b '.round($info_ogg['bitrate_nominal'] / 1000);
rlm@3: 
rlm@3:                     } elseif (($info_audio['bitrate_mode'] == 'vbr') && ($info_audio['channels'] == 2) && ($info_audio['sample_rate'] >= 44100) && ($info_audio['sample_rate'] <= 48000)) {
rlm@3:                         // Set -q N on vbr files
rlm@3:                         $info_audio['encoder_options'] = '-q '.getid3_xiph::GetQualityFromNominalBitrate($info_ogg['bitrate_nominal']);
rlm@3:                     }
rlm@3:                 }
rlm@3: 
rlm@3:                 if (empty($info_audio['encoder_options']) && !empty($info_ogg['bitrate_nominal'])) {
rlm@3:                     $info_audio['encoder_options'] = 'Nominal bitrate: '.intval(round($info_ogg['bitrate_nominal'] / 1000)).'kbps';
rlm@3:                 }
rlm@3:             }
rlm@3:         }
rlm@3: 
rlm@3:         return true;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     private function ParseOggPageHeader() {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         // http://xiph.org/ogg/vorbis/doc/framing.html
rlm@3:         $ogg_header['page_start_offset'] = ftell($getid3->fp);      // where we started from in the file
rlm@3:         
rlm@3:         $file_data = fread($getid3->fp, getid3::FREAD_BUFFER_SIZE);
rlm@3:         $file_data_offset = 0;
rlm@3:         
rlm@3:         while ((substr($file_data, $file_data_offset++, 4) != 'OggS')) {
rlm@3:             if ((ftell($getid3->fp) - $ogg_header['page_start_offset']) >= getid3::FREAD_BUFFER_SIZE) {
rlm@3:                 // should be found before here
rlm@3:                 return false;
rlm@3:             }
rlm@3:             if ((($file_data_offset + 28) > strlen($file_data)) || (strlen($file_data) < 28)) {
rlm@3:                 if (feof($getid3->fp) || (($file_data .= fread($getid3->fp, getid3::FREAD_BUFFER_SIZE)) === false)) {
rlm@3:                     // get some more data, unless eof, in which case fail
rlm@3:                     return false;
rlm@3:                 }
rlm@3:             }
rlm@3:         }
rlm@3:         
rlm@3:         $file_data_offset += 3; // page, delimited by 'OggS'
rlm@3:         
rlm@3:         getid3_lib::ReadSequence('LittleEndian2Int', $ogg_header, $file_data, $file_data_offset, 
rlm@3:             array (
rlm@3:                 'stream_structver' => 1,
rlm@3:                 'flags_raw'        => 1,
rlm@3:                 'pcm_abs_position' => 8,
rlm@3:                 'stream_serialno'  => 4,
rlm@3:                 'page_seqno'       => 4,
rlm@3:                 'page_checksum'    => 4,
rlm@3:                 'page_segments'    => 1
rlm@3:             )
rlm@3:         );
rlm@3:         
rlm@3:         $file_data_offset += 23;
rlm@3: 
rlm@3:         $ogg_header['flags']['fresh'] = (bool)($ogg_header['flags_raw'] & 0x01); // fresh packet
rlm@3:         $ogg_header['flags']['bos']   = (bool)($ogg_header['flags_raw'] & 0x02); // first page of logical bitstream (bos)
rlm@3:         $ogg_header['flags']['eos']   = (bool)($ogg_header['flags_raw'] & 0x04); // last page of logical bitstream (eos)
rlm@3: 
rlm@3:         $ogg_header['page_length'] = 0;
rlm@3:         for ($i = 0; $i < $ogg_header['page_segments']; $i++) {
rlm@3:             $ogg_header['segment_table'][$i] = getid3_lib::LittleEndian2Int($file_data{$file_data_offset++});
rlm@3:             $ogg_header['page_length'] += $ogg_header['segment_table'][$i];
rlm@3:         }
rlm@3:         $ogg_header['header_end_offset'] = $ogg_header['page_start_offset'] + $file_data_offset;
rlm@3:         $ogg_header['page_end_offset']   = $ogg_header['header_end_offset'] + $ogg_header['page_length'];
rlm@3:         fseek($getid3->fp, $ogg_header['header_end_offset'], SEEK_SET);
rlm@3: 
rlm@3:         return $ogg_header;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3:     
rlm@3:     private function ParseVorbisCommentsFilepointer() {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3: 
rlm@3:         $original_offset      = ftell($getid3->fp);
rlm@3:         $comment_start_offset = $original_offset;
rlm@3:         $comment_data_offset  = 0;
rlm@3:         $vorbis_comment_page  = 1;
rlm@3: 
rlm@3:         switch ($getid3->info['audio']['dataformat']) {
rlm@3:             
rlm@3:             case 'vorbis':
rlm@3:                 $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset'];  // Second Ogg page, after header block
rlm@3:                 fseek($getid3->fp, $comment_start_offset, SEEK_SET);
rlm@3:                 $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];
rlm@3:                 $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);
rlm@3:                 $comment_data_offset += (strlen('vorbis') + 1);
rlm@3:                 break;
rlm@3:                 
rlm@3: 
rlm@3:             case 'flac':
rlm@3:                 fseek($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4, SEEK_SET);
rlm@3:                 $comment_data = fread($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['block_length']);
rlm@3:                 break;
rlm@3:                 
rlm@3: 
rlm@3:             case 'speex':
rlm@3:                 $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset'];  // Second Ogg page, after header block
rlm@3:                 fseek($getid3->fp, $comment_start_offset, SEEK_SET);
rlm@3:                 $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];
rlm@3:                 $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);
rlm@3:                 break;
rlm@3:                 
rlm@3: 
rlm@3:             default:
rlm@3:                 return false;
rlm@3:         }
rlm@3: 
rlm@3:         $vendor_size = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
rlm@3:         $comment_data_offset += 4;
rlm@3: 
rlm@3:         $getid3->info['ogg']['vendor'] = substr($comment_data, $comment_data_offset, $vendor_size);
rlm@3:         $comment_data_offset += $vendor_size;
rlm@3: 
rlm@3:         $comments_count = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
rlm@3:         $comment_data_offset += 4;
rlm@3:         
rlm@3:         $getid3->info['avdataoffset'] = $comment_start_offset + $comment_data_offset;
rlm@3: 
rlm@3:         for ($i = 0; $i < $comments_count; $i++) {
rlm@3: 
rlm@3:             $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] = $comment_start_offset + $comment_data_offset;
rlm@3: 
rlm@3:             if (ftell($getid3->fp) < ($getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + 4)) {
rlm@3:                 $vorbis_comment_page++;
rlm@3: 
rlm@3:                 $ogg_page_info = $this->ParseOggPageHeader();
rlm@3:                 $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
rlm@3: 
rlm@3:                 // First, save what we haven't read yet
rlm@3:                 $as_yet_unused_data = substr($comment_data, $comment_data_offset);
rlm@3: 
rlm@3:                 // Then take that data off the end
rlm@3:                 $comment_data = substr($comment_data, 0, $comment_data_offset);
rlm@3: 
rlm@3:                 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
rlm@3:                 $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
rlm@3:                 $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
rlm@3: 
rlm@3:                 // Finally, stick the unused data back on the end
rlm@3:                 $comment_data .= $as_yet_unused_data;
rlm@3: 
rlm@3:                 $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1));
rlm@3:             }
rlm@3:             $getid3->info['ogg']['comments_raw'][$i]['size'] = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
rlm@3: 
rlm@3:             // replace avdataoffset with position just after the last vorbiscomment
rlm@3:             $getid3->info['avdataoffset'] = $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + $getid3->info['ogg']['comments_raw'][$i]['size'] + 4;
rlm@3: 
rlm@3:             $comment_data_offset += 4;
rlm@3:             while ((strlen($comment_data) - $comment_data_offset) < $getid3->info['ogg']['comments_raw'][$i]['size']) {
rlm@3:             
rlm@3:                 if (($getid3->info['ogg']['comments_raw'][$i]['size'] > $getid3->info['avdataend']) || ($getid3->info['ogg']['comments_raw'][$i]['size'] < 0)) {
rlm@3:                     throw new getid3_exception('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($getid3->info['ogg']['comments_raw'][$i]['size']).' bytes) - aborting reading comments');
rlm@3:                 }
rlm@3: 
rlm@3:                 $vorbis_comment_page++;
rlm@3: 
rlm@3:                 $ogg_page_info = $this->ParseOggPageHeader();
rlm@3:                 $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
rlm@3: 
rlm@3:                 // First, save what we haven't read yet
rlm@3:                 $as_yet_unused_data = substr($comment_data, $comment_data_offset);
rlm@3: 
rlm@3:                 // Then take that data off the end
rlm@3:                 $comment_data     = substr($comment_data, 0, $comment_data_offset);
rlm@3: 
rlm@3:                 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
rlm@3:                 $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
rlm@3:                 $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
rlm@3: 
rlm@3:                 // Finally, stick the unused data back on the end
rlm@3:                 $comment_data .= $as_yet_unused_data;
rlm@3: 
rlm@3:                 //$comment_data .= fread($getid3->fp, $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_length']);
rlm@3:                 $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1));
rlm@3: 
rlm@3:                 //$filebaseoffset += $ogg_page_info['header_end_offset'] - $ogg_page_info['page_start_offset'];
rlm@3:             }
rlm@3:             $comment_string = substr($comment_data, $comment_data_offset, $getid3->info['ogg']['comments_raw'][$i]['size']);
rlm@3:             $comment_data_offset += $getid3->info['ogg']['comments_raw'][$i]['size'];
rlm@3: 
rlm@3:             if (!$comment_string) {
rlm@3: 
rlm@3:                 // no comment?
rlm@3:                 $getid3->warning('Blank Ogg comment ['.$i.']');
rlm@3: 
rlm@3:             } elseif (strstr($comment_string, '=')) {
rlm@3: 
rlm@3:                 $comment_exploded = explode('=', $comment_string, 2);
rlm@3:                 $getid3->info['ogg']['comments_raw'][$i]['key']   = strtoupper($comment_exploded[0]);
rlm@3:                 $getid3->info['ogg']['comments_raw'][$i]['value'] = @$comment_exploded[1];
rlm@3:                 $getid3->info['ogg']['comments_raw'][$i]['data']  = base64_decode($getid3->info['ogg']['comments_raw'][$i]['value']);
rlm@3: 
rlm@3:                 $getid3->info['ogg']['comments'][strtolower($getid3->info['ogg']['comments_raw'][$i]['key'])][] = $getid3->info['ogg']['comments_raw'][$i]['value'];
rlm@3: 
rlm@3:                 if ($getid3->option_tags_images) {
rlm@3:                     $image_chunk_check = getid3_lib_image_size::get($getid3->info['ogg']['comments_raw'][$i]['data']);
rlm@3:                     $getid3->info['ogg']['comments_raw'][$i]['image_mime'] = image_type_to_mime_type($image_chunk_check[2]);
rlm@3:                 }
rlm@3:                 
rlm@3:                 if (!@$getid3->info['ogg']['comments_raw'][$i]['image_mime'] || ($getid3->info['ogg']['comments_raw'][$i]['image_mime'] == 'application/octet-stream')) {
rlm@3:                     unset($getid3->info['ogg']['comments_raw'][$i]['image_mime']);
rlm@3:                     unset($getid3->info['ogg']['comments_raw'][$i]['data']);
rlm@3:                 }
rlm@3:                 
rlm@3: 
rlm@3:             } else {
rlm@3: 
rlm@3:                 $getid3->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$comment_string);
rlm@3:             }
rlm@3:         }
rlm@3: 
rlm@3: 
rlm@3:         // Replay Gain Adjustment
rlm@3:         // http://privatewww.essex.ac.uk/~djmrob/replaygain/
rlm@3:         if (isset($getid3->info['ogg']['comments']) && is_array($getid3->info['ogg']['comments'])) {
rlm@3:             foreach ($getid3->info['ogg']['comments'] as $index => $commentvalue) {
rlm@3:                 switch ($index) {
rlm@3:                     case 'rg_audiophile':
rlm@3:                     case 'replaygain_album_gain':
rlm@3:                         $getid3->info['replay_gain']['album']['adjustment'] = (float)$commentvalue[0];
rlm@3:                         unset($getid3->info['ogg']['comments'][$index]);
rlm@3:                         break;
rlm@3: 
rlm@3:                     case 'rg_radio':
rlm@3:                     case 'replaygain_track_gain':
rlm@3:                         $getid3->info['replay_gain']['track']['adjustment'] = (float)$commentvalue[0];
rlm@3:                         unset($getid3->info['ogg']['comments'][$index]);
rlm@3:                         break;
rlm@3: 
rlm@3:                     case 'replaygain_album_peak':
rlm@3:                         $getid3->info['replay_gain']['album']['peak'] = (float)$commentvalue[0];
rlm@3:                         unset($getid3->info['ogg']['comments'][$index]);
rlm@3:                         break;
rlm@3: 
rlm@3:                     case 'rg_peak':
rlm@3:                     case 'replaygain_track_peak':
rlm@3:                         $getid3->info['replay_gain']['track']['peak'] = (float)$commentvalue[0];
rlm@3:                         unset($getid3->info['ogg']['comments'][$index]);
rlm@3:                         break;
rlm@3:                         
rlm@3:                     case 'replaygain_reference_loudness':
rlm@3:                         $getid3->info['replay_gain']['reference_volume'] = (float)$commentvalue[0];
rlm@3:                         unset($getid3->info['ogg']['comments'][$index]);
rlm@3:                         break;
rlm@3:                 }
rlm@3:             }
rlm@3:         }
rlm@3: 
rlm@3:         fseek($getid3->fp, $original_offset, SEEK_SET);
rlm@3: 
rlm@3:         return true;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     private function ParseFLAC() {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         // http://flac.sourceforge.net/format.html
rlm@3: 
rlm@3:         $getid3->info['fileformat']            = 'flac';
rlm@3:         $getid3->info['audio']['dataformat']   = 'flac';
rlm@3:         $getid3->info['audio']['bitrate_mode'] = 'vbr';
rlm@3:         $getid3->info['audio']['lossless']     = true;
rlm@3: 
rlm@3:         return $this->FLACparseMETAdata();
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     private function FLACparseMETAdata() {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3: 
rlm@3:         do {
rlm@3:             
rlm@3:             $meta_data_block_offset    = ftell($getid3->fp);
rlm@3:             $meta_data_block_header    = fread($getid3->fp, 4);
rlm@3:             $meta_data_last_block_flag = (bool)(getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x80);
rlm@3:             $meta_data_block_type      = getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x7F;
rlm@3:             $meta_data_block_length    = getid3_lib::BigEndian2Int(substr($meta_data_block_header, 1, 3));
rlm@3:             $meta_data_block_type_text = getid3_xiph::FLACmetaBlockTypeLookup($meta_data_block_type);
rlm@3: 
rlm@3:             if ($meta_data_block_length < 0) {
rlm@3:                 throw new getid3_exception('corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset);
rlm@3:             }
rlm@3: 
rlm@3:             $getid3->info['flac'][$meta_data_block_type_text]['raw'] = array (
rlm@3:                 'offset'          => $meta_data_block_offset,
rlm@3:                 'last_meta_block' => $meta_data_last_block_flag,
rlm@3:                 'block_type'      => $meta_data_block_type,
rlm@3:                 'block_type_text' => $meta_data_block_type_text,
rlm@3:                 'block_length'    => $meta_data_block_length,
rlm@3:                 'block_data'      => @fread($getid3->fp, $meta_data_block_length)
rlm@3:             );
rlm@3:             $getid3->info['avdataoffset'] = ftell($getid3->fp);
rlm@3: 
rlm@3:             switch ($meta_data_block_type_text) {
rlm@3: 
rlm@3:                 case 'STREAMINFO':
rlm@3:                     if (!$this->FLACparseSTREAMINFO($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
rlm@3:                         return false;
rlm@3:                     }
rlm@3:                     break;
rlm@3: 
rlm@3:                 case 'PADDING':
rlm@3:                     // ignore
rlm@3:                     break;
rlm@3: 
rlm@3:                 case 'APPLICATION':
rlm@3:                     if (!$this->FLACparseAPPLICATION($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
rlm@3:                         return false;
rlm@3:                     }
rlm@3:                     break;
rlm@3: 
rlm@3:                 case 'SEEKTABLE':
rlm@3:                     if (!$this->FLACparseSEEKTABLE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
rlm@3:                         return false;
rlm@3:                     }
rlm@3:                     break;
rlm@3: 
rlm@3:                 case 'VORBIS_COMMENT':
rlm@3:                     $old_offset = ftell($getid3->fp);
rlm@3:                     fseek($getid3->fp, 0 - $meta_data_block_length, SEEK_CUR);
rlm@3:                     $this->ParseVorbisCommentsFilepointer($getid3->fp, $getid3->info);
rlm@3:                     fseek($getid3->fp, $old_offset, SEEK_SET);
rlm@3:                     break;
rlm@3: 
rlm@3:                 case 'CUESHEET':
rlm@3:                     if (!$this->FLACparseCUESHEET($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
rlm@3:                         return false;
rlm@3:                     }
rlm@3:                     break;
rlm@3:                     
rlm@3:                 case 'PICTURE':
rlm@3:                     if (!$this->FLACparsePICTURE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
rlm@3:                         return false;
rlm@3:                     }
rlm@3:                     break;
rlm@3: 
rlm@3:                 default:
rlm@3:                     $getid3->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset);
rlm@3:             }
rlm@3: 
rlm@3:         } while ($meta_data_last_block_flag === false);
rlm@3: 
rlm@3: 
rlm@3:         if (isset($getid3->info['flac']['STREAMINFO'])) {
rlm@3:             $getid3->info['flac']['compressed_audio_bytes']   = $getid3->info['avdataend'] - $getid3->info['avdataoffset'];
rlm@3:             $getid3->info['flac']['uncompressed_audio_bytes'] = $getid3->info['flac']['STREAMINFO']['samples_stream'] * $getid3->info['flac']['STREAMINFO']['channels'] * ($getid3->info['flac']['STREAMINFO']['bits_per_sample'] / 8);
rlm@3:             $getid3->info['flac']['compression_ratio']        = $getid3->info['flac']['compressed_audio_bytes'] / $getid3->info['flac']['uncompressed_audio_bytes'];
rlm@3:         }
rlm@3: 
rlm@3:         // set md5_data_source - built into flac 0.5+
rlm@3:         if (isset($getid3->info['flac']['STREAMINFO']['audio_signature'])) {
rlm@3: 
rlm@3:             if ($getid3->info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
rlm@3:                 $getid3->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
rlm@3: 
rlm@3:             } else {
rlm@3: 
rlm@3:                 $getid3->info['md5_data_source'] = '';
rlm@3:                 $md5 = $getid3->info['flac']['STREAMINFO']['audio_signature'];
rlm@3:                 for ($i = 0; $i < strlen($md5); $i++) {
rlm@3:                     $getid3->info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);
rlm@3:                 }
rlm@3:                 if (!preg_match('/^[0-9a-f]{32}$/', $getid3->info['md5_data_source'])) {
rlm@3:                     unset($getid3->info['md5_data_source']);
rlm@3:                 }
rlm@3: 
rlm@3:             }
rlm@3: 
rlm@3:         }
rlm@3: 
rlm@3:         $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];
rlm@3:         if ($getid3->info['audio']['bits_per_sample'] == 8) {
rlm@3:             // special case
rlm@3:             // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
rlm@3:             // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
rlm@3:             $getid3->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
rlm@3:         }
rlm@3:         if (!empty($getid3->info['ogg']['vendor'])) {
rlm@3:             $getid3->info['audio']['encoder'] = $getid3->info['ogg']['vendor'];
rlm@3:         }
rlm@3: 
rlm@3:         return true;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     private function FLACparseSTREAMINFO($meta_data_block_data) {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         getid3_lib::ReadSequence('BigEndian2Int', $getid3->info['flac']['STREAMINFO'], $meta_data_block_data, 0,
rlm@3:             array (
rlm@3:                 'min_block_size' => 2,
rlm@3:                 'max_block_size' => 2,
rlm@3:                 'min_frame_size' => 3,
rlm@3:                 'max_frame_size' => 3
rlm@3:             )
rlm@3:         );
rlm@3: 
rlm@3:         $sample_rate_channels_sample_bits_stream_samples = getid3_lib::BigEndian2Bin(substr($meta_data_block_data, 10, 8));
rlm@3:         
rlm@3:         $getid3->info['flac']['STREAMINFO']['sample_rate']     = bindec(substr($sample_rate_channels_sample_bits_stream_samples,  0, 20));
rlm@3:         $getid3->info['flac']['STREAMINFO']['channels']        = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 20,  3)) + 1;
rlm@3:         $getid3->info['flac']['STREAMINFO']['bits_per_sample'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 23,  5)) + 1;
rlm@3:         $getid3->info['flac']['STREAMINFO']['samples_stream']  = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 28, 36));      // bindec() returns float in case of int overrun
rlm@3:         $getid3->info['flac']['STREAMINFO']['audio_signature'] = substr($meta_data_block_data, 18, 16);
rlm@3: 
rlm@3:         if (!empty($getid3->info['flac']['STREAMINFO']['sample_rate'])) {
rlm@3: 
rlm@3:             $getid3->info['audio']['bitrate_mode']    = 'vbr';
rlm@3:             $getid3->info['audio']['sample_rate']     = $getid3->info['flac']['STREAMINFO']['sample_rate'];
rlm@3:             $getid3->info['audio']['channels']        = $getid3->info['flac']['STREAMINFO']['channels'];
rlm@3:             $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];
rlm@3:             $getid3->info['playtime_seconds']         = $getid3->info['flac']['STREAMINFO']['samples_stream'] / $getid3->info['flac']['STREAMINFO']['sample_rate'];
rlm@3:             $getid3->info['audio']['bitrate']         = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds'];
rlm@3: 
rlm@3:         } else {
rlm@3: 
rlm@3:             throw new getid3_exception('Corrupt METAdata block: STREAMINFO');
rlm@3:         }
rlm@3:         
rlm@3:         unset($getid3->info['flac']['STREAMINFO']['raw']);
rlm@3: 
rlm@3:         return true;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     private function FLACparseAPPLICATION($meta_data_block_data) {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         $application_id = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 0, 4));
rlm@3:         
rlm@3:         $getid3->info['flac']['APPLICATION'][$application_id]['name'] = getid3_xiph::FLACapplicationIDLookup($application_id);
rlm@3:         $getid3->info['flac']['APPLICATION'][$application_id]['data'] = substr($meta_data_block_data, 4);
rlm@3:         
rlm@3:         unset($getid3->info['flac']['APPLICATION']['raw']);
rlm@3: 
rlm@3:         return true;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     private function FLACparseSEEKTABLE($meta_data_block_data) {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         $offset = 0;
rlm@3:         $meta_data_block_length = strlen($meta_data_block_data);
rlm@3:         while ($offset < $meta_data_block_length) {
rlm@3:             $sample_number_string = substr($meta_data_block_data, $offset, 8);
rlm@3:             $offset += 8;
rlm@3:             if ($sample_number_string == "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {
rlm@3: 
rlm@3:                 // placeholder point
rlm@3:                 @$getid3->info['flac']['SEEKTABLE']['placeholders']++;
rlm@3:                 $offset += 10;
rlm@3: 
rlm@3:             } else {
rlm@3: 
rlm@3:                 $sample_number = getid3_lib::BigEndian2Int($sample_number_string);
rlm@3:                 
rlm@3:                 $getid3->info['flac']['SEEKTABLE'][$sample_number]['offset']  = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
rlm@3:                 $offset += 8;
rlm@3:                 
rlm@3:                 $getid3->info['flac']['SEEKTABLE'][$sample_number]['samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 2));
rlm@3:                 $offset += 2;
rlm@3: 
rlm@3:             }
rlm@3:         }
rlm@3:         
rlm@3:         unset($getid3->info['flac']['SEEKTABLE']['raw']);
rlm@3:         
rlm@3:         return true;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     private function FLACparseCUESHEET($meta_data_block_data) {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         $getid3->info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($meta_data_block_data, 0, 128), "\0");
rlm@3:         $getid3->info['flac']['CUESHEET']['lead_in_samples']      = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 128, 8));
rlm@3:         $getid3->info['flac']['CUESHEET']['flags']['is_cd']       = (bool)(getid3_lib::BigEndian2Int($meta_data_block_data[136]) & 0x80);
rlm@3:         $getid3->info['flac']['CUESHEET']['number_tracks']        = getid3_lib::BigEndian2Int($meta_data_block_data[395]);
rlm@3: 
rlm@3:         $offset = 396;
rlm@3:         
rlm@3:         for ($track = 0; $track < $getid3->info['flac']['CUESHEET']['number_tracks']; $track++) {
rlm@3:         
rlm@3:             $track_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
rlm@3:             $offset += 8;
rlm@3: 
rlm@3:             $track_number        = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
rlm@3: 
rlm@3:             $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['sample_offset'] = $track_sample_offset;
rlm@3:             $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['isrc']          = substr($meta_data_block_data, $offset, 12);
rlm@3:             $offset += 12;
rlm@3: 
rlm@3:             $track_flags_raw = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
rlm@3:             $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['is_audio']     = (bool)($track_flags_raw & 0x80);
rlm@3:             $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['pre_emphasis'] = (bool)($track_flags_raw & 0x40);
rlm@3: 
rlm@3:             $offset += 13; // reserved
rlm@3: 
rlm@3:             $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points'] = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
rlm@3: 
rlm@3:             for ($index = 0; $index < $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points']; $index++) {
rlm@3:                 
rlm@3:                 $index_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
rlm@3:                 $offset += 8;
rlm@3:                 
rlm@3:                 $index_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
rlm@3:                 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['indexes'][$index_number] = $index_sample_offset;
rlm@3:                 
rlm@3:                 $offset += 3; // reserved
rlm@3:             }
rlm@3:         }
rlm@3:         
rlm@3:         unset($getid3->info['flac']['CUESHEET']['raw']);
rlm@3:         
rlm@3:         return true;
rlm@3:     }
rlm@3:     
rlm@3:     
rlm@3:     
rlm@3:     private function FLACparsePICTURE($meta_data_block_data) {
rlm@3:         
rlm@3:         $getid3 = $this->getid3;
rlm@3:         
rlm@3:         $picture = &$getid3->info['flac']['PICTURE'][sizeof($getid3->info['flac']['PICTURE']) - 1];
rlm@3:         
rlm@3:         $offset = 0;
rlm@3:         
rlm@3:         $picture['type'] = $this->FLACpictureTypeLookup(getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $picture['mime_type'] = substr($meta_data_block_data, $offset, $length);
rlm@3:         $offset += $length;
rlm@3:         
rlm@3:         $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $picture['description'] = substr($meta_data_block_data, $offset, $length);
rlm@3:         $offset += $length;
rlm@3:         
rlm@3:         $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
rlm@3:         $offset += 4;
rlm@3:         
rlm@3:         $picture['image_data'] = substr($meta_data_block_data, $offset, $length);
rlm@3:         $offset += $length;
rlm@3:         
rlm@3:         unset($getid3->info['flac']['PICTURE']['raw']);
rlm@3:         
rlm@3:         return true;
rlm@3:     }
rlm@3:     
rlm@3:     
rlm@3:     
rlm@3:     public static function SpeexBandModeLookup($mode) {
rlm@3:         
rlm@3:         static $lookup = array (
rlm@3:             0 => 'narrow',
rlm@3:             1 => 'wide',
rlm@3:             2 => 'ultra-wide'
rlm@3:         );
rlm@3:         return (isset($lookup[$mode]) ? $lookup[$mode] : null);
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     public static function OggPageSegmentLength($ogg_info_array, $segment_number=1) {
rlm@3:         
rlm@3:         for ($i = 0; $i < $segment_number; $i++) {
rlm@3:             $segment_length = 0;
rlm@3:             foreach ($ogg_info_array['segment_table'] as $key => $value) {
rlm@3:                 $segment_length += $value;
rlm@3:                 if ($value < 255) {
rlm@3:                     break;
rlm@3:                 }
rlm@3:             }
rlm@3:         }
rlm@3:         return $segment_length;
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     public static function GetQualityFromNominalBitrate($nominal_bitrate) {
rlm@3: 
rlm@3:         // decrease precision
rlm@3:         $nominal_bitrate = $nominal_bitrate / 1000;
rlm@3: 
rlm@3:         if ($nominal_bitrate < 128) {
rlm@3:             // q-1 to q4
rlm@3:             $qval = ($nominal_bitrate - 64) / 16;
rlm@3:         } elseif ($nominal_bitrate < 256) {
rlm@3:             // q4 to q8
rlm@3:             $qval = $nominal_bitrate / 32;
rlm@3:         } elseif ($nominal_bitrate < 320) {
rlm@3:             // q8 to q9
rlm@3:             $qval = ($nominal_bitrate + 256) / 64;
rlm@3:         } else {
rlm@3:             // q9 to q10
rlm@3:             $qval = ($nominal_bitrate + 1300) / 180;
rlm@3:         }
rlm@3:         return round($qval, 1); // 5 or 4.9
rlm@3:     }
rlm@3:     
rlm@3:     
rlm@3:     
rlm@3:     public static function FLACmetaBlockTypeLookup($block_type) {
rlm@3:     
rlm@3:         static $lookup = array (
rlm@3:             0 => 'STREAMINFO',
rlm@3:             1 => 'PADDING',
rlm@3:             2 => 'APPLICATION',
rlm@3:             3 => 'SEEKTABLE',
rlm@3:             4 => 'VORBIS_COMMENT',
rlm@3:             5 => 'CUESHEET',
rlm@3:             6 => 'PICTURE'
rlm@3:         );
rlm@3:         return (isset($lookup[$block_type]) ? $lookup[$block_type] : 'reserved');
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3: 
rlm@3:     public static function FLACapplicationIDLookup($application_id) {
rlm@3:         
rlm@3:         // http://flac.sourceforge.net/id.html
rlm@3:         
rlm@3:         static $lookup = array (
rlm@3:             0x46746F6C => 'flac-tools',                                                 // 'Ftol'
rlm@3:             0x46746F6C => 'Sound Font FLAC',                                            // 'SFFL'
rlm@3:             0x7065656D => 'Parseable Embedded Extensible Metadata (specification)',     //  'peem'
rlm@3:             0x786D6364 => 'xmcd'
rlm@3:             
rlm@3:         );
rlm@3:         return (isset($lookup[$application_id]) ? $lookup[$application_id] : 'reserved');
rlm@3:     }
rlm@3: 
rlm@3: 
rlm@3:     public static function FLACpictureTypeLookup($type_id) {
rlm@3:         
rlm@3:         static $lookup = array (
rlm@3:             
rlm@3:              0 => 'Other',
rlm@3:              1 => "32x32 pixels 'file icon' (PNG only)",
rlm@3:              2 => 'Other file icon',
rlm@3:              3 => 'Cover (front)',
rlm@3:              4 => 'Cover (back)',
rlm@3:              5 => 'Leaflet page',
rlm@3:              6 => 'Media (e.g. label side of CD)',
rlm@3:              7 => 'Lead artist/lead performer/soloist',
rlm@3:              8 => 'Artist/performer',
rlm@3:              9 => 'Conductor',
rlm@3:             10 => 'Band/Orchestra',
rlm@3:             11 => 'Composer',
rlm@3:             12 => 'Lyricist/text writer',
rlm@3:             13 => 'Recording Location',
rlm@3:             14 => 'During recording',
rlm@3:             15 => 'During performance',
rlm@3:             16 => 'Movie/video screen capture',
rlm@3:             17 => 'A bright coloured fish',
rlm@3:             18 => 'Illustration',
rlm@3:             19 => 'Band/artist logotype',
rlm@3:             20 => 'Publisher/Studio logotype'
rlm@3:         );
rlm@3:         return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
rlm@3:     }
rlm@3: 
rlm@3: }
rlm@3: 
rlm@3: ?>